From 3e15b2f46c225404eeb00cc982bbed7e2869885f Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 3 Jun 2026 06:03:28 +0200 Subject: [PATCH] Route VR render state through renderer GL --- docs/modernization/build-inventory.md | 5 +- docs/modernization/roadmap.md | 4 ++ src/app_vr.cpp | 51 +++++++++++++---- src/renderer_gl/opengl_capabilities.cpp | 29 ++++++++++ src/renderer_gl/opengl_capabilities.h | 16 ++++++ tests/renderer_gl/capabilities_tests.cpp | 70 ++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 13 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 1b73482..05b8327 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -464,8 +464,9 @@ Known local toolchain state: version/vendor/renderer/GLSL string query dispatch, tested default clear color/buffer dispatch consumed by `App::clear`, tested app UI viewport/scissor dispatch consumed by `App::draw` and `App::vr_draw_ui`, - plus renderer API to OpenGL token mapping and command-planning contracts used - by the OpenGL parity work. + tested generic capability/buffer-clear dispatch consumed by VR draw state + setup, plus renderer API to OpenGL token mapping and command-planning + contracts used by the OpenGL parity work. - `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability, new-document warning, publish prompt, and save-before-upload planning as JSON; the live cloud upload command consumes the same start contract before diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index e5b8b8d..c2f01ff 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -531,6 +531,10 @@ the live OpenGL call sequence. VR UI framebuffer viewport and scissor-test setup now also consumes those `pp_renderer_gl` contracts, keeping desktop and VR UI rendering aligned while the retained OpenVR app path is split incrementally. +VR draw blend/depth state transitions and depth-buffer clears now use generic +tested `pp_renderer_gl` capability and clear dispatch contracts, reducing +direct OpenGL execution in the retained VR app path without changing state +restore behavior. Windows RenderDoc frame capture hooks now also dispatch through `PlatformServices`, keeping capture integration in the platform service while leaving non-Windows adapters as no-ops. diff --git a/src/app_vr.cpp b/src/app_vr.cpp index c966c32..cb5f0ba 100644 --- a/src/app_vr.cpp +++ b/src/app_vr.cpp @@ -39,6 +39,11 @@ void set_opengl_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std glViewport(static_cast(x), static_cast(y), static_cast(width), static_cast(height)); } +void clear_opengl_mask(std::uint32_t mask) noexcept +{ + glClear(static_cast(mask)); +} + void apply_vr_ui_viewport(pp::renderer::gl::OpenGlViewportRect viewport) { const auto status = pp::renderer::gl::apply_opengl_viewport( @@ -62,6 +67,30 @@ void apply_vr_ui_scissor_test(bool enabled) LOG("OpenGL VR UI scissor test failed: %s", status.message); } +void apply_vr_render_capability(std::uint32_t state, bool enabled) +{ + const auto status = pp::renderer::gl::apply_opengl_capability( + state, + enabled, + pp::renderer::gl::OpenGlCapabilityDispatch { + .enable = enable_opengl_state, + .disable = disable_opengl_state, + }); + if (!status.ok()) + LOG("OpenGL VR render state failed: %s", status.message); +} + +void clear_vr_depth_buffer() +{ + const auto status = pp::renderer::gl::clear_opengl_buffers( + pp::renderer::gl::framebuffer_depth_buffer_mask(), + pp::renderer::gl::OpenGlBufferClearDispatch { + .clear = clear_opengl_mask, + }); + if (!status.ok()) + LOG("OpenGL VR depth clear failed: %s", status.message); +} + } bool trigger_down = false; @@ -247,9 +276,9 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat auto blend = glIsEnabled(pp::renderer::gl::blend_state()); auto depth = glIsEnabled(pp::renderer::gl::depth_test_state()); - glDisable(pp::renderer::gl::blend_state()); - glDisable(pp::renderer::gl::depth_test_state()); - glClear(pp::renderer::gl::framebuffer_depth_buffer_mask()); + apply_vr_render_capability(pp::renderer::gl::blend_state(), false); + apply_vr_render_capability(pp::renderer::gl::depth_test_state(), false); + clear_vr_depth_buffer(); for (int plane_index = 0; plane_index < 6; plane_index++) { @@ -264,9 +293,9 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat m_face_plane.draw_fill(); } - glEnable(pp::renderer::gl::blend_state()); - glEnable(pp::renderer::gl::depth_test_state()); - glClear(pp::renderer::gl::framebuffer_depth_buffer_mask()); + apply_vr_render_capability(pp::renderer::gl::blend_state(), true); + apply_vr_render_capability(pp::renderer::gl::depth_test_state(), true); + clear_vr_depth_buffer(); for (size_t i = 0; i < canvas->m_canvas->m_layers.size(); i++) { @@ -411,7 +440,7 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat m_face_plane.draw_stroke(); } - glDisable(pp::renderer::gl::depth_test_state()); + apply_vr_render_capability(pp::renderer::gl::depth_test_state(), false); // draw the brush /* auto mode = dynamic_cast(canvas->m_canvas->modes[(int)canvas->m_canvas->m_current_mode][0]); @@ -436,7 +465,7 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat glm::scale(glm::vec3(canvas->m_canvas->m_current_brush->m_tip_size / height)) * glm::eulerAngleZ(canvas->m_canvas->m_current_brush->m_tip_angle * (float)(M_PI * 2.0)) ); - glEnable(pp::renderer::gl::blend_state()); + apply_vr_render_capability(pp::renderer::gl::blend_state(), true); set_active_texture_unit(0); auto& tex = *canvas->m_canvas->m_current_brush->m_tip_texture; tex.bind(); @@ -510,7 +539,7 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat glm::scale(glm::vec3(canvas->m_canvas->m_current_brush->m_tip_size * 100.f / height)) * glm::eulerAngleZ(canvas->m_canvas->m_current_brush->m_tip_angle * (float)(M_PI * 2.0)) ); - glEnable(pp::renderer::gl::blend_state()); + apply_vr_render_capability(pp::renderer::gl::blend_state(), true); set_active_texture_unit(0); auto& tex = *canvas->m_canvas->m_current_brush->m_tip_texture; tex.bind(); @@ -538,8 +567,8 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat mode->on_Draw(ortho_proj, proj, camera); */ - blend ? glEnable(pp::renderer::gl::blend_state()) : glDisable(pp::renderer::gl::blend_state()); - depth ? glEnable(pp::renderer::gl::depth_test_state()) : glDisable(pp::renderer::gl::depth_test_state()); + apply_vr_render_capability(pp::renderer::gl::blend_state(), blend != 0U); + apply_vr_render_capability(pp::renderer::gl::depth_test_state(), depth != 0U); sampler.unbind(); } diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index ac37aa2..2478964 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -326,6 +326,35 @@ pp::foundation::Status apply_opengl_scissor_test( return pp::foundation::Status::success(); } +pp::foundation::Status apply_opengl_capability( + std::uint32_t state, + bool enabled, + OpenGlCapabilityDispatch dispatch) noexcept +{ + if (dispatch.enable == nullptr || dispatch.disable == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL capability dispatch callbacks must not be null"); + } + + if (enabled) { + dispatch.enable(state); + } else { + dispatch.disable(state); + } + return pp::foundation::Status::success(); +} + +pp::foundation::Status clear_opengl_buffers( + std::uint32_t mask, + OpenGlBufferClearDispatch dispatch) noexcept +{ + if (dispatch.clear == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL buffer clear dispatch callback must not be null"); + } + + dispatch.clear(mask); + return pp::foundation::Status::success(); +} + std::uint32_t extension_count_query() noexcept { return gl_num_extensions; diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index ce8ebe6..20b1f98 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -178,6 +178,15 @@ struct OpenGlScissorTestDispatch { OpenGlCapabilityFn disable = nullptr; }; +struct OpenGlCapabilityDispatch { + OpenGlCapabilityFn enable = nullptr; + OpenGlCapabilityFn disable = nullptr; +}; + +struct OpenGlBufferClearDispatch { + OpenGlClearFn clear = nullptr; +}; + [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( std::span extensions, OpenGlRuntime runtime) noexcept; @@ -198,6 +207,13 @@ struct OpenGlScissorTestDispatch { [[nodiscard]] pp::foundation::Status apply_opengl_scissor_test( bool enabled, OpenGlScissorTestDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status apply_opengl_capability( + std::uint32_t state, + bool enabled, + OpenGlCapabilityDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Status clear_opengl_buffers( + std::uint32_t mask, + OpenGlBufferClearDispatch dispatch) noexcept; [[nodiscard]] std::uint32_t extension_count_query() noexcept; [[nodiscard]] std::uint32_t extension_string_name() noexcept; diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index b5c606a..b2ba7a6 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -991,6 +991,72 @@ void rejects_incomplete_scissor_test_dispatch(pp::tests::Harness& h) PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument); } +void applies_generic_capability_dispatch(pp::tests::Harness& h) +{ + recorded_state_calls.clear(); + + const auto enabled_status = pp::renderer::gl::apply_opengl_capability( + 0x0BE2U, + true, + pp::renderer::gl::OpenGlCapabilityDispatch { + .enable = record_enable, + .disable = record_disable, + }); + const auto disabled_status = pp::renderer::gl::apply_opengl_capability( + 0x0B71U, + false, + pp::renderer::gl::OpenGlCapabilityDispatch { + .enable = record_enable, + .disable = record_disable, + }); + + PP_EXPECT(h, enabled_status.ok()); + PP_EXPECT(h, disabled_status.ok()); + PP_EXPECT(h, recorded_state_calls.size() == 2U); + PP_EXPECT(h, recorded_state_calls[0].kind == RecordedOpenGlStateCall::Kind::enable); + PP_EXPECT(h, recorded_state_calls[0].first == 0x0BE2U); + PP_EXPECT(h, recorded_state_calls[1].kind == RecordedOpenGlStateCall::Kind::disable); + PP_EXPECT(h, recorded_state_calls[1].first == 0x0B71U); +} + +void rejects_incomplete_generic_capability_dispatch(pp::tests::Harness& h) +{ + const auto status = pp::renderer::gl::apply_opengl_capability( + 0x0BE2U, + true, + pp::renderer::gl::OpenGlCapabilityDispatch { + .enable = record_enable, + }); + + PP_EXPECT(h, !status.ok()); + PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument); +} + +void applies_buffer_clear_dispatch(pp::tests::Harness& h) +{ + recorded_clear_calls.clear(); + + const auto status = pp::renderer::gl::clear_opengl_buffers( + 0x00000100U, + pp::renderer::gl::OpenGlBufferClearDispatch { + .clear = record_clear, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_clear_calls.size() == 1U); + PP_EXPECT(h, recorded_clear_calls[0].mask == 0x00000100U); +} + +void rejects_incomplete_buffer_clear_dispatch(pp::tests::Harness& h) +{ + const auto status = pp::renderer::gl::clear_opengl_buffers( + 0x00000100U, + pp::renderer::gl::OpenGlBufferClearDispatch {}); + + PP_EXPECT(h, !status.ok()); + PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument); +} + void maps_renderer_viewports_and_scissors(pp::tests::Harness& h) { const auto viewport = pp::renderer::gl::viewport_for_renderer_viewport( @@ -1377,6 +1443,10 @@ int main() harness.run("rejects_incomplete_scissor_dispatch", rejects_incomplete_scissor_dispatch); harness.run("applies_scissor_test_dispatch", applies_scissor_test_dispatch); harness.run("rejects_incomplete_scissor_test_dispatch", rejects_incomplete_scissor_test_dispatch); + harness.run("applies_generic_capability_dispatch", applies_generic_capability_dispatch); + harness.run("rejects_incomplete_generic_capability_dispatch", rejects_incomplete_generic_capability_dispatch); + harness.run("applies_buffer_clear_dispatch", applies_buffer_clear_dispatch); + harness.run("rejects_incomplete_buffer_clear_dispatch", rejects_incomplete_buffer_clear_dispatch); harness.run("maps_renderer_viewports_and_scissors", maps_renderer_viewports_and_scissors); harness.run("maps_renderer_blend_state_tokens", maps_renderer_blend_state_tokens); harness.run("maps_renderer_color_write_masks", maps_renderer_color_write_masks);