Route VR render state through renderer GL

This commit is contained in:
2026-06-03 06:03:28 +02:00
parent 7dcf76c3aa
commit 3e15b2f46c
6 changed files with 162 additions and 13 deletions

View File

@@ -464,8 +464,9 @@ Known local toolchain state:
version/vendor/renderer/GLSL string query dispatch, tested default clear version/vendor/renderer/GLSL string query dispatch, tested default clear
color/buffer dispatch consumed by `App::clear`, tested app UI color/buffer dispatch consumed by `App::clear`, tested app UI
viewport/scissor dispatch consumed by `App::draw` and `App::vr_draw_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 tested generic capability/buffer-clear dispatch consumed by VR draw state
by the OpenGL parity work. 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, - `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability,
new-document warning, publish prompt, and save-before-upload planning as JSON; new-document warning, publish prompt, and save-before-upload planning as JSON;
the live cloud upload command consumes the same start contract before the live cloud upload command consumes the same start contract before

View File

@@ -531,6 +531,10 @@ the live OpenGL call sequence.
VR UI framebuffer viewport and scissor-test setup now also consumes those VR UI framebuffer viewport and scissor-test setup now also consumes those
`pp_renderer_gl` contracts, keeping desktop and VR UI rendering aligned while `pp_renderer_gl` contracts, keeping desktop and VR UI rendering aligned while
the retained OpenVR app path is split incrementally. 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 Windows RenderDoc frame capture hooks now also dispatch through
`PlatformServices`, keeping capture integration in the platform service while `PlatformServices`, keeping capture integration in the platform service while
leaving non-Windows adapters as no-ops. leaving non-Windows adapters as no-ops.

View File

@@ -39,6 +39,11 @@ void set_opengl_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std
glViewport(static_cast<GLint>(x), static_cast<GLint>(y), static_cast<GLsizei>(width), static_cast<GLsizei>(height)); glViewport(static_cast<GLint>(x), static_cast<GLint>(y), static_cast<GLsizei>(width), static_cast<GLsizei>(height));
} }
void clear_opengl_mask(std::uint32_t mask) noexcept
{
glClear(static_cast<GLbitfield>(mask));
}
void apply_vr_ui_viewport(pp::renderer::gl::OpenGlViewportRect viewport) void apply_vr_ui_viewport(pp::renderer::gl::OpenGlViewportRect viewport)
{ {
const auto status = pp::renderer::gl::apply_opengl_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); 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; 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 blend = glIsEnabled(pp::renderer::gl::blend_state());
auto depth = glIsEnabled(pp::renderer::gl::depth_test_state()); auto depth = glIsEnabled(pp::renderer::gl::depth_test_state());
glDisable(pp::renderer::gl::blend_state()); apply_vr_render_capability(pp::renderer::gl::blend_state(), false);
glDisable(pp::renderer::gl::depth_test_state()); apply_vr_render_capability(pp::renderer::gl::depth_test_state(), false);
glClear(pp::renderer::gl::framebuffer_depth_buffer_mask()); clear_vr_depth_buffer();
for (int plane_index = 0; plane_index < 6; plane_index++) 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(); m_face_plane.draw_fill();
} }
glEnable(pp::renderer::gl::blend_state()); apply_vr_render_capability(pp::renderer::gl::blend_state(), true);
glEnable(pp::renderer::gl::depth_test_state()); apply_vr_render_capability(pp::renderer::gl::depth_test_state(), true);
glClear(pp::renderer::gl::framebuffer_depth_buffer_mask()); clear_vr_depth_buffer();
for (size_t i = 0; i < canvas->m_canvas->m_layers.size(); i++) 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(); 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 // draw the brush
/* /*
auto mode = dynamic_cast<CanvasModePen*>(canvas->m_canvas->modes[(int)canvas->m_canvas->m_current_mode][0]); auto mode = dynamic_cast<CanvasModePen*>(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::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)) 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); set_active_texture_unit(0);
auto& tex = *canvas->m_canvas->m_current_brush->m_tip_texture; auto& tex = *canvas->m_canvas->m_current_brush->m_tip_texture;
tex.bind(); 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::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)) 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); set_active_texture_unit(0);
auto& tex = *canvas->m_canvas->m_current_brush->m_tip_texture; auto& tex = *canvas->m_canvas->m_current_brush->m_tip_texture;
tex.bind(); 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); mode->on_Draw(ortho_proj, proj, camera);
*/ */
blend ? glEnable(pp::renderer::gl::blend_state()) : glDisable(pp::renderer::gl::blend_state()); apply_vr_render_capability(pp::renderer::gl::blend_state(), blend != 0U);
depth ? glEnable(pp::renderer::gl::depth_test_state()) : glDisable(pp::renderer::gl::depth_test_state()); apply_vr_render_capability(pp::renderer::gl::depth_test_state(), depth != 0U);
sampler.unbind(); sampler.unbind();
} }

View File

@@ -326,6 +326,35 @@ pp::foundation::Status apply_opengl_scissor_test(
return pp::foundation::Status::success(); 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 std::uint32_t extension_count_query() noexcept
{ {
return gl_num_extensions; return gl_num_extensions;

View File

@@ -178,6 +178,15 @@ struct OpenGlScissorTestDispatch {
OpenGlCapabilityFn disable = nullptr; OpenGlCapabilityFn disable = nullptr;
}; };
struct OpenGlCapabilityDispatch {
OpenGlCapabilityFn enable = nullptr;
OpenGlCapabilityFn disable = nullptr;
};
struct OpenGlBufferClearDispatch {
OpenGlClearFn clear = nullptr;
};
[[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities(
std::span<const std::string_view> extensions, std::span<const std::string_view> extensions,
OpenGlRuntime runtime) noexcept; OpenGlRuntime runtime) noexcept;
@@ -198,6 +207,13 @@ struct OpenGlScissorTestDispatch {
[[nodiscard]] pp::foundation::Status apply_opengl_scissor_test( [[nodiscard]] pp::foundation::Status apply_opengl_scissor_test(
bool enabled, bool enabled,
OpenGlScissorTestDispatch dispatch) noexcept; 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_count_query() noexcept;
[[nodiscard]] std::uint32_t extension_string_name() noexcept; [[nodiscard]] std::uint32_t extension_string_name() noexcept;

View File

@@ -991,6 +991,72 @@ void rejects_incomplete_scissor_test_dispatch(pp::tests::Harness& h)
PP_EXPECT(h, status.code == pp::foundation::StatusCode::invalid_argument); 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) void maps_renderer_viewports_and_scissors(pp::tests::Harness& h)
{ {
const auto viewport = pp::renderer::gl::viewport_for_renderer_viewport( 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("rejects_incomplete_scissor_dispatch", rejects_incomplete_scissor_dispatch);
harness.run("applies_scissor_test_dispatch", applies_scissor_test_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("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_viewports_and_scissors", maps_renderer_viewports_and_scissors);
harness.run("maps_renderer_blend_state_tokens", maps_renderer_blend_state_tokens); harness.run("maps_renderer_blend_state_tokens", maps_renderer_blend_state_tokens);
harness.run("maps_renderer_color_write_masks", maps_renderer_color_write_masks); harness.run("maps_renderer_color_write_masks", maps_renderer_color_write_masks);