Add renderer texture upload contract

This commit is contained in:
2026-06-02 15:25:31 +02:00
parent 818014127a
commit 1c40602744
8 changed files with 164 additions and 30 deletions

View File

@@ -285,12 +285,13 @@ Known local toolchain state:
source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed
legacy OpenGL implementation files. legacy OpenGL implementation files.
- `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates - `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates
command order, readback bounds, frame-capture sources, and destination buffer command order, texture-upload byte counts, readback bounds, frame-capture
sizes, records render/readback/frame-capture commands, and records trace sources, and destination buffer sizes, records render/upload/readback/
markers without a window or GL context. frame-capture commands, and records trace markers without a window or GL
context.
- `pano_cli record-render` exposes the recording renderer through JSON - `pano_cli record-render` exposes the recording renderer through JSON
automation, including readback and frame-capture command/byte totals, and is automation, including upload/readback/frame-capture command and byte totals,
covered by `pano_cli_record_render_smoke` plus and is covered by `pano_cli_record_render_smoke` plus
`pano_cli_record_render_rejects_oversized_target`. `pano_cli_record_render_rejects_oversized_target`.
- `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory` - `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory`
apply/undo/redo state through JSON automation and is covered by apply/undo/redo state through JSON automation and is covered by

View File

@@ -416,10 +416,10 @@ adding new backends.
Status: started. `pp_renderer_api` exists as a headless renderer-neutral target Status: started. `pp_renderer_api` exists as a headless renderer-neutral target
with texture descriptor, byte-size, viewport, mesh, readback bounds, command with texture descriptor, byte-size, viewport, mesh, readback bounds, command
context, render device, shader program descriptor, mesh, render target, context, render device, shader program descriptor, mesh, render target,
readback byte-size helpers, readback command validation, frame-capture readback byte-size helpers, texture-upload/readback command validation,
byte-size helpers, frame-capture command validation, trace interface frame-capture byte-size helpers, frame-capture command validation, trace
validation, and the canonical PanoPainter shader catalog now consumed by the interface validation, and the canonical PanoPainter shader catalog now consumed
legacy OpenGL app initialization path. by the legacy OpenGL app initialization path.
`pp_renderer_gl` now exists as the first OpenGL backend library and owns pure `pp_renderer_gl` now exists as the first OpenGL backend library and owns pure
OpenGL capability detection for framebuffer fetch, map-buffer alignment, and OpenGL capability detection for framebuffer fetch, map-buffer alignment, and
float texture support. It also owns the OpenGL texture upload-type mapping used float texture support. It also owns the OpenGL texture upload-type mapping used
@@ -719,8 +719,9 @@ Results:
plus malformed payload rejection at the export boundary. plus malformed payload rejection at the export boundary.
- `pp_renderer_api_tests` passed, including shader descriptor validation, - `pp_renderer_api_tests` passed, including shader descriptor validation,
PanoPainter shader catalog validation, readback byte-size and command-order PanoPainter shader catalog validation, readback byte-size and command-order
validation, frame-capture byte-size and command-order validation, recording validation, texture-upload byte-count validation, frame-capture byte-size and
readback/frame-capture command capture, and invalid catalog rejection. command-order validation, recording upload/readback/frame-capture command
capture, and invalid catalog rejection.
- `pp_paint_renderer_compositor_tests` passed. - `pp_paint_renderer_compositor_tests` passed.
- `pp_ui_core_color_tests` passed. - `pp_ui_core_color_tests` passed.
- `pp_ui_core_layout_value_tests` passed. - `pp_ui_core_layout_value_tests` passed.
@@ -814,13 +815,15 @@ Results:
reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL
implementation files. implementation files.
- `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict - `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict
command-order/readback/frame-capture validation; it records commands, trace command-order/texture-upload/readback/frame-capture validation; it records
markers, texture readbacks, and frame captures, giving automation a commands, trace markers, texture uploads/readbacks, and frame captures, giving
backend-neutral render path that does not require a window or GL context. automation a backend-neutral render path that does not require a window or GL
context.
- `pano_cli record-render` exercises that headless recording renderer and emits - `pano_cli record-render` exercises that headless recording renderer and emits
JSON command counts, target dimensions, backend name, trace/draw summary, and JSON command counts, target dimensions, backend name, trace/draw summary, and
readback/frame-capture command/byte totals for agent automation, with an texture-upload/readback/frame-capture command/byte totals for agent
expected-failure smoke for oversized render/readback targets. automation, with an expected-failure smoke for oversized render/readback
targets.
- `pano_cli simulate-document-history` exercises pure document history - `pano_cli simulate-document-history` exercises pure document history
apply/undo/redo behavior and emits JSON layer/frame/history state for agent apply/undo/redo behavior and emits JSON layer/frame/history state for agent
automation. automation.

View File

@@ -202,6 +202,34 @@ pp::foundation::Status RecordingCommandContext::read_texture(
return pp::foundation::Status::success(); return pp::foundation::Status::success();
} }
pp::foundation::Status RecordingCommandContext::upload_texture(
ITexture2D& texture,
ReadbackRegion region,
std::span<const std::byte> rgba_or_channel_bytes) noexcept
{
if (in_render_pass_) {
return pp::foundation::Status::invalid_argument("texture upload must be outside a render pass");
}
const auto desc = texture.desc();
const auto bytes = readback_byte_size(desc, region);
if (!bytes) {
return bytes.status();
}
if (rgba_or_channel_bytes.size() != bytes.value()) {
return pp::foundation::Status::invalid_argument("texture upload byte size does not match the region");
}
push_command(commands_, RecordedRenderCommand {
.kind = RecordedRenderCommandKind::upload_texture,
.texture_desc = desc,
.readback_region = region,
.upload_bytes = bytes.value(),
});
return pp::foundation::Status::success();
}
pp::foundation::Status RecordingCommandContext::capture_frame( pp::foundation::Status RecordingCommandContext::capture_frame(
IRenderTarget& target, IRenderTarget& target,
IReadbackBuffer& destination) noexcept IReadbackBuffer& destination) noexcept
@@ -305,6 +333,8 @@ const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) no
return "bind_mesh"; return "bind_mesh";
case RecordedRenderCommandKind::draw: case RecordedRenderCommandKind::draw:
return "draw"; return "draw";
case RecordedRenderCommandKind::upload_texture:
return "upload_texture";
case RecordedRenderCommandKind::read_texture: case RecordedRenderCommandKind::read_texture:
return "read_texture"; return "read_texture";
case RecordedRenderCommandKind::capture_frame: case RecordedRenderCommandKind::capture_frame:

View File

@@ -13,6 +13,7 @@ enum class RecordedRenderCommandKind : std::uint8_t {
bind_shader, bind_shader,
bind_mesh, bind_mesh,
draw, draw,
upload_texture,
read_texture, read_texture,
capture_frame, capture_frame,
end_render_pass, end_render_pass,
@@ -27,6 +28,7 @@ struct RecordedRenderCommand {
MeshDesc mesh_desc {}; MeshDesc mesh_desc {};
TextureDesc texture_desc {}; TextureDesc texture_desc {};
ReadbackRegion readback_region {}; ReadbackRegion readback_region {};
std::uint64_t upload_bytes = 0;
std::uint64_t readback_bytes = 0; std::uint64_t readback_bytes = 0;
std::uint64_t capture_bytes = 0; std::uint64_t capture_bytes = 0;
const char* component = ""; const char* component = "";
@@ -93,6 +95,10 @@ public:
ITexture2D& texture, ITexture2D& texture,
ReadbackRegion region, ReadbackRegion region,
IReadbackBuffer& destination) noexcept override; IReadbackBuffer& destination) noexcept override;
[[nodiscard]] pp::foundation::Status upload_texture(
ITexture2D& texture,
ReadbackRegion region,
std::span<const std::byte> rgba_or_channel_bytes) noexcept override;
[[nodiscard]] pp::foundation::Status capture_frame( [[nodiscard]] pp::foundation::Status capture_frame(
IRenderTarget& target, IRenderTarget& target,
IReadbackBuffer& destination) noexcept override; IReadbackBuffer& destination) noexcept override;

View File

@@ -4,6 +4,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <span>
namespace pp::renderer { namespace pp::renderer {
@@ -126,6 +127,10 @@ public:
ITexture2D& texture, ITexture2D& texture,
ReadbackRegion region, ReadbackRegion region,
IReadbackBuffer& destination) noexcept = 0; IReadbackBuffer& destination) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status upload_texture(
ITexture2D& texture,
ReadbackRegion region,
std::span<const std::byte> rgba_or_channel_bytes) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status capture_frame( [[nodiscard]] virtual pp::foundation::Status capture_frame(
IRenderTarget& target, IRenderTarget& target,
IReadbackBuffer& destination) noexcept = 0; IReadbackBuffer& destination) noexcept = 0;

View File

@@ -365,7 +365,7 @@ if(TARGET pano_cli)
COMMAND pano_cli record-render --width 32 --height 16) COMMAND pano_cli record-render --width 32 --height 16)
set_tests_properties(pano_cli_record_render_smoke PROPERTIES set_tests_properties(pano_cli_record_render_smoke PROPERTIES
LABELS "renderer;integration;desktop-fast" LABELS "renderer;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"commands\":9.*\"drawCommands\":1.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048") PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"commands\":10.*\"drawCommands\":1.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048")
add_test(NAME pano_cli_record_render_rejects_oversized_target add_test(NAME pano_cli_record_render_rejects_oversized_target
COMMAND "${CMAKE_COMMAND}" COMMAND "${CMAKE_COMMAND}"

View File

@@ -4,6 +4,7 @@
#include "test_harness.h" #include "test_harness.h"
#include <array> #include <array>
#include <cstddef>
#include <string_view> #include <string_view>
using pp::foundation::StatusCode; using pp::foundation::StatusCode;
@@ -168,6 +169,22 @@ public:
return pp::foundation::Status::success(); return pp::foundation::Status::success();
} }
[[nodiscard]] pp::foundation::Status upload_texture(
pp::renderer::ITexture2D& texture,
ReadbackRegion region,
std::span<const std::byte> rgba_or_channel_bytes) noexcept override
{
const auto bytes = readback_byte_size(texture.desc(), region);
if (!bytes) {
return bytes.status();
}
if (rgba_or_channel_bytes.size() != bytes.value()) {
return pp::foundation::Status::invalid_argument("texture upload byte size does not match the region");
}
last_upload_bytes = bytes.value();
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status capture_frame( [[nodiscard]] pp::foundation::Status capture_frame(
IRenderTarget& target, IRenderTarget& target,
pp::renderer::IReadbackBuffer& destination) noexcept override pp::renderer::IReadbackBuffer& destination) noexcept override
@@ -190,6 +207,7 @@ public:
bool in_render_pass = false; bool in_render_pass = false;
const char* shader_name = nullptr; const char* shader_name = nullptr;
std::uint64_t last_upload_bytes = 0;
std::uint64_t last_readback_bytes = 0; std::uint64_t last_readback_bytes = 0;
std::uint64_t last_capture_bytes = 0; std::uint64_t last_capture_bytes = 0;
}; };
@@ -463,6 +481,7 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
FakeRenderTarget target; FakeRenderTarget target;
FakeTexture texture; FakeTexture texture;
FakeReadbackBuffer readback_buffer(64U * 32U * 4U); FakeReadbackBuffer readback_buffer(64U * 32U * 4U);
const std::array<std::byte, 80> upload_bytes {};
FakeShaderProgram shader; FakeShaderProgram shader;
FakeMesh mesh; FakeMesh mesh;
@@ -482,6 +501,11 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
const auto draw_after_end = context.draw(); const auto draw_after_end = context.draw();
PP_EXPECT(h, !draw_after_end.ok()); PP_EXPECT(h, !draw_after_end.ok());
PP_EXPECT(h, draw_after_end.code == StatusCode::invalid_argument); PP_EXPECT(h, draw_after_end.code == StatusCode::invalid_argument);
PP_EXPECT(h, context.upload_texture(
texture,
ReadbackRegion { .x = 2, .y = 3, .width = 4, .height = 5 },
upload_bytes)
.ok());
PP_EXPECT(h, context.read_texture( PP_EXPECT(h, context.read_texture(
texture, texture,
ReadbackRegion { .x = 2, .y = 3, .width = 4, .height = 5 }, ReadbackRegion { .x = 2, .y = 3, .width = 4, .height = 5 },
@@ -489,6 +513,7 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
.ok()); .ok());
PP_EXPECT(h, context.capture_frame(target, readback_buffer).ok()); PP_EXPECT(h, context.capture_frame(target, readback_buffer).ok());
PP_EXPECT(h, device.context.shader_name == std::string_view("fake-shader")); PP_EXPECT(h, device.context.shader_name == std::string_view("fake-shader"));
PP_EXPECT(h, device.context.last_upload_bytes == 80U);
PP_EXPECT(h, device.context.last_readback_bytes == 80U); PP_EXPECT(h, device.context.last_readback_bytes == 80U);
PP_EXPECT(h, device.context.last_capture_bytes == 8192U); PP_EXPECT(h, device.context.last_capture_bytes == 8192U);
} }
@@ -502,6 +527,7 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
.render_target = true, .render_target = true,
}); });
RecordingReadbackBuffer readback_buffer(64U * 32U * 4U); RecordingReadbackBuffer readback_buffer(64U * 32U * 4U);
const std::array<std::byte, 96> upload_bytes {};
RecordingRenderTarget target(TextureDesc { RecordingRenderTarget target(TextureDesc {
.extent = Extent2D { .width = 64, .height = 32 }, .extent = Extent2D { .width = 64, .height = 32 },
.format = TextureFormat::rgba8, .format = TextureFormat::rgba8,
@@ -539,28 +565,41 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
PP_EXPECT(h, commands[6].kind == RecordedRenderCommandKind::end_render_pass); PP_EXPECT(h, commands[6].kind == RecordedRenderCommandKind::end_render_pass);
PP_EXPECT(h, recorded_render_command_kind_name(commands[5].kind) == std::string_view("draw")); PP_EXPECT(h, recorded_render_command_kind_name(commands[5].kind) == std::string_view("draw"));
PP_EXPECT(h, context.upload_texture(
texture,
ReadbackRegion { .x = 4, .y = 5, .width = 8, .height = 3 },
upload_bytes)
.ok());
const auto commands_after_upload = device.commands();
PP_EXPECT(h, commands_after_upload.size() == 8U);
PP_EXPECT(h, commands_after_upload[7].kind == RecordedRenderCommandKind::upload_texture);
PP_EXPECT(h, commands_after_upload[7].texture_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_upload[7].readback_region.x == 4U);
PP_EXPECT(h, commands_after_upload[7].upload_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_upload[7].kind) == std::string_view("upload_texture"));
PP_EXPECT(h, context.read_texture( PP_EXPECT(h, context.read_texture(
texture, texture,
ReadbackRegion { .x = 4, .y = 5, .width = 8, .height = 3 }, ReadbackRegion { .x = 4, .y = 5, .width = 8, .height = 3 },
readback_buffer) readback_buffer)
.ok()); .ok());
const auto commands_after_readback = device.commands(); const auto commands_after_readback = device.commands();
PP_EXPECT(h, commands_after_readback.size() == 8U); PP_EXPECT(h, commands_after_readback.size() == 9U);
PP_EXPECT(h, commands_after_readback[7].kind == RecordedRenderCommandKind::read_texture); PP_EXPECT(h, commands_after_readback[8].kind == RecordedRenderCommandKind::read_texture);
PP_EXPECT(h, commands_after_readback[7].texture_desc.extent.width == 64U); PP_EXPECT(h, commands_after_readback[8].texture_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_readback[7].readback_region.x == 4U); PP_EXPECT(h, commands_after_readback[8].readback_region.x == 4U);
PP_EXPECT(h, commands_after_readback[7].readback_region.height == 3U); PP_EXPECT(h, commands_after_readback[8].readback_region.height == 3U);
PP_EXPECT(h, commands_after_readback[7].readback_bytes == 96U); PP_EXPECT(h, commands_after_readback[8].readback_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[7].kind) == std::string_view("read_texture")); PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[8].kind) == std::string_view("read_texture"));
PP_EXPECT(h, context.capture_frame(target, readback_buffer).ok()); PP_EXPECT(h, context.capture_frame(target, readback_buffer).ok());
const auto commands_after_capture = device.commands(); const auto commands_after_capture = device.commands();
PP_EXPECT(h, commands_after_capture.size() == 9U); PP_EXPECT(h, commands_after_capture.size() == 10U);
PP_EXPECT(h, commands_after_capture[8].kind == RecordedRenderCommandKind::capture_frame); PP_EXPECT(h, commands_after_capture[9].kind == RecordedRenderCommandKind::capture_frame);
PP_EXPECT(h, commands_after_capture[8].target_desc.extent.width == 64U); PP_EXPECT(h, commands_after_capture[9].target_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_capture[8].target_desc.extent.height == 32U); PP_EXPECT(h, commands_after_capture[9].target_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_capture[8].capture_bytes == 8192U); PP_EXPECT(h, commands_after_capture[9].capture_bytes == 8192U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[8].kind) == std::string_view("capture_frame")); PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[9].kind) == std::string_view("capture_frame"));
device.clear(); device.clear();
PP_EXPECT(h, device.commands().empty()); PP_EXPECT(h, device.commands().empty());
@@ -586,6 +625,8 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
}); });
RecordingReadbackBuffer small_readback_buffer(3U); RecordingReadbackBuffer small_readback_buffer(3U);
RecordingReadbackBuffer full_readback_buffer(32U * 16U * 4U); RecordingReadbackBuffer full_readback_buffer(32U * 16U * 4U);
const std::array<std::byte, 4> one_pixel_upload {};
const std::array<std::byte, 3> undersized_upload {};
RecordingShaderProgram shader("strict-shader"); RecordingShaderProgram shader("strict-shader");
RecordingMesh mesh(MeshDesc { .vertex_count = 3, .topology = PrimitiveTopology::triangles }); RecordingMesh mesh(MeshDesc { .vertex_count = 3, .topology = PrimitiveTopology::triangles });
RecordingMesh empty_mesh(MeshDesc {}); RecordingMesh empty_mesh(MeshDesc {});
@@ -601,6 +642,13 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, device.commands().empty()); PP_EXPECT(h, device.commands().empty());
PP_EXPECT(h, context.begin_render_pass(target, ClearColor {}).ok()); PP_EXPECT(h, context.begin_render_pass(target, ClearColor {}).ok());
const auto upload_during_render_pass = context.upload_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
one_pixel_upload);
PP_EXPECT(h, !upload_during_render_pass.ok());
PP_EXPECT(h, upload_during_render_pass.code == StatusCode::invalid_argument);
const auto read_during_render_pass = context.read_texture( const auto read_during_render_pass = context.read_texture(
texture, texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 }, ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
@@ -644,6 +692,20 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, !read_outside_bounds.ok()); PP_EXPECT(h, !read_outside_bounds.ok());
PP_EXPECT(h, read_outside_bounds.code == StatusCode::out_of_range); PP_EXPECT(h, read_outside_bounds.code == StatusCode::out_of_range);
const auto upload_outside_bounds = context.upload_texture(
texture,
ReadbackRegion { .x = 31, .y = 15, .width = 2, .height = 1 },
one_pixel_upload);
PP_EXPECT(h, !upload_outside_bounds.ok());
PP_EXPECT(h, upload_outside_bounds.code == StatusCode::out_of_range);
const auto upload_with_wrong_size = context.upload_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
undersized_upload);
PP_EXPECT(h, !upload_with_wrong_size.ok());
PP_EXPECT(h, upload_with_wrong_size.code == StatusCode::invalid_argument);
const auto read_into_small_buffer = context.read_texture( const auto read_into_small_buffer = context.read_texture(
texture, texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 }, ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },

View File

@@ -2223,6 +2223,12 @@ int record_render(int argc, char** argv)
}); });
pp::renderer::RecordingReadbackBuffer readback_buffer( pp::renderer::RecordingReadbackBuffer readback_buffer(
static_cast<std::uint64_t>(args.width) * args.height * 4U); static_cast<std::uint64_t>(args.width) * args.height * 4U);
const std::array<std::byte, 4> upload_pixel {
std::byte { 0xff },
std::byte { 0x00 },
std::byte { 0xff },
std::byte { 0xff },
};
pp::renderer::RecordingShaderProgram shader("pano-cli-record-render"); pp::renderer::RecordingShaderProgram shader("pano-cli-record-render");
pp::renderer::RecordingMesh mesh(pp::renderer::MeshDesc { pp::renderer::RecordingMesh mesh(pp::renderer::MeshDesc {
.vertex_count = 3, .vertex_count = 3,
@@ -2232,6 +2238,20 @@ int record_render(int argc, char** argv)
device.trace()->marker("renderer", "pano_cli_record_render"); device.trace()->marker("renderer", "pano_cli_record_render");
auto& context = device.immediate_context(); auto& context = device.immediate_context();
const auto upload_status = context.upload_texture(
texture,
pp::renderer::ReadbackRegion {
.x = 0,
.y = 0,
.width = 1,
.height = 1,
},
upload_pixel);
if (!upload_status.ok()) {
print_error("record-render", upload_status.message);
return 2;
}
const auto begin_status = context.begin_render_pass( const auto begin_status = context.begin_render_pass(
target, target,
pp::renderer::ClearColor { .r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F }); pp::renderer::ClearColor { .r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F });
@@ -2286,15 +2306,20 @@ int record_render(int argc, char** argv)
} }
std::size_t draw_commands = 0; std::size_t draw_commands = 0;
std::size_t upload_commands = 0;
std::size_t readback_commands = 0; std::size_t readback_commands = 0;
std::size_t capture_commands = 0; std::size_t capture_commands = 0;
std::size_t trace_markers = 0; std::size_t trace_markers = 0;
std::uint64_t upload_bytes = 0;
std::uint64_t readback_bytes = 0; std::uint64_t readback_bytes = 0;
std::uint64_t capture_bytes = 0; std::uint64_t capture_bytes = 0;
const auto commands = device.commands(); const auto commands = device.commands();
for (const auto& command : commands) { for (const auto& command : commands) {
if (command.kind == pp::renderer::RecordedRenderCommandKind::draw) { if (command.kind == pp::renderer::RecordedRenderCommandKind::draw) {
++draw_commands; ++draw_commands;
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::upload_texture) {
++upload_commands;
upload_bytes += command.upload_bytes;
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::read_texture) { } else if (command.kind == pp::renderer::RecordedRenderCommandKind::read_texture) {
++readback_commands; ++readback_commands;
readback_bytes += command.readback_bytes; readback_bytes += command.readback_bytes;
@@ -2313,6 +2338,8 @@ int record_render(int argc, char** argv)
<< ",\"format\":\"rgba8\"}" << ",\"format\":\"rgba8\"}"
<< ",\"commands\":" << commands.size() << ",\"commands\":" << commands.size()
<< ",\"drawCommands\":" << draw_commands << ",\"drawCommands\":" << draw_commands
<< ",\"uploadCommands\":" << upload_commands
<< ",\"uploadBytes\":" << upload_bytes
<< ",\"readbackCommands\":" << readback_commands << ",\"readbackCommands\":" << readback_commands
<< ",\"readbackBytes\":" << readback_bytes << ",\"readbackBytes\":" << readback_bytes
<< ",\"captureCommands\":" << capture_commands << ",\"captureCommands\":" << capture_commands