Plan OpenGL shader command metadata

This commit is contained in:
2026-06-02 21:02:24 +02:00
parent b6a25474ff
commit 1dcd96ab36
7 changed files with 119 additions and 30 deletions

View File

@@ -275,10 +275,11 @@ Known local toolchain state:
blit filters into GL-facing planned command data while rejecting unsupported
enum tokens before a real GL context is needed. It also plans whole recorded
command streams, preserving per-command planned data while counting render
passes, draws, texture uploads, mipmap generation, texture transitions,
texture copies, texture readbacks, frame captures, passthrough commands,
trace commands, unsupported commands, and render-pass ordering errors such as
state changes outside a pass, nested passes, and unclosed passes.
passes, draws, shader binds, shader uniforms, texture uploads, mipmap
generation, texture transitions, texture copies, texture readbacks, frame
captures, passthrough commands, trace commands, unsupported commands, and
render-pass ordering errors such as state changes outside a pass, nested
passes, and unclosed passes.
Desktop VR drawing also consumes backend-owned scissor/depth/blend state,
depth clear masks, active texture units, and fallback 2D texture unbind
targets while retaining the existing VR SDK/platform bridge shape.
@@ -343,9 +344,9 @@ Known local toolchain state:
labeled descriptor counts, backend resource creation counts, plus draw
descriptor vertex/index totals. When `pp_renderer_gl` is available, it also
emits an `openGlPlan` JSON object with the planned command count, support
status, render-pass/draw/texture-upload/mipmap/transition/copy/readback/
capture/passthrough/trace counts, unsupported command count, render-pass
order error count, and unclosed-pass state. Its
status, render-pass/draw/shader-bind/uniform/texture-upload/mipmap/
transition/copy/readback/capture/passthrough/trace counts, unsupported
command count, render-pass order error count, and unclosed-pass state. Its
`--exercise-clear` mode verifies
interrupted-frame recorder clear/reuse behavior and reports the result in
JSON, and is covered by `pano_cli_record_render_smoke`,

View File

@@ -534,10 +534,11 @@ blend/depth/sampler state, texture formats, primitive modes, draw counts, and
blit filters into GL-facing planned command data with explicit unsupported-token
rejection before a runtime GL context is needed. It also plans whole recorded
command streams, preserving per-command planned data while counting render
passes, draws, texture uploads, mipmap generation, texture transitions, texture
copies, texture readbacks, frame captures, passthrough commands, trace
commands, unsupported commands, and render-pass ordering errors such as state
changes outside a pass, nested passes, and unclosed passes.
passes, draws, shader binds, shader uniforms, texture uploads, mipmap
generation, texture transitions, texture copies, texture readbacks, frame
captures, passthrough commands, trace commands, unsupported commands, and
render-pass ordering errors such as state changes outside a pass, nested
passes, and unclosed passes.
The existing renderer classes are not yet fully
behind the renderer interfaces.
@@ -848,11 +849,11 @@ Results:
- `pp_renderer_gl_command_plan_tests` covers the headless OpenGL command
planner for recorded render-pass clear masks/values, viewport/scissor state,
blend/depth/sampler state, texture format mapping, mesh/draw primitive modes,
draw counts, texture upload/mipmap/transition/copy/readback/capture metadata,
blit filters and byte totals, planned command names, unsupported enum/state
rejection, whole recorded stream planning, valid trace/render/draw/blit
ordering, typed texture-command counts, and broken render-pass order
detection.
draw counts, shader bind/uniform names and byte counts, texture
upload/mipmap/transition/copy/readback/capture metadata, blit filters and
byte totals, planned command names, unsupported enum/state rejection, whole
recorded stream planning, valid trace/render/shader/draw/blit ordering, typed
texture-command counts, and broken render-pass order detection.
- PowerShell analyze automation returns JSON summaries and includes the shader
validation target and renderer-boundary guard.
- `windows-msvc-vcpkg-headless` configured through the Visual Studio bundled
@@ -895,10 +896,10 @@ Results:
shader-uniform/texture/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/
frame-capture/blit command/byte totals for agent automation. When
`pp_renderer_gl` is available, it also emits an `openGlPlan` JSON object with
planned command count, support status, render-pass/draw/texture-upload/
mipmap/transition/copy/readback/capture/passthrough/trace counts,
unsupported command count, render-pass order error count, and unclosed-pass
state. The
planned command count, support status, render-pass/draw/shader-bind/uniform/
texture-upload/mipmap/transition/copy/readback/capture/passthrough/trace
counts, unsupported command count, render-pass order error count, and
unclosed-pass state. The
`--exercise-clear` mode deliberately clears an interrupted trace/render pass,
verifies stale trace-scope state is rejected, verifies the render context can
be reused, and then emits that reset status in JSON. It also has an

View File

@@ -29,6 +29,11 @@ namespace {
}
}
[[nodiscard]] bool non_empty_name(const char* name) noexcept
{
return name != nullptr && name[0] != '\0';
}
[[nodiscard]] bool requires_render_pass(pp::renderer::RecordedRenderCommandKind kind) noexcept
{
switch (kind) {
@@ -81,6 +86,10 @@ const char* planned_command_kind_name(OpenGlPlannedCommandKind kind) noexcept
return "set_blend_state";
case OpenGlPlannedCommandKind::set_depth_state:
return "set_depth_state";
case OpenGlPlannedCommandKind::bind_shader:
return "bind_shader";
case OpenGlPlannedCommandKind::set_shader_uniform:
return "set_shader_uniform";
case OpenGlPlannedCommandKind::bind_texture:
return "bind_texture";
case OpenGlPlannedCommandKind::bind_sampler:
@@ -160,6 +169,17 @@ OpenGlPlannedCommand plan_recorded_render_command(pp::renderer::RecordedRenderCo
planned.depth = depth_state_for_renderer_depth_state(command.depth_state);
planned.supported = planned.depth.supported;
break;
case pp::renderer::RecordedRenderCommandKind::bind_shader:
planned.kind = OpenGlPlannedCommandKind::bind_shader;
planned.name = command.name;
planned.supported = non_empty_name(planned.name);
break;
case pp::renderer::RecordedRenderCommandKind::set_shader_uniform:
planned.kind = OpenGlPlannedCommandKind::set_shader_uniform;
planned.name = command.name;
planned.uniform_bytes = command.uniform_bytes;
planned.supported = non_empty_name(planned.name) && planned.uniform_bytes > 0U;
break;
case pp::renderer::RecordedRenderCommandKind::bind_texture:
planned.kind = OpenGlPlannedCommandKind::bind_texture;
planned.texture_format = texture_format_for_renderer_format(command.texture_desc.format);
@@ -250,10 +270,6 @@ OpenGlPlannedCommand plan_recorded_render_command(pp::renderer::RecordedRenderCo
case pp::renderer::RecordedRenderCommandKind::trace_end_scope:
planned.kind = OpenGlPlannedCommandKind::trace;
break;
case pp::renderer::RecordedRenderCommandKind::bind_shader:
case pp::renderer::RecordedRenderCommandKind::set_shader_uniform:
planned.kind = OpenGlPlannedCommandKind::passthrough;
break;
default:
planned.kind = OpenGlPlannedCommandKind::unknown;
planned.supported = false;
@@ -297,6 +313,18 @@ OpenGlCommandPlan plan_recorded_render_commands(
record_render_pass_order_error(plan, index);
}
break;
case OpenGlPlannedCommandKind::bind_shader:
++plan.shader_bind_command_count;
if (!in_render_pass) {
record_render_pass_order_error(plan, index);
}
break;
case OpenGlPlannedCommandKind::set_shader_uniform:
++plan.uniform_command_count;
if (!in_render_pass) {
record_render_pass_order_error(plan, index);
}
break;
case OpenGlPlannedCommandKind::upload_texture:
++plan.upload_command_count;
break;

View File

@@ -17,6 +17,8 @@ enum class OpenGlPlannedCommandKind : std::uint8_t {
set_scissor,
set_blend_state,
set_depth_state,
bind_shader,
set_shader_uniform,
bind_texture,
bind_sampler,
bind_mesh,
@@ -60,9 +62,11 @@ struct OpenGlPlannedCommand {
std::uint64_t capture_bytes = 0;
std::uint64_t blit_source_bytes = 0;
std::uint64_t blit_destination_bytes = 0;
std::uint64_t uniform_bytes = 0;
std::uint32_t primitive_mode = 0;
std::uint32_t draw_vertex_count = 0;
std::uint32_t draw_index_count = 0;
const char* name = "";
bool requires_render_pass = false;
bool supported = false;
};
@@ -73,6 +77,8 @@ struct OpenGlCommandPlan {
std::vector<OpenGlPlannedCommand> commands;
std::uint32_t render_pass_count = 0;
std::uint32_t draw_command_count = 0;
std::uint32_t shader_bind_command_count = 0;
std::uint32_t uniform_command_count = 0;
std::uint32_t upload_command_count = 0;
std::uint32_t mipmap_command_count = 0;
std::uint32_t transition_command_count = 0;

View File

@@ -384,7 +384,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\".*\"framebufferFetch\":false.*\"explicitTextureTransitions\":true.*\"textureCopy\":true.*\"renderTargetBlit\":true.*\"frameCapture\":true.*\"float16RenderTargets\":false.*\"float32RenderTargets\":false.*\"width\":32.*\"height\":16.*\"createdResources\":7.*\"exercisedClearReset\":false.*\"clearRejectedOrphanedTraceEnd\":false.*\"clearReusedRenderPass\":false.*\"labeledCommandDescriptors\":16.*\"commands\":25.*\"openGlPlan\":.*\"available\":true.*\"supported\":true.*\"commands\":25.*\"renderPasses\":1.*\"drawCommands\":1.*\"uploadCommands\":1.*\"mipmapCommands\":1.*\"transitionCommands\":4.*\"copyCommands\":1.*\"readbackCommands\":1.*\"captureCommands\":1.*\"passthroughCommands\":2.*\"traceCommands\":3.*\"unsupportedCommands\":0.*\"renderPassOrderErrors\":0.*\"endedInRenderPass\":false.*\"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.*\"mipmapCommands\":1.*\"mipmapLevels\":3.*\"mipmapBytes\":84.*\"transitionCommands\":4.*\"transitionToUpload\":1.*\"transitionToShaderRead\":2.*\"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")
PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"framebufferFetch\":false.*\"explicitTextureTransitions\":true.*\"textureCopy\":true.*\"renderTargetBlit\":true.*\"frameCapture\":true.*\"float16RenderTargets\":false.*\"float32RenderTargets\":false.*\"width\":32.*\"height\":16.*\"createdResources\":7.*\"exercisedClearReset\":false.*\"clearRejectedOrphanedTraceEnd\":false.*\"clearReusedRenderPass\":false.*\"labeledCommandDescriptors\":16.*\"commands\":25.*\"openGlPlan\":.*\"available\":true.*\"supported\":true.*\"commands\":25.*\"renderPasses\":1.*\"drawCommands\":1.*\"shaderBindCommands\":1.*\"uniformCommands\":1.*\"uploadCommands\":1.*\"mipmapCommands\":1.*\"transitionCommands\":4.*\"copyCommands\":1.*\"readbackCommands\":1.*\"captureCommands\":1.*\"passthroughCommands\":0.*\"traceCommands\":3.*\"unsupportedCommands\":0.*\"renderPassOrderErrors\":0.*\"endedInRenderPass\":false.*\"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.*\"mipmapCommands\":1.*\"mipmapLevels\":3.*\"mipmapBytes\":84.*\"transitionCommands\":4.*\"transitionToUpload\":1.*\"transitionToShaderRead\":2.*\"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_exercises_clear_reset
COMMAND pano_cli record-render --width 32 --height 16 --exercise-clear)

View File

@@ -42,6 +42,23 @@ pp::renderer::RecordedRenderCommand draw_command() noexcept
return command;
}
pp::renderer::RecordedRenderCommand bind_shader_command(const char* name) noexcept
{
pp::renderer::RecordedRenderCommand command;
command.kind = pp::renderer::RecordedRenderCommandKind::bind_shader;
command.name = name;
return command;
}
pp::renderer::RecordedRenderCommand shader_uniform_command(const char* name, std::uint64_t bytes) noexcept
{
pp::renderer::RecordedRenderCommand command;
command.kind = pp::renderer::RecordedRenderCommandKind::set_shader_uniform;
command.name = name;
command.uniform_bytes = bytes;
return command;
}
pp::renderer::RecordedRenderCommand blit_command(pp::renderer::BlitFilter filter) noexcept
{
pp::renderer::RecordedRenderCommand command;
@@ -256,6 +273,24 @@ void maps_texture_io_and_capture_commands(pp::tests::Harness& h)
PP_EXPECT(h, capture.capture_bytes == 512U);
}
void maps_shader_commands(pp::tests::Harness& h)
{
const auto shader = pp::renderer::gl::plan_recorded_render_command(
bind_shader_command("stroke-composite"));
const auto uniform = pp::renderer::gl::plan_recorded_render_command(
shader_uniform_command("mvp", 64U));
PP_EXPECT(h, shader.supported);
PP_EXPECT(h, shader.kind == pp::renderer::gl::OpenGlPlannedCommandKind::bind_shader);
PP_EXPECT(h, shader.name == std::string_view("stroke-composite"));
PP_EXPECT(h, shader.requires_render_pass);
PP_EXPECT(h, uniform.supported);
PP_EXPECT(h, uniform.kind == pp::renderer::gl::OpenGlPlannedCommandKind::set_shader_uniform);
PP_EXPECT(h, uniform.name == std::string_view("mvp"));
PP_EXPECT(h, uniform.uniform_bytes == 64U);
PP_EXPECT(h, uniform.requires_render_pass);
}
void rejects_unsupported_command_tokens(pp::tests::Harness& h)
{
const auto bad_blend = pp::renderer::gl::plan_recorded_render_command(pp::renderer::RecordedRenderCommand {
@@ -290,6 +325,10 @@ void rejects_unsupported_command_tokens(pp::tests::Harness& h)
bad_transition_command.before_state = static_cast<pp::renderer::TextureState>(255U);
bad_transition_command.after_state = pp::renderer::TextureState::shader_read;
const auto bad_transition = pp::renderer::gl::plan_recorded_render_command(bad_transition_command);
const auto bad_shader = pp::renderer::gl::plan_recorded_render_command(
bind_shader_command(""));
const auto bad_uniform = pp::renderer::gl::plan_recorded_render_command(
shader_uniform_command("mvp", 0U));
const auto unknown = pp::renderer::gl::plan_recorded_render_command(pp::renderer::RecordedRenderCommand {
.kind = static_cast<pp::renderer::RecordedRenderCommandKind>(255U),
});
@@ -306,6 +345,10 @@ void rejects_unsupported_command_tokens(pp::tests::Harness& h)
PP_EXPECT(h, bad_blit.blit_filter.value == 0U);
PP_EXPECT(h, !bad_transition.supported);
PP_EXPECT(h, bad_transition.kind == pp::renderer::gl::OpenGlPlannedCommandKind::transition_texture);
PP_EXPECT(h, !bad_shader.supported);
PP_EXPECT(h, bad_shader.kind == pp::renderer::gl::OpenGlPlannedCommandKind::bind_shader);
PP_EXPECT(h, !bad_uniform.supported);
PP_EXPECT(h, bad_uniform.kind == pp::renderer::gl::OpenGlPlannedCommandKind::set_shader_uniform);
PP_EXPECT(h, !unknown.supported);
PP_EXPECT(h, unknown.kind == pp::renderer::gl::OpenGlPlannedCommandKind::unknown);
}
@@ -321,6 +364,9 @@ void names_planned_command_kinds(pp::tests::Harness& h)
PP_EXPECT(h, pp::renderer::gl::planned_command_kind_name(
pp::renderer::gl::OpenGlPlannedCommandKind::copy_texture)
== std::string_view("copy_texture"));
PP_EXPECT(h, pp::renderer::gl::planned_command_kind_name(
pp::renderer::gl::OpenGlPlannedCommandKind::set_shader_uniform)
== std::string_view("set_shader_uniform"));
PP_EXPECT(h, pp::renderer::gl::planned_command_kind_name(
static_cast<pp::renderer::gl::OpenGlPlannedCommandKind>(255U))
== std::string_view("unknown"));
@@ -332,7 +378,8 @@ void plans_valid_recorded_command_streams(pp::tests::Harness& h)
command_with_kind(pp::renderer::RecordedRenderCommandKind::trace_begin_scope),
begin_render_pass_command(),
viewport_command(),
command_with_kind(pp::renderer::RecordedRenderCommandKind::bind_shader),
bind_shader_command("stream-shader"),
shader_uniform_command("mvp", 64U),
draw_command(),
command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass),
blit_command(pp::renderer::BlitFilter::nearest),
@@ -344,7 +391,9 @@ void plans_valid_recorded_command_streams(pp::tests::Harness& h)
PP_EXPECT(h, plan.commands.size() == commands.size());
PP_EXPECT(h, plan.render_pass_count == 1U);
PP_EXPECT(h, plan.draw_command_count == 1U);
PP_EXPECT(h, plan.passthrough_command_count == 1U);
PP_EXPECT(h, plan.shader_bind_command_count == 1U);
PP_EXPECT(h, plan.uniform_command_count == 1U);
PP_EXPECT(h, plan.passthrough_command_count == 0U);
PP_EXPECT(h, plan.trace_command_count == 1U);
PP_EXPECT(h, plan.unsupported_command_count == 0U);
PP_EXPECT(h, plan.render_pass_order_error_count == 0U);
@@ -352,9 +401,10 @@ void plans_valid_recorded_command_streams(pp::tests::Harness& h)
PP_EXPECT(h, plan.first_render_pass_order_error == pp::renderer::gl::OpenGlCommandPlan::npos);
PP_EXPECT(h, !plan.ended_in_render_pass);
PP_EXPECT(h, plan.commands[1].kind == pp::renderer::gl::OpenGlPlannedCommandKind::begin_render_pass);
PP_EXPECT(h, plan.commands[3].kind == pp::renderer::gl::OpenGlPlannedCommandKind::passthrough);
PP_EXPECT(h, plan.commands[4].kind == pp::renderer::gl::OpenGlPlannedCommandKind::draw);
PP_EXPECT(h, plan.commands[6].kind == pp::renderer::gl::OpenGlPlannedCommandKind::blit_render_target);
PP_EXPECT(h, plan.commands[3].kind == pp::renderer::gl::OpenGlPlannedCommandKind::bind_shader);
PP_EXPECT(h, plan.commands[4].kind == pp::renderer::gl::OpenGlPlannedCommandKind::set_shader_uniform);
PP_EXPECT(h, plan.commands[5].kind == pp::renderer::gl::OpenGlPlannedCommandKind::draw);
PP_EXPECT(h, plan.commands[7].kind == pp::renderer::gl::OpenGlPlannedCommandKind::blit_render_target);
}
void flags_broken_render_pass_command_order(pp::tests::Harness& h)
@@ -470,6 +520,7 @@ int main()
harness.run("maps_render_pass_and_state_commands", maps_render_pass_and_state_commands);
harness.run("maps_binding_draw_and_blit_commands", maps_binding_draw_and_blit_commands);
harness.run("maps_texture_io_and_capture_commands", maps_texture_io_and_capture_commands);
harness.run("maps_shader_commands", maps_shader_commands);
harness.run("rejects_unsupported_command_tokens", rejects_unsupported_command_tokens);
harness.run("names_planned_command_kinds", names_planned_command_kinds);
harness.run("plans_valid_recorded_command_streams", plans_valid_recorded_command_streams);

View File

@@ -2785,6 +2785,8 @@ int record_render(int argc, char** argv)
<< ",\"commands\":" << open_gl_plan.commands.size()
<< ",\"renderPasses\":" << open_gl_plan.render_pass_count
<< ",\"drawCommands\":" << open_gl_plan.draw_command_count
<< ",\"shaderBindCommands\":" << open_gl_plan.shader_bind_command_count
<< ",\"uniformCommands\":" << open_gl_plan.uniform_command_count
<< ",\"uploadCommands\":" << open_gl_plan.upload_command_count
<< ",\"mipmapCommands\":" << open_gl_plan.mipmap_command_count
<< ",\"transitionCommands\":" << open_gl_plan.transition_command_count