Add renderer trace scope contract

This commit is contained in:
2026-06-02 16:55:23 +02:00
parent 07293c0590
commit bbe3db1747
9 changed files with 305 additions and 101 deletions

View File

@@ -295,12 +295,13 @@ Known local toolchain state:
render-pass-clear/scissor/depth/blend/shader-uniform/texture-bind/ render-pass-clear/scissor/depth/blend/shader-uniform/texture-bind/
sampler-bind/draw/upload/mipmap-generation/texture-copy/readback/ sampler-bind/draw/upload/mipmap-generation/texture-copy/readback/
frame-capture/blit commands, draw mesh inputs, explicit draw ranges, and 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 - `pano_cli record-render` exposes the recording renderer through JSON
automation, including render-pass/depth-clear counts, scissor/depth/blend/ automation, including render-pass/depth-clear counts, scissor/depth/blend/
shader-uniform/texture-bind/sampler-bind/upload/texture-copy/readback/ shader-uniform/texture-bind/sampler-bind/upload/texture-copy/readback/
frame-capture/blit command and byte totals, backend resource creation counts, frame-capture/blit command and byte totals, trace marker/scope counts,
plus draw descriptor vertex/index totals, and is covered by backend resource creation counts, plus draw descriptor vertex/index totals,
and is covered by
`pano_cli_record_render_smoke` plus `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`

View File

@@ -421,7 +421,7 @@ texture mip-level validation, texture-upload/readback command validation,
mipmap-generation command validation, frame-capture byte-size helpers, mipmap-generation command validation, frame-capture byte-size helpers,
frame-capture command validation, render-target blit validation, texture-slot frame-capture command validation, render-target blit validation, texture-slot
binding validation, blend-state validation, scissor-state validation, 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 and the canonical PanoPainter shader catalog now consumed by the legacy OpenGL
app initialization path. 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
@@ -725,7 +725,8 @@ Results:
PanoPainter shader catalog validation, explicit texture usage validation, PanoPainter shader catalog validation, explicit texture usage validation,
texture mip-level validation, readback byte-size and command-order texture mip-level validation, readback byte-size and command-order
validation, texture-upload byte-count validation, mipmap-generation command 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 render-target blit validation, texture-slot binding validation, blend-state
validation, scissor-state validation, render-pass color/depth/stencil clear validation, scissor-state validation, render-pass color/depth/stencil clear
validation, shader-uniform write validation, draw descriptor/range validation, shader-uniform write validation, draw descriptor/range
@@ -834,7 +835,7 @@ Results:
mipmap-generation/readback/frame-capture/blit validation plus explicit draw mipmap-generation/readback/frame-capture/blit validation plus explicit draw
descriptor and texture-copy validation; it creates validated textures, descriptor and texture-copy validation; it creates validated textures,
render targets, shaders, meshes, and readback buffers, then records commands, 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, color/depth/stencil clear intent, scissor state, depth state, blend state,
shader uniform writes, texture/sampler binds, draw mesh inputs, explicit draw shader uniform writes, texture/sampler binds, draw mesh inputs, explicit draw
ranges, texture uploads/mipmap generations/copies/readbacks, frame captures, ranges, texture uploads/mipmap generations/copies/readbacks, frame captures,
@@ -842,7 +843,7 @@ Results:
does not require a window or GL context. 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, resource creation counts, target dimensions, backend 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 descriptor vertex/index totals, scissor/depth/blend-state plus
shader-uniform/texture/sampler-bind/upload/texture-copy/readback/ shader-uniform/texture/sampler-bind/upload/texture-copy/readback/
frame-capture/blit command/byte totals for agent automation, with an frame-capture/blit command/byte totals for agent automation, with an

View File

@@ -575,13 +575,48 @@ RecordingRenderTrace::RecordingRenderTrace(std::vector<RecordedRenderCommand>& 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 { push_command(commands_, RecordedRenderCommand {
.kind = RecordedRenderCommandKind::trace_marker, .kind = RecordedRenderCommandKind::trace_marker,
.component = non_null_name(component), .component = non_null_name(component),
.name = non_null_name(name), .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 RecordingRenderDevice::RecordingRenderDevice() noexcept
@@ -731,6 +766,10 @@ const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) no
return "end_render_pass"; return "end_render_pass";
case RecordedRenderCommandKind::trace_marker: case RecordedRenderCommandKind::trace_marker:
return "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"; return "unknown";

View File

@@ -27,6 +27,8 @@ enum class RecordedRenderCommandKind : std::uint8_t {
blit_render_target, blit_render_target,
end_render_pass, end_render_pass,
trace_marker, trace_marker,
trace_begin_scope,
trace_end_scope,
}; };
struct RecordedRenderCommand { struct RecordedRenderCommand {
@@ -176,10 +178,13 @@ private:
class RecordingRenderTrace final : public IRenderTrace { class RecordingRenderTrace final : public IRenderTrace {
public: public:
explicit RecordingRenderTrace(std::vector<RecordedRenderCommand>& commands) noexcept; explicit RecordingRenderTrace(std::vector<RecordedRenderCommand>& 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: private:
std::vector<RecordedRenderCommand>* commands_ = nullptr; std::vector<RecordedRenderCommand>* commands_ = nullptr;
std::uint32_t scope_depth_ = 0;
}; };
class RecordingRenderDevice final : public IRenderDevice { class RecordingRenderDevice final : public IRenderDevice {

View File

@@ -12,6 +12,20 @@ namespace {
return text == nullptr || text[0] == '\0'; 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( [[nodiscard]] pp::foundation::Status validate_shader_stage_source(
ShaderStageSource source, ShaderStageSource source,
const char* stage_name) noexcept const char* stage_name) noexcept
@@ -506,6 +520,27 @@ pp::foundation::Status validate_shader_uniform_write(
return pp::foundation::Status::success(); 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 pp::foundation::Status validate_readback_region(TextureDesc desc, ReadbackRegion region) noexcept
{ {
const auto extent_status = validate_extent(desc.extent); const auto extent_status = validate_extent(desc.extent);

View File

@@ -16,6 +16,7 @@ constexpr std::uint32_t max_texture_slots = 32;
constexpr std::uint64_t max_texture_bytes = 1024ULL * 1024ULL * 1024ULL; 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_source_bytes = 4ULL * 1024ULL * 1024ULL;
constexpr std::size_t max_shader_uniform_bytes = 64ULL * 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 { enum class TextureFormat : std::uint8_t {
rgba8, rgba8,
@@ -244,7 +245,9 @@ public:
class IRenderTrace { class IRenderTrace {
public: public:
virtual ~IRenderTrace() = default; 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 { class ICommandContext {
@@ -338,6 +341,7 @@ public:
[[nodiscard]] pp::foundation::Status validate_shader_uniform_write( [[nodiscard]] pp::foundation::Status validate_shader_uniform_write(
const char* name, const char* name,
std::span<const std::byte> bytes) noexcept; std::span<const std::byte> bytes) noexcept;
[[nodiscard]] pp::foundation::Status validate_trace_label(const char* component, const char* name) noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> texture_byte_size(TextureDesc desc) noexcept; [[nodiscard]] pp::foundation::Result<std::uint64_t> texture_byte_size(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> readback_byte_size( [[nodiscard]] pp::foundation::Result<std::uint64_t> readback_byte_size(
TextureDesc desc, TextureDesc desc,

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.*\"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 add_test(NAME pano_cli_record_render_rejects_oversized_target
COMMAND "${CMAKE_COMMAND}" COMMAND "${CMAKE_COMMAND}"

View File

@@ -61,6 +61,7 @@ using pp::renderer::max_shader_uniform_bytes;
using pp::renderer::max_mip_levels_for_extent; using pp::renderer::max_mip_levels_for_extent;
using pp::renderer::max_texture_dimension; using pp::renderer::max_texture_dimension;
using pp::renderer::max_texture_slots; using pp::renderer::max_texture_slots;
using pp::renderer::max_trace_label_bytes;
using pp::renderer::panopainter_shader_catalog; using pp::renderer::panopainter_shader_catalog;
using pp::renderer::primitive_topology_name; using pp::renderer::primitive_topology_name;
using pp::renderer::readback_byte_size; 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_desc;
using pp::renderer::validate_texture_slot; using pp::renderer::validate_texture_slot;
using pp::renderer::validate_texture_usage; using pp::renderer::validate_texture_usage;
using pp::renderer::validate_trace_label;
using pp::renderer::validate_viewport; using pp::renderer::validate_viewport;
namespace { namespace {
@@ -190,14 +192,50 @@ private:
class FakeTrace final : public IRenderTrace { class FakeTrace final : public IRenderTrace {
public: 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_component = component;
last_name = name; 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_component = nullptr;
const char* last_name = 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 { 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); PP_EXPECT(h, excessive.code == StatusCode::out_of_range);
} }
void validates_trace_labels(pp::tests::Harness& h)
{
std::array<char, max_trace_label_bytes + 2U> 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) void validates_panopainter_shader_catalog(pp::tests::Harness& h)
{ {
const auto catalog = panopainter_shader_catalog(); const auto catalog = panopainter_shader_catalog();
@@ -1294,9 +1358,14 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
FakeMesh mesh; FakeMesh mesh;
PP_EXPECT(h, device.backend_name() == std::string_view("fake")); 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_component == std::string_view("renderer"));
PP_EXPECT(h, device.trace_recorder.last_name == std::string_view("begin")); 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(); auto& context = device.immediate_context();
PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc { 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 }); RecordingMesh mesh(MeshDesc { .vertex_count = 3, .index_count = 3, .topology = PrimitiveTopology::triangles });
PP_EXPECT(h, device.backend_name() == std::string_view("recording")); 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(); auto& context = device.immediate_context();
PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc { 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(); context.end_render_pass();
const auto commands = device.commands(); const auto commands = device.commands();
PP_EXPECT(h, commands.size() == 12U); PP_EXPECT(h, commands.size() == 14U);
PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::trace_marker); 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].component == std::string_view("renderer"));
PP_EXPECT(h, commands[0].name == std::string_view("frame")); PP_EXPECT(h, commands[0].name == std::string_view("recorded-frame"));
PP_EXPECT(h, commands[1].kind == RecordedRenderCommandKind::begin_render_pass); PP_EXPECT(h, recorded_render_command_kind_name(commands[0].kind) == std::string_view("trace_begin_scope"));
PP_EXPECT(h, commands[1].target_desc.extent.width == 64U); PP_EXPECT(h, commands[1].kind == RecordedRenderCommandKind::trace_marker);
PP_EXPECT(h, commands[1].clear_color_enabled); PP_EXPECT(h, commands[1].component == std::string_view("renderer"));
PP_EXPECT(h, commands[1].clear_color.a == 1.0F); PP_EXPECT(h, commands[1].name == std::string_view("frame"));
PP_EXPECT(h, commands[1].clear_depth_enabled); PP_EXPECT(h, commands[2].kind == RecordedRenderCommandKind::trace_end_scope);
PP_EXPECT(h, commands[1].clear_depth == 1.0F); PP_EXPECT(h, recorded_render_command_kind_name(commands[2].kind) == std::string_view("trace_end_scope"));
PP_EXPECT(h, commands[1].clear_stencil_enabled); PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::begin_render_pass);
PP_EXPECT(h, commands[1].clear_stencil == 7U); PP_EXPECT(h, commands[3].target_desc.extent.width == 64U);
PP_EXPECT(h, commands[2].kind == RecordedRenderCommandKind::set_viewport); PP_EXPECT(h, commands[3].clear_color_enabled);
PP_EXPECT(h, commands[2].viewport.height == 32U); PP_EXPECT(h, commands[3].clear_color.a == 1.0F);
PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::set_scissor); PP_EXPECT(h, commands[3].clear_depth_enabled);
PP_EXPECT(h, commands[3].scissor.enabled); PP_EXPECT(h, commands[3].clear_depth == 1.0F);
PP_EXPECT(h, commands[3].scissor.x == 4); PP_EXPECT(h, commands[3].clear_stencil_enabled);
PP_EXPECT(h, commands[3].scissor.height == 8U); PP_EXPECT(h, commands[3].clear_stencil == 7U);
PP_EXPECT(h, recorded_render_command_kind_name(commands[3].kind) == std::string_view("set_scissor")); PP_EXPECT(h, commands[4].kind == RecordedRenderCommandKind::set_viewport);
PP_EXPECT(h, commands[4].kind == RecordedRenderCommandKind::set_blend_state); PP_EXPECT(h, commands[4].viewport.height == 32U);
PP_EXPECT(h, commands[4].blend_state.enabled); PP_EXPECT(h, commands[5].kind == RecordedRenderCommandKind::set_scissor);
PP_EXPECT(h, commands[4].blend_state.destination_color == BlendFactor::one_minus_source_alpha); PP_EXPECT(h, commands[5].scissor.enabled);
PP_EXPECT(h, recorded_render_command_kind_name(commands[4].kind) == std::string_view("set_blend_state")); PP_EXPECT(h, commands[5].scissor.x == 4);
PP_EXPECT(h, commands[5].kind == RecordedRenderCommandKind::set_depth_state); PP_EXPECT(h, commands[5].scissor.height == 8U);
PP_EXPECT(h, commands[5].depth_state.test_enabled); PP_EXPECT(h, recorded_render_command_kind_name(commands[5].kind) == std::string_view("set_scissor"));
PP_EXPECT(h, commands[5].depth_state.write_enabled); PP_EXPECT(h, commands[6].kind == RecordedRenderCommandKind::set_blend_state);
PP_EXPECT(h, commands[5].depth_state.compare == CompareOp::less_or_equal); PP_EXPECT(h, commands[6].blend_state.enabled);
PP_EXPECT(h, recorded_render_command_kind_name(commands[5].kind) == std::string_view("set_depth_state")); PP_EXPECT(h, commands[6].blend_state.destination_color == BlendFactor::one_minus_source_alpha);
PP_EXPECT(h, commands[6].kind == RecordedRenderCommandKind::bind_shader); PP_EXPECT(h, recorded_render_command_kind_name(commands[6].kind) == std::string_view("set_blend_state"));
PP_EXPECT(h, commands[6].name == std::string_view("recorded-shader")); PP_EXPECT(h, commands[7].kind == RecordedRenderCommandKind::set_depth_state);
PP_EXPECT(h, commands[7].kind == RecordedRenderCommandKind::bind_texture); PP_EXPECT(h, commands[7].depth_state.test_enabled);
PP_EXPECT(h, commands[7].texture_slot == 1U); PP_EXPECT(h, commands[7].depth_state.write_enabled);
PP_EXPECT(h, commands[7].texture_desc.extent.height == 32U); 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("bind_texture")); 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_sampler); PP_EXPECT(h, commands[8].kind == RecordedRenderCommandKind::bind_shader);
PP_EXPECT(h, commands[8].sampler_slot == 1U); PP_EXPECT(h, commands[8].name == std::string_view("recorded-shader"));
PP_EXPECT(h, commands[8].sampler_desc.mag_filter == SamplerFilter::nearest); PP_EXPECT(h, commands[9].kind == RecordedRenderCommandKind::bind_texture);
PP_EXPECT(h, commands[8].sampler_desc.address_w == SamplerAddressMode::clamp_to_border); PP_EXPECT(h, commands[9].texture_slot == 1U);
PP_EXPECT(h, recorded_render_command_kind_name(commands[8].kind) == std::string_view("bind_sampler")); PP_EXPECT(h, commands[9].texture_desc.extent.height == 32U);
PP_EXPECT(h, commands[9].kind == RecordedRenderCommandKind::bind_mesh); PP_EXPECT(h, recorded_render_command_kind_name(commands[9].kind) == std::string_view("bind_texture"));
PP_EXPECT(h, commands[9].mesh_desc.vertex_count == 3U); PP_EXPECT(h, commands[10].kind == RecordedRenderCommandKind::bind_sampler);
PP_EXPECT(h, commands[9].mesh_desc.index_count == 3U); PP_EXPECT(h, commands[10].sampler_slot == 1U);
PP_EXPECT(h, commands[10].kind == RecordedRenderCommandKind::draw); PP_EXPECT(h, commands[10].sampler_desc.mag_filter == SamplerFilter::nearest);
PP_EXPECT(h, commands[10].mesh_desc.vertex_count == 3U); PP_EXPECT(h, commands[10].sampler_desc.address_w == SamplerAddressMode::clamp_to_border);
PP_EXPECT(h, commands[10].mesh_desc.index_count == 3U); PP_EXPECT(h, recorded_render_command_kind_name(commands[10].kind) == std::string_view("bind_sampler"));
PP_EXPECT(h, commands[10].mesh_desc.topology == PrimitiveTopology::triangles); PP_EXPECT(h, commands[11].kind == RecordedRenderCommandKind::bind_mesh);
PP_EXPECT(h, commands[10].draw_desc.vertex_count == 3U); PP_EXPECT(h, commands[11].mesh_desc.vertex_count == 3U);
PP_EXPECT(h, commands[10].draw_desc.index_count == 3U); PP_EXPECT(h, commands[11].mesh_desc.index_count == 3U);
PP_EXPECT(h, commands[10].draw_desc.instance_count == 1U); PP_EXPECT(h, commands[12].kind == RecordedRenderCommandKind::draw);
PP_EXPECT(h, commands[11].kind == RecordedRenderCommandKind::end_render_pass); PP_EXPECT(h, commands[12].mesh_desc.vertex_count == 3U);
PP_EXPECT(h, recorded_render_command_kind_name(commands[10].kind) == std::string_view("draw")); 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( PP_EXPECT(h, context.upload_texture(
texture, texture,
@@ -1668,12 +1745,12 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
upload_bytes) upload_bytes)
.ok()); .ok());
const auto commands_after_upload = device.commands(); const auto commands_after_upload = device.commands();
PP_EXPECT(h, commands_after_upload.size() == 13U); PP_EXPECT(h, commands_after_upload.size() == 15U);
PP_EXPECT(h, commands_after_upload[12].kind == RecordedRenderCommandKind::upload_texture); PP_EXPECT(h, commands_after_upload[14].kind == RecordedRenderCommandKind::upload_texture);
PP_EXPECT(h, commands_after_upload[12].texture_desc.extent.width == 64U); PP_EXPECT(h, commands_after_upload[14].texture_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_upload[12].readback_region.x == 4U); PP_EXPECT(h, commands_after_upload[14].readback_region.x == 4U);
PP_EXPECT(h, commands_after_upload[12].upload_bytes == 96U); PP_EXPECT(h, commands_after_upload[14].upload_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_upload[12].kind) == std::string_view("upload_texture")); PP_EXPECT(h, recorded_render_command_kind_name(commands_after_upload[14].kind) == std::string_view("upload_texture"));
PP_EXPECT(h, context.copy_texture( PP_EXPECT(h, context.copy_texture(
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 }) ReadbackRegion { .x = 0, .y = 0, .width = 8, .height = 3 })
.ok()); .ok());
const auto commands_after_copy = device.commands(); const auto commands_after_copy = device.commands();
PP_EXPECT(h, commands_after_copy.size() == 14U); PP_EXPECT(h, commands_after_copy.size() == 16U);
PP_EXPECT(h, commands_after_copy[13].kind == RecordedRenderCommandKind::copy_texture); PP_EXPECT(h, commands_after_copy[15].kind == RecordedRenderCommandKind::copy_texture);
PP_EXPECT(h, commands_after_copy[13].source_region.x == 4U); PP_EXPECT(h, commands_after_copy[15].source_region.x == 4U);
PP_EXPECT(h, commands_after_copy[13].destination_region.x == 0U); PP_EXPECT(h, commands_after_copy[15].destination_region.x == 0U);
PP_EXPECT(h, commands_after_copy[13].copy_source_bytes == 96U); PP_EXPECT(h, commands_after_copy[15].copy_source_bytes == 96U);
PP_EXPECT(h, commands_after_copy[13].copy_destination_bytes == 96U); PP_EXPECT(h, commands_after_copy[15].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, recorded_render_command_kind_name(commands_after_copy[15].kind) == std::string_view("copy_texture"));
PP_EXPECT(h, context.read_texture( PP_EXPECT(h, context.read_texture(
texture, texture,
@@ -1696,22 +1773,22 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
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() == 15U); PP_EXPECT(h, commands_after_readback.size() == 17U);
PP_EXPECT(h, commands_after_readback[14].kind == RecordedRenderCommandKind::read_texture); PP_EXPECT(h, commands_after_readback[16].kind == RecordedRenderCommandKind::read_texture);
PP_EXPECT(h, commands_after_readback[14].texture_desc.extent.width == 64U); PP_EXPECT(h, commands_after_readback[16].texture_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_readback[14].readback_region.x == 4U); PP_EXPECT(h, commands_after_readback[16].readback_region.x == 4U);
PP_EXPECT(h, commands_after_readback[14].readback_region.height == 3U); PP_EXPECT(h, commands_after_readback[16].readback_region.height == 3U);
PP_EXPECT(h, commands_after_readback[14].readback_bytes == 96U); PP_EXPECT(h, commands_after_readback[16].readback_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[14].kind) == std::string_view("read_texture")); 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()); 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() == 16U); PP_EXPECT(h, commands_after_capture.size() == 18U);
PP_EXPECT(h, commands_after_capture[15].kind == RecordedRenderCommandKind::capture_frame); PP_EXPECT(h, commands_after_capture[17].kind == RecordedRenderCommandKind::capture_frame);
PP_EXPECT(h, commands_after_capture[15].target_desc.extent.width == 64U); PP_EXPECT(h, commands_after_capture[17].target_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_capture[15].target_desc.extent.height == 32U); PP_EXPECT(h, commands_after_capture[17].target_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_capture[15].capture_bytes == 8192U); PP_EXPECT(h, commands_after_capture[17].capture_bytes == 8192U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[15].kind) == std::string_view("capture_frame")); 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( PP_EXPECT(h, context.blit_render_target(
target, target,
@@ -1721,16 +1798,16 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
BlitFilter::linear) BlitFilter::linear)
.ok()); .ok());
const auto commands_after_blit = device.commands(); const auto commands_after_blit = device.commands();
PP_EXPECT(h, commands_after_blit.size() == 17U); PP_EXPECT(h, commands_after_blit.size() == 19U);
PP_EXPECT(h, commands_after_blit[16].kind == RecordedRenderCommandKind::blit_render_target); PP_EXPECT(h, commands_after_blit[18].kind == RecordedRenderCommandKind::blit_render_target);
PP_EXPECT(h, commands_after_blit[16].source_desc.extent.width == 64U); PP_EXPECT(h, commands_after_blit[18].source_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_blit[16].destination_desc.extent.height == 32U); PP_EXPECT(h, commands_after_blit[18].destination_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_blit[16].source_region.width == 16U); PP_EXPECT(h, commands_after_blit[18].source_region.width == 16U);
PP_EXPECT(h, commands_after_blit[16].destination_region.x == 2U); PP_EXPECT(h, commands_after_blit[18].destination_region.x == 2U);
PP_EXPECT(h, commands_after_blit[16].blit_filter == BlitFilter::linear); PP_EXPECT(h, commands_after_blit[18].blit_filter == BlitFilter::linear);
PP_EXPECT(h, commands_after_blit[16].blit_source_bytes == 512U); PP_EXPECT(h, commands_after_blit[18].blit_source_bytes == 512U);
PP_EXPECT(h, commands_after_blit[16].blit_destination_bytes == 128U); PP_EXPECT(h, commands_after_blit[18].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, recorded_render_command_kind_name(commands_after_blit[18].kind) == std::string_view("blit_render_target"));
device.clear(); device.clear();
PP_EXPECT(h, device.commands().empty()); 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 mesh(MeshDesc { .vertex_count = 3, .topology = PrimitiveTopology::triangles });
RecordingMesh empty_mesh(MeshDesc {}); 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(); auto& context = device.immediate_context();
const auto draw_before_begin = context.draw(DrawDesc { .vertex_count = 3 }); const auto draw_before_begin = context.draw(DrawDesc { .vertex_count = 3 });
PP_EXPECT(h, !draw_before_begin.ok()); 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_render_pass_descriptors", validates_render_pass_descriptors);
harness.run("validates_shader_program_descriptors", validates_shader_program_descriptors); harness.run("validates_shader_program_descriptors", validates_shader_program_descriptors);
harness.run("validates_shader_uniform_writes", validates_shader_uniform_writes); 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("validates_panopainter_shader_catalog", validates_panopainter_shader_catalog);
harness.run("rejects_invalid_shader_catalogs", rejects_invalid_shader_catalogs); harness.run("rejects_invalid_shader_catalogs", rejects_invalid_shader_catalogs);
harness.run("renderer_interfaces_support_backend_neutral_dispatch", renderer_interfaces_support_backend_neutral_dispatch); harness.run("renderer_interfaces_support_backend_neutral_dispatch", renderer_interfaces_support_backend_neutral_dispatch);

View File

@@ -2279,7 +2279,23 @@ int record_render(int argc, char** argv)
} }
constexpr std::size_t created_resources = 6; 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(); auto& context = device.immediate_context();
const auto upload_status = context.upload_texture( const auto upload_status = context.upload_texture(
*texture.value(), *texture.value(),
@@ -2463,6 +2479,8 @@ int record_render(int argc, char** argv)
std::size_t capture_commands = 0; std::size_t capture_commands = 0;
std::size_t blit_commands = 0; std::size_t blit_commands = 0;
std::size_t trace_markers = 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 render_passes = 0;
std::size_t depth_clears = 0; std::size_t depth_clears = 0;
std::size_t stencil_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; blit_destination_bytes += command.blit_destination_bytes;
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::trace_marker) { } else if (command.kind == pp::renderer::RecordedRenderCommandKind::trace_marker) {
++trace_markers; ++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 << ",\"blitSourceBytes\":" << blit_source_bytes
<< ",\"blitDestinationBytes\":" << blit_destination_bytes << ",\"blitDestinationBytes\":" << blit_destination_bytes
<< ",\"traceMarkers\":" << trace_markers << ",\"traceMarkers\":" << trace_markers
<< ",\"traceBeginScopes\":" << trace_begin_scopes
<< ",\"traceEndScopes\":" << trace_end_scopes
<< ",\"first\":\"" << ",\"first\":\""
<< pp::renderer::recorded_render_command_kind_name(commands.front().kind) << pp::renderer::recorded_render_command_kind_name(commands.front().kind)
<< "\",\"last\":\"" << "\",\"last\":\""