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
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

View File

@@ -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.

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));
}
void clear_opengl_mask(std::uint32_t mask) noexcept
{
glClear(static_cast<GLbitfield>(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<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::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();
}

View File

@@ -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;

View File

@@ -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<const std::string_view> 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;

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);
}
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);