From bbe3db1747c5b8bd1d7baaacc388a2985e4fc506 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 2 Jun 2026 16:55:23 +0200 Subject: [PATCH] Add renderer trace scope contract --- docs/modernization/build-inventory.md | 7 +- docs/modernization/roadmap.md | 9 +- src/renderer_api/recording_renderer.cpp | 41 +++- src/renderer_api/recording_renderer.h | 7 +- src/renderer_api/renderer_api.cpp | 35 +++ src/renderer_api/renderer_api.h | 6 +- tests/CMakeLists.txt | 2 +- tests/renderer_api/renderer_api_tests.cpp | 273 +++++++++++++++------- tools/pano_cli/main.cpp | 26 ++- 9 files changed, 305 insertions(+), 101 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index a6fabed..82902e4 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -295,12 +295,13 @@ Known local toolchain state: render-pass-clear/scissor/depth/blend/shader-uniform/texture-bind/ sampler-bind/draw/upload/mipmap-generation/texture-copy/readback/ frame-capture/blit commands, draw mesh inputs, explicit draw ranges, and - records trace markers without a window or GL context. + records trace markers and scopes without a window or GL context. - `pano_cli record-render` exposes the recording renderer through JSON automation, including render-pass/depth-clear counts, scissor/depth/blend/ shader-uniform/texture-bind/sampler-bind/upload/texture-copy/readback/ - frame-capture/blit command and byte totals, backend resource creation counts, - plus draw descriptor vertex/index totals, and is covered by + frame-capture/blit command and byte totals, trace marker/scope counts, + backend resource creation counts, plus draw descriptor vertex/index totals, + and is covered by `pano_cli_record_render_smoke` plus `pano_cli_record_render_rejects_oversized_target`. - `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory` diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index eb1cf3b..aeeeb90 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -421,7 +421,7 @@ texture mip-level validation, texture-upload/readback command validation, mipmap-generation command validation, frame-capture byte-size helpers, frame-capture command validation, render-target blit validation, texture-slot binding validation, blend-state validation, scissor-state validation, -depth-state validation, trace interface validation, sampler-state validation, +depth-state validation, trace marker/scope validation, sampler-state validation, and the canonical PanoPainter shader catalog now consumed by the legacy OpenGL app initialization path. `pp_renderer_gl` now exists as the first OpenGL backend library and owns pure @@ -725,7 +725,8 @@ Results: PanoPainter shader catalog validation, explicit texture usage validation, texture mip-level validation, readback byte-size and command-order validation, texture-upload byte-count validation, mipmap-generation command - validation, frame-capture byte-size and command-order validation, + validation, trace marker/scope validation, frame-capture byte-size and + command-order validation, render-target blit validation, texture-slot binding validation, blend-state validation, scissor-state validation, render-pass color/depth/stencil clear validation, shader-uniform write validation, draw descriptor/range @@ -834,7 +835,7 @@ Results: mipmap-generation/readback/frame-capture/blit validation plus explicit draw descriptor and texture-copy validation; it creates validated textures, render targets, shaders, meshes, and readback buffers, then records commands, - trace markers, render-pass + trace markers/scopes, render-pass color/depth/stencil clear intent, scissor state, depth state, blend state, shader uniform writes, texture/sampler binds, draw mesh inputs, explicit draw ranges, texture uploads/mipmap generations/copies/readbacks, frame captures, @@ -842,7 +843,7 @@ Results: does not require a window or GL context. - `pano_cli record-render` exercises that headless recording renderer and emits JSON command counts, resource creation counts, target dimensions, backend - name, trace/draw summary, render-pass/depth-clear counts, and draw + name, trace marker/scope and draw summary, render-pass/depth-clear counts, and draw descriptor vertex/index totals, scissor/depth/blend-state plus shader-uniform/texture/sampler-bind/upload/texture-copy/readback/ frame-capture/blit command/byte totals for agent automation, with an diff --git a/src/renderer_api/recording_renderer.cpp b/src/renderer_api/recording_renderer.cpp index b39840e..914a7a0 100644 --- a/src/renderer_api/recording_renderer.cpp +++ b/src/renderer_api/recording_renderer.cpp @@ -575,13 +575,48 @@ RecordingRenderTrace::RecordingRenderTrace(std::vector& c { } -void RecordingRenderTrace::marker(const char* component, const char* name) noexcept +pp::foundation::Status RecordingRenderTrace::marker(const char* component, const char* name) noexcept { + const auto status = validate_trace_label(component, name); + if (!status.ok()) { + return status; + } + push_command(commands_, RecordedRenderCommand { .kind = RecordedRenderCommandKind::trace_marker, .component = non_null_name(component), .name = non_null_name(name), }); + return pp::foundation::Status::success(); +} + +pp::foundation::Status RecordingRenderTrace::begin_scope(const char* component, const char* name) noexcept +{ + const auto status = validate_trace_label(component, name); + if (!status.ok()) { + return status; + } + + push_command(commands_, RecordedRenderCommand { + .kind = RecordedRenderCommandKind::trace_begin_scope, + .component = non_null_name(component), + .name = non_null_name(name), + }); + ++scope_depth_; + return pp::foundation::Status::success(); +} + +pp::foundation::Status RecordingRenderTrace::end_scope() noexcept +{ + if (scope_depth_ == 0U) { + return pp::foundation::Status::invalid_argument("trace scope has not begun"); + } + + push_command(commands_, RecordedRenderCommand { + .kind = RecordedRenderCommandKind::trace_end_scope, + }); + --scope_depth_; + return pp::foundation::Status::success(); } RecordingRenderDevice::RecordingRenderDevice() noexcept @@ -731,6 +766,10 @@ const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) no return "end_render_pass"; case RecordedRenderCommandKind::trace_marker: return "trace_marker"; + case RecordedRenderCommandKind::trace_begin_scope: + return "trace_begin_scope"; + case RecordedRenderCommandKind::trace_end_scope: + return "trace_end_scope"; } return "unknown"; diff --git a/src/renderer_api/recording_renderer.h b/src/renderer_api/recording_renderer.h index a7fb927..7dedd17 100644 --- a/src/renderer_api/recording_renderer.h +++ b/src/renderer_api/recording_renderer.h @@ -27,6 +27,8 @@ enum class RecordedRenderCommandKind : std::uint8_t { blit_render_target, end_render_pass, trace_marker, + trace_begin_scope, + trace_end_scope, }; struct RecordedRenderCommand { @@ -176,10 +178,13 @@ private: class RecordingRenderTrace final : public IRenderTrace { public: explicit RecordingRenderTrace(std::vector& commands) noexcept; - void marker(const char* component, const char* name) noexcept override; + [[nodiscard]] pp::foundation::Status marker(const char* component, const char* name) noexcept override; + [[nodiscard]] pp::foundation::Status begin_scope(const char* component, const char* name) noexcept override; + [[nodiscard]] pp::foundation::Status end_scope() noexcept override; private: std::vector* commands_ = nullptr; + std::uint32_t scope_depth_ = 0; }; class RecordingRenderDevice final : public IRenderDevice { diff --git a/src/renderer_api/renderer_api.cpp b/src/renderer_api/renderer_api.cpp index f033ee7..c89588c 100644 --- a/src/renderer_api/renderer_api.cpp +++ b/src/renderer_api/renderer_api.cpp @@ -12,6 +12,20 @@ namespace { return text == nullptr || text[0] == '\0'; } +[[nodiscard]] std::size_t bounded_c_string_length(const char* text, std::size_t limit) noexcept +{ + if (text == nullptr) { + return 0; + } + + std::size_t length = 0; + while (length <= limit && text[length] != '\0') { + ++length; + } + + return length; +} + [[nodiscard]] pp::foundation::Status validate_shader_stage_source( ShaderStageSource source, const char* stage_name) noexcept @@ -506,6 +520,27 @@ pp::foundation::Status validate_shader_uniform_write( return pp::foundation::Status::success(); } +pp::foundation::Status validate_trace_label(const char* component, const char* name) noexcept +{ + if (is_empty_c_string(component)) { + return pp::foundation::Status::invalid_argument("trace component must not be empty"); + } + + if (is_empty_c_string(name)) { + return pp::foundation::Status::invalid_argument("trace name must not be empty"); + } + + if (bounded_c_string_length(component, max_trace_label_bytes) > max_trace_label_bytes) { + return pp::foundation::Status::out_of_range("trace component exceeds the configured limit"); + } + + if (bounded_c_string_length(name, max_trace_label_bytes) > max_trace_label_bytes) { + return pp::foundation::Status::out_of_range("trace name exceeds the configured limit"); + } + + return pp::foundation::Status::success(); +} + pp::foundation::Status validate_readback_region(TextureDesc desc, ReadbackRegion region) noexcept { const auto extent_status = validate_extent(desc.extent); diff --git a/src/renderer_api/renderer_api.h b/src/renderer_api/renderer_api.h index 62a6e4d..a66fee6 100644 --- a/src/renderer_api/renderer_api.h +++ b/src/renderer_api/renderer_api.h @@ -16,6 +16,7 @@ constexpr std::uint32_t max_texture_slots = 32; constexpr std::uint64_t max_texture_bytes = 1024ULL * 1024ULL * 1024ULL; constexpr std::size_t max_shader_source_bytes = 4ULL * 1024ULL * 1024ULL; constexpr std::size_t max_shader_uniform_bytes = 64ULL * 1024ULL; +constexpr std::size_t max_trace_label_bytes = 256; enum class TextureFormat : std::uint8_t { rgba8, @@ -244,7 +245,9 @@ public: class IRenderTrace { public: virtual ~IRenderTrace() = default; - virtual void marker(const char* component, const char* name) noexcept = 0; + [[nodiscard]] virtual pp::foundation::Status marker(const char* component, const char* name) noexcept = 0; + [[nodiscard]] virtual pp::foundation::Status begin_scope(const char* component, const char* name) noexcept = 0; + [[nodiscard]] virtual pp::foundation::Status end_scope() noexcept = 0; }; class ICommandContext { @@ -338,6 +341,7 @@ public: [[nodiscard]] pp::foundation::Status validate_shader_uniform_write( const char* name, std::span bytes) noexcept; +[[nodiscard]] pp::foundation::Status validate_trace_label(const char* component, const char* name) noexcept; [[nodiscard]] pp::foundation::Result texture_byte_size(TextureDesc desc) noexcept; [[nodiscard]] pp::foundation::Result readback_byte_size( TextureDesc desc, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 562b89a..d85bd52 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -365,7 +365,7 @@ if(TARGET pano_cli) COMMAND pano_cli record-render --width 32 --height 16) set_tests_properties(pano_cli_record_render_smoke PROPERTIES LABELS "renderer;integration;desktop-fast" - PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"createdResources\":6.*\"commands\":18.*\"renderPasses\":1.*\"depthClears\":1.*\"stencilClears\":0.*\"drawCommands\":1.*\"drawVertices\":3.*\"drawIndices\":3.*\"scissorCommands\":1.*\"blendCommands\":1.*\"depthCommands\":1.*\"uniformCommands\":1.*\"uniformBytes\":64.*\"bindTextureCommands\":1.*\"bindSamplerCommands\":1.*\"boundTextureBytes\":2048.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"copyCommands\":1.*\"copySourceBytes\":2048.*\"copyDestinationBytes\":2048.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048") + PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"createdResources\":6.*\"commands\":20.*\"renderPasses\":1.*\"depthClears\":1.*\"stencilClears\":0.*\"drawCommands\":1.*\"drawVertices\":3.*\"drawIndices\":3.*\"scissorCommands\":1.*\"blendCommands\":1.*\"depthCommands\":1.*\"uniformCommands\":1.*\"uniformBytes\":64.*\"bindTextureCommands\":1.*\"bindSamplerCommands\":1.*\"boundTextureBytes\":2048.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"copyCommands\":1.*\"copySourceBytes\":2048.*\"copyDestinationBytes\":2048.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048.*\"traceMarkers\":1.*\"traceBeginScopes\":1.*\"traceEndScopes\":1") add_test(NAME pano_cli_record_render_rejects_oversized_target COMMAND "${CMAKE_COMMAND}" diff --git a/tests/renderer_api/renderer_api_tests.cpp b/tests/renderer_api/renderer_api_tests.cpp index 97f9122..9e6fee9 100644 --- a/tests/renderer_api/renderer_api_tests.cpp +++ b/tests/renderer_api/renderer_api_tests.cpp @@ -61,6 +61,7 @@ using pp::renderer::max_shader_uniform_bytes; using pp::renderer::max_mip_levels_for_extent; using pp::renderer::max_texture_dimension; using pp::renderer::max_texture_slots; +using pp::renderer::max_trace_label_bytes; using pp::renderer::panopainter_shader_catalog; using pp::renderer::primitive_topology_name; using pp::renderer::readback_byte_size; @@ -92,6 +93,7 @@ using pp::renderer::validate_texture_copy_descs; using pp::renderer::validate_texture_desc; using pp::renderer::validate_texture_slot; using pp::renderer::validate_texture_usage; +using pp::renderer::validate_trace_label; using pp::renderer::validate_viewport; namespace { @@ -190,14 +192,50 @@ private: class FakeTrace final : public IRenderTrace { public: - void marker(const char* component, const char* name) noexcept override + [[nodiscard]] pp::foundation::Status marker(const char* component, const char* name) noexcept override { + const auto status = validate_trace_label(component, name); + if (!status.ok()) { + return status; + } + last_component = component; last_name = name; + ++marker_count; + return pp::foundation::Status::success(); + } + + [[nodiscard]] pp::foundation::Status begin_scope(const char* component, const char* name) noexcept override + { + const auto status = validate_trace_label(component, name); + if (!status.ok()) { + return status; + } + + last_component = component; + last_name = name; + ++begin_scope_count; + ++scope_depth; + return pp::foundation::Status::success(); + } + + [[nodiscard]] pp::foundation::Status end_scope() noexcept override + { + if (scope_depth == 0U) { + return pp::foundation::Status::invalid_argument("trace scope has not begun"); + } + + ++end_scope_count; + --scope_depth; + return pp::foundation::Status::success(); } const char* last_component = nullptr; const char* last_name = nullptr; + std::uint32_t marker_count = 0; + std::uint32_t begin_scope_count = 0; + std::uint32_t end_scope_count = 0; + std::uint32_t scope_depth = 0; }; class FakeCommandContext final : public ICommandContext { @@ -1223,6 +1261,32 @@ void validates_shader_uniform_writes(pp::tests::Harness& h) PP_EXPECT(h, excessive.code == StatusCode::out_of_range); } +void validates_trace_labels(pp::tests::Harness& h) +{ + std::array oversized_label {}; + oversized_label.fill('x'); + oversized_label[max_trace_label_bytes + 1U] = '\0'; + + PP_EXPECT(h, validate_trace_label("renderer", "frame").ok()); + + const auto empty_component = validate_trace_label("", "frame"); + const auto null_component = validate_trace_label(nullptr, "frame"); + const auto empty_name = validate_trace_label("renderer", ""); + const auto oversized_component = validate_trace_label(oversized_label.data(), "frame"); + const auto oversized_name = validate_trace_label("renderer", oversized_label.data()); + + PP_EXPECT(h, !empty_component.ok()); + PP_EXPECT(h, empty_component.code == StatusCode::invalid_argument); + PP_EXPECT(h, !null_component.ok()); + PP_EXPECT(h, null_component.code == StatusCode::invalid_argument); + PP_EXPECT(h, !empty_name.ok()); + PP_EXPECT(h, empty_name.code == StatusCode::invalid_argument); + PP_EXPECT(h, !oversized_component.ok()); + PP_EXPECT(h, oversized_component.code == StatusCode::out_of_range); + PP_EXPECT(h, !oversized_name.ok()); + PP_EXPECT(h, oversized_name.code == StatusCode::out_of_range); +} + void validates_panopainter_shader_catalog(pp::tests::Harness& h) { const auto catalog = panopainter_shader_catalog(); @@ -1294,9 +1358,14 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h) FakeMesh mesh; PP_EXPECT(h, device.backend_name() == std::string_view("fake")); - device.trace()->marker("renderer", "begin"); + PP_EXPECT(h, device.trace()->begin_scope("renderer", "dispatch").ok()); + PP_EXPECT(h, device.trace()->marker("renderer", "begin").ok()); + PP_EXPECT(h, device.trace()->end_scope().ok()); PP_EXPECT(h, device.trace_recorder.last_component == std::string_view("renderer")); PP_EXPECT(h, device.trace_recorder.last_name == std::string_view("begin")); + PP_EXPECT(h, device.trace_recorder.marker_count == 1U); + PP_EXPECT(h, device.trace_recorder.begin_scope_count == 1U); + PP_EXPECT(h, device.trace_recorder.end_scope_count == 1U); auto& context = device.immediate_context(); PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc { @@ -1568,7 +1637,9 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h) RecordingMesh mesh(MeshDesc { .vertex_count = 3, .index_count = 3, .topology = PrimitiveTopology::triangles }); PP_EXPECT(h, device.backend_name() == std::string_view("recording")); - device.trace()->marker("renderer", "frame"); + PP_EXPECT(h, device.trace()->begin_scope("renderer", "recorded-frame").ok()); + PP_EXPECT(h, device.trace()->marker("renderer", "frame").ok()); + PP_EXPECT(h, device.trace()->end_scope().ok()); auto& context = device.immediate_context(); PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc { @@ -1610,57 +1681,63 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h) context.end_render_pass(); const auto commands = device.commands(); - PP_EXPECT(h, commands.size() == 12U); - PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::trace_marker); + PP_EXPECT(h, commands.size() == 14U); + PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::trace_begin_scope); PP_EXPECT(h, commands[0].component == std::string_view("renderer")); - PP_EXPECT(h, commands[0].name == std::string_view("frame")); - PP_EXPECT(h, commands[1].kind == RecordedRenderCommandKind::begin_render_pass); - PP_EXPECT(h, commands[1].target_desc.extent.width == 64U); - PP_EXPECT(h, commands[1].clear_color_enabled); - PP_EXPECT(h, commands[1].clear_color.a == 1.0F); - PP_EXPECT(h, commands[1].clear_depth_enabled); - PP_EXPECT(h, commands[1].clear_depth == 1.0F); - PP_EXPECT(h, commands[1].clear_stencil_enabled); - PP_EXPECT(h, commands[1].clear_stencil == 7U); - PP_EXPECT(h, commands[2].kind == RecordedRenderCommandKind::set_viewport); - PP_EXPECT(h, commands[2].viewport.height == 32U); - PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::set_scissor); - PP_EXPECT(h, commands[3].scissor.enabled); - PP_EXPECT(h, commands[3].scissor.x == 4); - PP_EXPECT(h, commands[3].scissor.height == 8U); - PP_EXPECT(h, recorded_render_command_kind_name(commands[3].kind) == std::string_view("set_scissor")); - PP_EXPECT(h, commands[4].kind == RecordedRenderCommandKind::set_blend_state); - PP_EXPECT(h, commands[4].blend_state.enabled); - PP_EXPECT(h, commands[4].blend_state.destination_color == BlendFactor::one_minus_source_alpha); - PP_EXPECT(h, recorded_render_command_kind_name(commands[4].kind) == std::string_view("set_blend_state")); - PP_EXPECT(h, commands[5].kind == RecordedRenderCommandKind::set_depth_state); - PP_EXPECT(h, commands[5].depth_state.test_enabled); - PP_EXPECT(h, commands[5].depth_state.write_enabled); - PP_EXPECT(h, commands[5].depth_state.compare == CompareOp::less_or_equal); - PP_EXPECT(h, recorded_render_command_kind_name(commands[5].kind) == std::string_view("set_depth_state")); - PP_EXPECT(h, commands[6].kind == RecordedRenderCommandKind::bind_shader); - PP_EXPECT(h, commands[6].name == std::string_view("recorded-shader")); - PP_EXPECT(h, commands[7].kind == RecordedRenderCommandKind::bind_texture); - PP_EXPECT(h, commands[7].texture_slot == 1U); - PP_EXPECT(h, commands[7].texture_desc.extent.height == 32U); - PP_EXPECT(h, recorded_render_command_kind_name(commands[7].kind) == std::string_view("bind_texture")); - PP_EXPECT(h, commands[8].kind == RecordedRenderCommandKind::bind_sampler); - PP_EXPECT(h, commands[8].sampler_slot == 1U); - PP_EXPECT(h, commands[8].sampler_desc.mag_filter == SamplerFilter::nearest); - PP_EXPECT(h, commands[8].sampler_desc.address_w == SamplerAddressMode::clamp_to_border); - PP_EXPECT(h, recorded_render_command_kind_name(commands[8].kind) == std::string_view("bind_sampler")); - PP_EXPECT(h, commands[9].kind == RecordedRenderCommandKind::bind_mesh); - PP_EXPECT(h, commands[9].mesh_desc.vertex_count == 3U); - PP_EXPECT(h, commands[9].mesh_desc.index_count == 3U); - PP_EXPECT(h, commands[10].kind == RecordedRenderCommandKind::draw); - PP_EXPECT(h, commands[10].mesh_desc.vertex_count == 3U); - PP_EXPECT(h, commands[10].mesh_desc.index_count == 3U); - PP_EXPECT(h, commands[10].mesh_desc.topology == PrimitiveTopology::triangles); - PP_EXPECT(h, commands[10].draw_desc.vertex_count == 3U); - PP_EXPECT(h, commands[10].draw_desc.index_count == 3U); - PP_EXPECT(h, commands[10].draw_desc.instance_count == 1U); - PP_EXPECT(h, commands[11].kind == RecordedRenderCommandKind::end_render_pass); - PP_EXPECT(h, recorded_render_command_kind_name(commands[10].kind) == std::string_view("draw")); + PP_EXPECT(h, commands[0].name == std::string_view("recorded-frame")); + PP_EXPECT(h, recorded_render_command_kind_name(commands[0].kind) == std::string_view("trace_begin_scope")); + PP_EXPECT(h, commands[1].kind == RecordedRenderCommandKind::trace_marker); + PP_EXPECT(h, commands[1].component == std::string_view("renderer")); + PP_EXPECT(h, commands[1].name == std::string_view("frame")); + PP_EXPECT(h, commands[2].kind == RecordedRenderCommandKind::trace_end_scope); + PP_EXPECT(h, recorded_render_command_kind_name(commands[2].kind) == std::string_view("trace_end_scope")); + PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::begin_render_pass); + PP_EXPECT(h, commands[3].target_desc.extent.width == 64U); + PP_EXPECT(h, commands[3].clear_color_enabled); + PP_EXPECT(h, commands[3].clear_color.a == 1.0F); + PP_EXPECT(h, commands[3].clear_depth_enabled); + PP_EXPECT(h, commands[3].clear_depth == 1.0F); + PP_EXPECT(h, commands[3].clear_stencil_enabled); + PP_EXPECT(h, commands[3].clear_stencil == 7U); + PP_EXPECT(h, commands[4].kind == RecordedRenderCommandKind::set_viewport); + PP_EXPECT(h, commands[4].viewport.height == 32U); + PP_EXPECT(h, commands[5].kind == RecordedRenderCommandKind::set_scissor); + PP_EXPECT(h, commands[5].scissor.enabled); + PP_EXPECT(h, commands[5].scissor.x == 4); + PP_EXPECT(h, commands[5].scissor.height == 8U); + PP_EXPECT(h, recorded_render_command_kind_name(commands[5].kind) == std::string_view("set_scissor")); + PP_EXPECT(h, commands[6].kind == RecordedRenderCommandKind::set_blend_state); + PP_EXPECT(h, commands[6].blend_state.enabled); + PP_EXPECT(h, commands[6].blend_state.destination_color == BlendFactor::one_minus_source_alpha); + PP_EXPECT(h, recorded_render_command_kind_name(commands[6].kind) == std::string_view("set_blend_state")); + PP_EXPECT(h, commands[7].kind == RecordedRenderCommandKind::set_depth_state); + PP_EXPECT(h, commands[7].depth_state.test_enabled); + PP_EXPECT(h, commands[7].depth_state.write_enabled); + PP_EXPECT(h, commands[7].depth_state.compare == CompareOp::less_or_equal); + PP_EXPECT(h, recorded_render_command_kind_name(commands[7].kind) == std::string_view("set_depth_state")); + PP_EXPECT(h, commands[8].kind == RecordedRenderCommandKind::bind_shader); + PP_EXPECT(h, commands[8].name == std::string_view("recorded-shader")); + PP_EXPECT(h, commands[9].kind == RecordedRenderCommandKind::bind_texture); + PP_EXPECT(h, commands[9].texture_slot == 1U); + PP_EXPECT(h, commands[9].texture_desc.extent.height == 32U); + PP_EXPECT(h, recorded_render_command_kind_name(commands[9].kind) == std::string_view("bind_texture")); + PP_EXPECT(h, commands[10].kind == RecordedRenderCommandKind::bind_sampler); + PP_EXPECT(h, commands[10].sampler_slot == 1U); + PP_EXPECT(h, commands[10].sampler_desc.mag_filter == SamplerFilter::nearest); + PP_EXPECT(h, commands[10].sampler_desc.address_w == SamplerAddressMode::clamp_to_border); + PP_EXPECT(h, recorded_render_command_kind_name(commands[10].kind) == std::string_view("bind_sampler")); + PP_EXPECT(h, commands[11].kind == RecordedRenderCommandKind::bind_mesh); + PP_EXPECT(h, commands[11].mesh_desc.vertex_count == 3U); + PP_EXPECT(h, commands[11].mesh_desc.index_count == 3U); + PP_EXPECT(h, commands[12].kind == RecordedRenderCommandKind::draw); + PP_EXPECT(h, commands[12].mesh_desc.vertex_count == 3U); + PP_EXPECT(h, commands[12].mesh_desc.index_count == 3U); + PP_EXPECT(h, commands[12].mesh_desc.topology == PrimitiveTopology::triangles); + PP_EXPECT(h, commands[12].draw_desc.vertex_count == 3U); + PP_EXPECT(h, commands[12].draw_desc.index_count == 3U); + PP_EXPECT(h, commands[12].draw_desc.instance_count == 1U); + PP_EXPECT(h, commands[13].kind == RecordedRenderCommandKind::end_render_pass); + PP_EXPECT(h, recorded_render_command_kind_name(commands[12].kind) == std::string_view("draw")); PP_EXPECT(h, context.upload_texture( texture, @@ -1668,12 +1745,12 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h) upload_bytes) .ok()); const auto commands_after_upload = device.commands(); - PP_EXPECT(h, commands_after_upload.size() == 13U); - PP_EXPECT(h, commands_after_upload[12].kind == RecordedRenderCommandKind::upload_texture); - PP_EXPECT(h, commands_after_upload[12].texture_desc.extent.width == 64U); - PP_EXPECT(h, commands_after_upload[12].readback_region.x == 4U); - PP_EXPECT(h, commands_after_upload[12].upload_bytes == 96U); - PP_EXPECT(h, recorded_render_command_kind_name(commands_after_upload[12].kind) == std::string_view("upload_texture")); + PP_EXPECT(h, commands_after_upload.size() == 15U); + PP_EXPECT(h, commands_after_upload[14].kind == RecordedRenderCommandKind::upload_texture); + PP_EXPECT(h, commands_after_upload[14].texture_desc.extent.width == 64U); + PP_EXPECT(h, commands_after_upload[14].readback_region.x == 4U); + PP_EXPECT(h, commands_after_upload[14].upload_bytes == 96U); + PP_EXPECT(h, recorded_render_command_kind_name(commands_after_upload[14].kind) == std::string_view("upload_texture")); PP_EXPECT(h, context.copy_texture( texture, @@ -1682,13 +1759,13 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h) ReadbackRegion { .x = 0, .y = 0, .width = 8, .height = 3 }) .ok()); const auto commands_after_copy = device.commands(); - PP_EXPECT(h, commands_after_copy.size() == 14U); - PP_EXPECT(h, commands_after_copy[13].kind == RecordedRenderCommandKind::copy_texture); - PP_EXPECT(h, commands_after_copy[13].source_region.x == 4U); - PP_EXPECT(h, commands_after_copy[13].destination_region.x == 0U); - PP_EXPECT(h, commands_after_copy[13].copy_source_bytes == 96U); - PP_EXPECT(h, commands_after_copy[13].copy_destination_bytes == 96U); - PP_EXPECT(h, recorded_render_command_kind_name(commands_after_copy[13].kind) == std::string_view("copy_texture")); + PP_EXPECT(h, commands_after_copy.size() == 16U); + PP_EXPECT(h, commands_after_copy[15].kind == RecordedRenderCommandKind::copy_texture); + PP_EXPECT(h, commands_after_copy[15].source_region.x == 4U); + PP_EXPECT(h, commands_after_copy[15].destination_region.x == 0U); + PP_EXPECT(h, commands_after_copy[15].copy_source_bytes == 96U); + PP_EXPECT(h, commands_after_copy[15].copy_destination_bytes == 96U); + PP_EXPECT(h, recorded_render_command_kind_name(commands_after_copy[15].kind) == std::string_view("copy_texture")); PP_EXPECT(h, context.read_texture( texture, @@ -1696,22 +1773,22 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h) readback_buffer) .ok()); const auto commands_after_readback = device.commands(); - PP_EXPECT(h, commands_after_readback.size() == 15U); - PP_EXPECT(h, commands_after_readback[14].kind == RecordedRenderCommandKind::read_texture); - PP_EXPECT(h, commands_after_readback[14].texture_desc.extent.width == 64U); - PP_EXPECT(h, commands_after_readback[14].readback_region.x == 4U); - PP_EXPECT(h, commands_after_readback[14].readback_region.height == 3U); - PP_EXPECT(h, commands_after_readback[14].readback_bytes == 96U); - PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[14].kind) == std::string_view("read_texture")); + PP_EXPECT(h, commands_after_readback.size() == 17U); + PP_EXPECT(h, commands_after_readback[16].kind == RecordedRenderCommandKind::read_texture); + PP_EXPECT(h, commands_after_readback[16].texture_desc.extent.width == 64U); + PP_EXPECT(h, commands_after_readback[16].readback_region.x == 4U); + PP_EXPECT(h, commands_after_readback[16].readback_region.height == 3U); + PP_EXPECT(h, commands_after_readback[16].readback_bytes == 96U); + PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[16].kind) == std::string_view("read_texture")); PP_EXPECT(h, context.capture_frame(target, readback_buffer).ok()); const auto commands_after_capture = device.commands(); - PP_EXPECT(h, commands_after_capture.size() == 16U); - PP_EXPECT(h, commands_after_capture[15].kind == RecordedRenderCommandKind::capture_frame); - PP_EXPECT(h, commands_after_capture[15].target_desc.extent.width == 64U); - PP_EXPECT(h, commands_after_capture[15].target_desc.extent.height == 32U); - PP_EXPECT(h, commands_after_capture[15].capture_bytes == 8192U); - PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[15].kind) == std::string_view("capture_frame")); + PP_EXPECT(h, commands_after_capture.size() == 18U); + PP_EXPECT(h, commands_after_capture[17].kind == RecordedRenderCommandKind::capture_frame); + PP_EXPECT(h, commands_after_capture[17].target_desc.extent.width == 64U); + PP_EXPECT(h, commands_after_capture[17].target_desc.extent.height == 32U); + PP_EXPECT(h, commands_after_capture[17].capture_bytes == 8192U); + PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[17].kind) == std::string_view("capture_frame")); PP_EXPECT(h, context.blit_render_target( target, @@ -1721,16 +1798,16 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h) BlitFilter::linear) .ok()); const auto commands_after_blit = device.commands(); - PP_EXPECT(h, commands_after_blit.size() == 17U); - PP_EXPECT(h, commands_after_blit[16].kind == RecordedRenderCommandKind::blit_render_target); - PP_EXPECT(h, commands_after_blit[16].source_desc.extent.width == 64U); - PP_EXPECT(h, commands_after_blit[16].destination_desc.extent.height == 32U); - PP_EXPECT(h, commands_after_blit[16].source_region.width == 16U); - PP_EXPECT(h, commands_after_blit[16].destination_region.x == 2U); - PP_EXPECT(h, commands_after_blit[16].blit_filter == BlitFilter::linear); - PP_EXPECT(h, commands_after_blit[16].blit_source_bytes == 512U); - PP_EXPECT(h, commands_after_blit[16].blit_destination_bytes == 128U); - PP_EXPECT(h, recorded_render_command_kind_name(commands_after_blit[16].kind) == std::string_view("blit_render_target")); + PP_EXPECT(h, commands_after_blit.size() == 19U); + PP_EXPECT(h, commands_after_blit[18].kind == RecordedRenderCommandKind::blit_render_target); + PP_EXPECT(h, commands_after_blit[18].source_desc.extent.width == 64U); + PP_EXPECT(h, commands_after_blit[18].destination_desc.extent.height == 32U); + PP_EXPECT(h, commands_after_blit[18].source_region.width == 16U); + PP_EXPECT(h, commands_after_blit[18].destination_region.x == 2U); + PP_EXPECT(h, commands_after_blit[18].blit_filter == BlitFilter::linear); + PP_EXPECT(h, commands_after_blit[18].blit_source_bytes == 512U); + PP_EXPECT(h, commands_after_blit[18].blit_destination_bytes == 128U); + PP_EXPECT(h, recorded_render_command_kind_name(commands_after_blit[18].kind) == std::string_view("blit_render_target")); device.clear(); PP_EXPECT(h, device.commands().empty()); @@ -1802,6 +1879,23 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har RecordingMesh mesh(MeshDesc { .vertex_count = 3, .topology = PrimitiveTopology::triangles }); RecordingMesh empty_mesh(MeshDesc {}); + RecordingRenderDevice trace_device; + auto* trace = trace_device.trace(); + const auto trace_marker_empty_component = trace->marker("", "frame"); + PP_EXPECT(h, !trace_marker_empty_component.ok()); + PP_EXPECT(h, trace_marker_empty_component.code == StatusCode::invalid_argument); + + const auto trace_scope_empty_name = trace->begin_scope("renderer", ""); + PP_EXPECT(h, !trace_scope_empty_name.ok()); + PP_EXPECT(h, trace_scope_empty_name.code == StatusCode::invalid_argument); + + const auto trace_end_without_begin = trace->end_scope(); + PP_EXPECT(h, !trace_end_without_begin.ok()); + PP_EXPECT(h, trace_end_without_begin.code == StatusCode::invalid_argument); + + PP_EXPECT(h, trace->begin_scope("renderer", "strict").ok()); + PP_EXPECT(h, trace->end_scope().ok()); + auto& context = device.immediate_context(); const auto draw_before_begin = context.draw(DrawDesc { .vertex_count = 3 }); PP_EXPECT(h, !draw_before_begin.ok()); @@ -2146,6 +2240,7 @@ int main() harness.run("validates_render_pass_descriptors", validates_render_pass_descriptors); harness.run("validates_shader_program_descriptors", validates_shader_program_descriptors); harness.run("validates_shader_uniform_writes", validates_shader_uniform_writes); + harness.run("validates_trace_labels", validates_trace_labels); harness.run("validates_panopainter_shader_catalog", validates_panopainter_shader_catalog); harness.run("rejects_invalid_shader_catalogs", rejects_invalid_shader_catalogs); harness.run("renderer_interfaces_support_backend_neutral_dispatch", renderer_interfaces_support_backend_neutral_dispatch); diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 67c86fb..9cbb5dc 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -2279,7 +2279,23 @@ int record_render(int argc, char** argv) } constexpr std::size_t created_resources = 6; - device.trace()->marker("renderer", "pano_cli_record_render"); + auto* trace = device.trace(); + const auto trace_begin_status = trace->begin_scope("renderer", "pano_cli_record_render"); + if (!trace_begin_status.ok()) { + print_error("record-render", trace_begin_status.message); + return 2; + } + const auto trace_marker_status = trace->marker("renderer", "frame"); + if (!trace_marker_status.ok()) { + print_error("record-render", trace_marker_status.message); + return 2; + } + const auto trace_end_status = trace->end_scope(); + if (!trace_end_status.ok()) { + print_error("record-render", trace_end_status.message); + return 2; + } + auto& context = device.immediate_context(); const auto upload_status = context.upload_texture( *texture.value(), @@ -2463,6 +2479,8 @@ int record_render(int argc, char** argv) std::size_t capture_commands = 0; std::size_t blit_commands = 0; std::size_t trace_markers = 0; + std::size_t trace_begin_scopes = 0; + std::size_t trace_end_scopes = 0; std::size_t render_passes = 0; std::size_t depth_clears = 0; std::size_t stencil_clears = 0; @@ -2527,6 +2545,10 @@ int record_render(int argc, char** argv) blit_destination_bytes += command.blit_destination_bytes; } else if (command.kind == pp::renderer::RecordedRenderCommandKind::trace_marker) { ++trace_markers; + } else if (command.kind == pp::renderer::RecordedRenderCommandKind::trace_begin_scope) { + ++trace_begin_scopes; + } else if (command.kind == pp::renderer::RecordedRenderCommandKind::trace_end_scope) { + ++trace_end_scopes; } } @@ -2564,6 +2586,8 @@ int record_render(int argc, char** argv) << ",\"blitSourceBytes\":" << blit_source_bytes << ",\"blitDestinationBytes\":" << blit_destination_bytes << ",\"traceMarkers\":" << trace_markers + << ",\"traceBeginScopes\":" << trace_begin_scopes + << ",\"traceEndScopes\":" << trace_end_scopes << ",\"first\":\"" << pp::renderer::recorded_render_command_kind_name(commands.front().kind) << "\",\"last\":\""