From 1dcd96ab367861a1aeab43dca0847f0bca4974b1 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 2 Jun 2026 21:02:24 +0200 Subject: [PATCH] Plan OpenGL shader command metadata --- docs/modernization/build-inventory.md | 15 +++--- docs/modernization/roadmap.md | 27 ++++++----- src/renderer_gl/command_plan.cpp | 36 ++++++++++++-- src/renderer_gl/command_plan.h | 6 +++ tests/CMakeLists.txt | 2 +- tests/renderer_gl/command_plan_tests.cpp | 61 ++++++++++++++++++++++-- tools/pano_cli/main.cpp | 2 + 7 files changed, 119 insertions(+), 30 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 0c2f507..fc20f84 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -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`, diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index c666991..dfdd5b3 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -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 diff --git a/src/renderer_gl/command_plan.cpp b/src/renderer_gl/command_plan.cpp index 2d17744..50827f7 100644 --- a/src/renderer_gl/command_plan.cpp +++ b/src/renderer_gl/command_plan.cpp @@ -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; diff --git a/src/renderer_gl/command_plan.h b/src/renderer_gl/command_plan.h index 19ba1db..6173662 100644 --- a/src/renderer_gl/command_plan.h +++ b/src/renderer_gl/command_plan.h @@ -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 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; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5aeddc8..70d4a09 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/renderer_gl/command_plan_tests.cpp b/tests/renderer_gl/command_plan_tests.cpp index bb34df0..bf98515 100644 --- a/tests/renderer_gl/command_plan_tests.cpp +++ b/tests/renderer_gl/command_plan_tests.cpp @@ -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(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(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(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); diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 26b0ad6..b1aef3c 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -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