diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index fc20f84..b1e2b0d 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -279,7 +279,9 @@ Known local toolchain state: 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, and unclosed passes. It also validates executable command + dependencies, including shader-before-uniform and shader-plus-mesh before + draw within each render pass. 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. @@ -346,7 +348,8 @@ Known local toolchain state: emits an `openGlPlan` JSON object with 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. Its + command count, render-pass order error count, dependency 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 dfdd5b3..ce41ac0 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -538,7 +538,9 @@ 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. +passes, and unclosed passes. It also validates executable command dependencies, +including shader-before-uniform and shader-plus-mesh before draw within each +render pass. The existing renderer classes are not yet fully behind the renderer interfaces. @@ -853,7 +855,8 @@ Results: 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. + texture-command counts, broken render-pass order detection, and executable + draw/uniform dependency failures. - 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 @@ -898,8 +901,8 @@ Results: `pp_renderer_gl` is available, it also emits an `openGlPlan` JSON object with 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 + counts, unsupported command count, render-pass order error count, dependency + 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 50827f7..da25bcd 100644 --- a/src/renderer_gl/command_plan.cpp +++ b/src/renderer_gl/command_plan.cpp @@ -69,6 +69,14 @@ void record_render_pass_order_error(OpenGlCommandPlan& plan, std::size_t index) } } +void record_dependency_error(OpenGlCommandPlan& plan, std::size_t index) noexcept +{ + ++plan.dependency_error_count; + if (plan.first_dependency_error == OpenGlCommandPlan::npos) { + plan.first_dependency_error = index; + } +} + } const char* planned_command_kind_name(OpenGlPlannedCommandKind kind) noexcept @@ -286,6 +294,8 @@ OpenGlCommandPlan plan_recorded_render_commands( plan.commands.reserve(commands.size()); bool in_render_pass = false; + bool shader_bound_in_pass = false; + bool mesh_bound_in_pass = false; for (std::size_t index = 0; index < commands.size(); ++index) { OpenGlPlannedCommand planned = plan_recorded_render_command(commands[index]); @@ -300,30 +310,47 @@ OpenGlCommandPlan plan_recorded_render_commands( record_render_pass_order_error(plan, index); } in_render_pass = true; + shader_bound_in_pass = false; + mesh_bound_in_pass = false; break; case OpenGlPlannedCommandKind::end_render_pass: if (!in_render_pass) { record_render_pass_order_error(plan, index); } in_render_pass = false; + shader_bound_in_pass = false; + mesh_bound_in_pass = false; break; case OpenGlPlannedCommandKind::draw: ++plan.draw_command_count; if (!in_render_pass) { record_render_pass_order_error(plan, index); } + if (!shader_bound_in_pass || !mesh_bound_in_pass) { + record_dependency_error(plan, index); + } break; case OpenGlPlannedCommandKind::bind_shader: ++plan.shader_bind_command_count; if (!in_render_pass) { record_render_pass_order_error(plan, index); } + shader_bound_in_pass = true; break; case OpenGlPlannedCommandKind::set_shader_uniform: ++plan.uniform_command_count; if (!in_render_pass) { record_render_pass_order_error(plan, index); } + if (!shader_bound_in_pass) { + record_dependency_error(plan, index); + } + break; + case OpenGlPlannedCommandKind::bind_mesh: + if (!in_render_pass) { + record_render_pass_order_error(plan, index); + } + mesh_bound_in_pass = true; break; case OpenGlPlannedCommandKind::upload_texture: ++plan.upload_command_count; @@ -368,7 +395,8 @@ OpenGlCommandPlan plan_recorded_render_commands( } plan.supported = plan.unsupported_command_count == 0U - && plan.render_pass_order_error_count == 0U; + && plan.render_pass_order_error_count == 0U + && plan.dependency_error_count == 0U; return plan; } diff --git a/src/renderer_gl/command_plan.h b/src/renderer_gl/command_plan.h index 6173662..a523f5a 100644 --- a/src/renderer_gl/command_plan.h +++ b/src/renderer_gl/command_plan.h @@ -89,8 +89,10 @@ struct OpenGlCommandPlan { std::uint32_t trace_command_count = 0; std::uint32_t unsupported_command_count = 0; std::uint32_t render_pass_order_error_count = 0; + std::uint32_t dependency_error_count = 0; std::size_t first_unsupported_command = npos; std::size_t first_render_pass_order_error = npos; + std::size_t first_dependency_error = npos; bool ended_in_render_pass = false; bool supported = true; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 70d4a09..57d3c3c 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.*\"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") + 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.*\"dependencyErrors\":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 bf98515..3010161 100644 --- a/tests/renderer_gl/command_plan_tests.cpp +++ b/tests/renderer_gl/command_plan_tests.cpp @@ -59,6 +59,16 @@ pp::renderer::RecordedRenderCommand shader_uniform_command(const char* name, std return command; } +pp::renderer::RecordedRenderCommand bind_mesh_command() noexcept +{ + pp::renderer::RecordedRenderCommand command; + command.kind = pp::renderer::RecordedRenderCommandKind::bind_mesh; + command.mesh_desc.topology = pp::renderer::PrimitiveTopology::triangles; + command.mesh_desc.vertex_count = 3U; + command.mesh_desc.index_count = 3U; + return command; +} + pp::renderer::RecordedRenderCommand blit_command(pp::renderer::BlitFilter filter) noexcept { pp::renderer::RecordedRenderCommand command; @@ -380,6 +390,7 @@ void plans_valid_recorded_command_streams(pp::tests::Harness& h) viewport_command(), bind_shader_command("stream-shader"), shader_uniform_command("mvp", 64U), + bind_mesh_command(), draw_command(), command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass), blit_command(pp::renderer::BlitFilter::nearest), @@ -397,14 +408,17 @@ void plans_valid_recorded_command_streams(pp::tests::Harness& h) 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); + PP_EXPECT(h, plan.dependency_error_count == 0U); PP_EXPECT(h, plan.first_unsupported_command == pp::renderer::gl::OpenGlCommandPlan::npos); PP_EXPECT(h, plan.first_render_pass_order_error == pp::renderer::gl::OpenGlCommandPlan::npos); + PP_EXPECT(h, plan.first_dependency_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::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); + PP_EXPECT(h, plan.commands[5].kind == pp::renderer::gl::OpenGlPlannedCommandKind::bind_mesh); + PP_EXPECT(h, plan.commands[6].kind == pp::renderer::gl::OpenGlPlannedCommandKind::draw); + PP_EXPECT(h, plan.commands[8].kind == pp::renderer::gl::OpenGlPlannedCommandKind::blit_render_target); } void flags_broken_render_pass_command_order(pp::tests::Harness& h) @@ -439,10 +453,65 @@ void flags_broken_render_pass_command_order(pp::tests::Harness& h) PP_EXPECT(h, unclosed.ended_in_render_pass); } +void flags_missing_render_dependencies(pp::tests::Harness& h) +{ + const std::vector uniform_before_shader { + begin_render_pass_command(), + shader_uniform_command("mvp", 64U), + bind_shader_command("shader"), + bind_mesh_command(), + draw_command(), + command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass), + }; + const std::vector draw_without_mesh { + begin_render_pass_command(), + bind_shader_command("shader"), + draw_command(), + command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass), + }; + const std::vector draw_without_shader { + begin_render_pass_command(), + bind_mesh_command(), + draw_command(), + command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass), + }; + const std::vector second_pass_missing_bindings { + begin_render_pass_command(), + bind_shader_command("shader"), + bind_mesh_command(), + draw_command(), + command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass), + begin_render_pass_command(), + draw_command(), + command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass), + }; + + const auto bad_uniform = pp::renderer::gl::plan_recorded_render_commands(uniform_before_shader); + const auto bad_mesh = pp::renderer::gl::plan_recorded_render_commands(draw_without_mesh); + const auto bad_shader = pp::renderer::gl::plan_recorded_render_commands(draw_without_shader); + const auto bad_second_pass = pp::renderer::gl::plan_recorded_render_commands(second_pass_missing_bindings); + + PP_EXPECT(h, !bad_uniform.supported); + PP_EXPECT(h, bad_uniform.dependency_error_count == 1U); + PP_EXPECT(h, bad_uniform.first_dependency_error == 1U); + PP_EXPECT(h, !bad_mesh.supported); + PP_EXPECT(h, bad_mesh.dependency_error_count == 1U); + PP_EXPECT(h, bad_mesh.first_dependency_error == 2U); + PP_EXPECT(h, !bad_shader.supported); + PP_EXPECT(h, bad_shader.dependency_error_count == 1U); + PP_EXPECT(h, bad_shader.first_dependency_error == 2U); + PP_EXPECT(h, !bad_second_pass.supported); + PP_EXPECT(h, bad_second_pass.dependency_error_count == 1U); + PP_EXPECT(h, bad_second_pass.first_dependency_error == 6U); + PP_EXPECT(h, bad_second_pass.render_pass_order_error_count == 0U); +} + void tracks_unsupported_commands_in_streams(pp::tests::Harness& h) { std::vector commands { begin_render_pass_command(), + bind_shader_command("shader"), + bind_mesh_command(), draw_command(), command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass), command_with_kind(static_cast(255U)), @@ -452,9 +521,10 @@ void tracks_unsupported_commands_in_streams(pp::tests::Harness& h) PP_EXPECT(h, !plan.supported); PP_EXPECT(h, plan.unsupported_command_count == 1U); - PP_EXPECT(h, plan.first_unsupported_command == 3U); + PP_EXPECT(h, plan.first_unsupported_command == 5U); PP_EXPECT(h, plan.render_pass_order_error_count == 0U); - PP_EXPECT(h, plan.commands[3].kind == pp::renderer::gl::OpenGlPlannedCommandKind::unknown); + PP_EXPECT(h, plan.dependency_error_count == 0U); + PP_EXPECT(h, plan.commands[5].kind == pp::renderer::gl::OpenGlPlannedCommandKind::unknown); } void counts_typed_texture_commands_in_streams(pp::tests::Harness& h) @@ -525,6 +595,7 @@ int main() harness.run("names_planned_command_kinds", names_planned_command_kinds); harness.run("plans_valid_recorded_command_streams", plans_valid_recorded_command_streams); harness.run("flags_broken_render_pass_command_order", flags_broken_render_pass_command_order); + harness.run("flags_missing_render_dependencies", flags_missing_render_dependencies); harness.run("tracks_unsupported_commands_in_streams", tracks_unsupported_commands_in_streams); harness.run("counts_typed_texture_commands_in_streams", counts_typed_texture_commands_in_streams); return harness.finish(); diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index b1aef3c..9e3f7c3 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -2797,6 +2797,7 @@ int record_render(int argc, char** argv) << ",\"traceCommands\":" << open_gl_plan.trace_command_count << ",\"unsupportedCommands\":" << open_gl_plan.unsupported_command_count << ",\"renderPassOrderErrors\":" << open_gl_plan.render_pass_order_error_count + << ",\"dependencyErrors\":" << open_gl_plan.dependency_error_count << ",\"endedInRenderPass\":" << json_bool(open_gl_plan.ended_in_render_pass) << "}" #endif