Reset recording renderer state on clear

This commit is contained in:
2026-06-02 17:53:17 +02:00
parent 9b00acec6f
commit 860e5ad31e
5 changed files with 70 additions and 4 deletions

View File

@@ -308,7 +308,9 @@ 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-transition/texture-copy/readback/ sampler-bind/draw/upload/mipmap-generation/texture-transition/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 and scopes without a window or GL context. records trace markers and scopes without a window or GL context. Recorder
`clear()` also resets active render-pass and trace-scope state so automation
can reuse the same recording device after an interrupted frame.
- `pano_cli record-render` exposes the recording renderer through JSON - `pano_cli record-render` exposes the recording renderer through JSON
automation, including backend feature flags, render-pass/depth-clear counts, scissor/depth/blend/ automation, including backend feature flags, render-pass/depth-clear counts, scissor/depth/blend/
shader-uniform/texture-bind/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/ shader-uniform/texture-bind/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/

View File

@@ -431,8 +431,8 @@ mipmap-generation command validation, texture-state transition validation, frame
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 marker/scope 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 recording-device reuse/reset validation, and the canonical PanoPainter shader
app initialization path. catalog now consumed 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
@@ -851,7 +851,9 @@ Results:
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/state transitions/copies/readbacks, frame captures, ranges, texture uploads/mipmap generations/state transitions/copies/readbacks, frame captures,
and render-target blits, giving automation a backend-neutral render path that and render-target blits, giving automation a backend-neutral render path that
does not require a window or GL context. does not require a window or GL context. Clearing the recording device now
resets active render-pass and trace-scope state so interrupted automation can
reuse a recorder without carrying stale frame state forward.
- `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, backend feature flags, resource creation counts, target dimensions, backend JSON command counts, backend feature flags, resource creation counts, target dimensions, backend
name, trace marker/scope and draw summary, labeled descriptor counts, name, trace marker/scope and draw summary, labeled descriptor counts,

View File

@@ -594,6 +594,15 @@ bool RecordingCommandContext::in_render_pass() const noexcept
return in_render_pass_; return in_render_pass_;
} }
void RecordingCommandContext::reset() noexcept
{
active_target_ = TextureDesc {};
active_mesh_ = MeshDesc {};
in_render_pass_ = false;
shader_bound_ = false;
mesh_bound_ = false;
}
RecordingRenderTrace::RecordingRenderTrace(std::vector<RecordedRenderCommand>& commands) noexcept RecordingRenderTrace::RecordingRenderTrace(std::vector<RecordedRenderCommand>& commands) noexcept
: commands_(&commands) : commands_(&commands)
{ {
@@ -643,6 +652,11 @@ pp::foundation::Status RecordingRenderTrace::end_scope() noexcept
return pp::foundation::Status::success(); return pp::foundation::Status::success();
} }
void RecordingRenderTrace::reset() noexcept
{
scope_depth_ = 0U;
}
RecordingRenderDevice::RecordingRenderDevice() noexcept RecordingRenderDevice::RecordingRenderDevice() noexcept
: context_(commands_) : context_(commands_)
, trace_(commands_) , trace_(commands_)
@@ -757,6 +771,8 @@ std::span<const RecordedRenderCommand> RecordingRenderDevice::commands() const n
void RecordingRenderDevice::clear() noexcept void RecordingRenderDevice::clear() noexcept
{ {
commands_.clear(); commands_.clear();
context_.reset();
trace_.reset();
} }
const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) noexcept const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) noexcept

View File

@@ -172,6 +172,7 @@ public:
void end_render_pass() noexcept override; void end_render_pass() noexcept override;
[[nodiscard]] bool in_render_pass() const noexcept; [[nodiscard]] bool in_render_pass() const noexcept;
void reset() noexcept;
private: private:
std::vector<RecordedRenderCommand>* commands_ = nullptr; std::vector<RecordedRenderCommand>* commands_ = nullptr;
@@ -188,6 +189,7 @@ public:
[[nodiscard]] pp::foundation::Status 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 begin_scope(const char* component, const char* name) noexcept override;
[[nodiscard]] pp::foundation::Status end_scope() noexcept override; [[nodiscard]] pp::foundation::Status end_scope() noexcept override;
void reset() noexcept;
private: private:
std::vector<RecordedRenderCommand>* commands_ = nullptr; std::vector<RecordedRenderCommand>* commands_ = nullptr;

View File

@@ -2127,6 +2127,49 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
PP_EXPECT(h, device.commands().empty()); PP_EXPECT(h, device.commands().empty());
} }
void recording_renderer_clear_resets_context_and_trace_state(pp::tests::Harness& h)
{
RecordingRenderDevice device;
RecordingRenderTarget target(TextureDesc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::readback_source,
.debug_name = "reset-target",
});
auto* trace = device.trace();
PP_EXPECT(h, trace != nullptr);
if (trace == nullptr) {
return;
}
PP_EXPECT(h, trace->begin_scope("renderer", "interrupted-frame").ok());
auto& context = device.immediate_context();
PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc {}).ok());
PP_EXPECT(h, !device.commands().empty());
device.clear();
PP_EXPECT(h, device.commands().empty());
const auto trace_end_after_clear = trace->end_scope();
PP_EXPECT(h, !trace_end_after_clear.ok());
PP_EXPECT(h, trace_end_after_clear.code == StatusCode::invalid_argument);
PP_EXPECT(h, device.commands().empty());
PP_EXPECT(h, trace->begin_scope("renderer", "next-frame").ok());
PP_EXPECT(h, trace->end_scope().ok());
PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc {}).ok());
context.end_render_pass();
const auto commands = device.commands();
PP_EXPECT(h, commands.size() == 4U);
PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::trace_begin_scope);
PP_EXPECT(h, commands[1].kind == RecordedRenderCommandKind::trace_end_scope);
PP_EXPECT(h, commands[2].kind == RecordedRenderCommandKind::begin_render_pass);
PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::end_render_pass);
}
void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Harness& h) void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Harness& h)
{ {
RecordingRenderDevice device; RecordingRenderDevice device;
@@ -2565,6 +2608,7 @@ int main()
harness.run("recording_renderer_records_mipmap_generation", recording_renderer_records_mipmap_generation); harness.run("recording_renderer_records_mipmap_generation", recording_renderer_records_mipmap_generation);
harness.run("recording_renderer_records_texture_transitions", recording_renderer_records_texture_transitions); harness.run("recording_renderer_records_texture_transitions", recording_renderer_records_texture_transitions);
harness.run("recording_renderer_records_valid_command_sequences", recording_renderer_records_valid_command_sequences); harness.run("recording_renderer_records_valid_command_sequences", recording_renderer_records_valid_command_sequences);
harness.run("recording_renderer_clear_resets_context_and_trace_state", recording_renderer_clear_resets_context_and_trace_state);
harness.run("recording_renderer_rejects_invalid_command_order_and_targets", recording_renderer_rejects_invalid_command_order_and_targets); harness.run("recording_renderer_rejects_invalid_command_order_and_targets", recording_renderer_rejects_invalid_command_order_and_targets);
return harness.finish(); return harness.finish();
} }