From 2a030318b1bfd7b016e92239df447175aafd53b7 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 3 Jun 2026 05:50:36 +0200 Subject: [PATCH] Route default clear through renderer GL --- docs/modernization/build-inventory.md | 6 +-- docs/modernization/roadmap.md | 3 ++ src/app.cpp | 24 ++++++++--- src/renderer_gl/opengl_capabilities.cpp | 20 +++++++++ src/renderer_gl/opengl_capabilities.h | 15 +++++++ tests/renderer_gl/capabilities_tests.cpp | 55 ++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 10 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index bb4fe09..98a3f78 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -461,9 +461,9 @@ Known local toolchain state: `src/platform_legacy/legacy_platform_services.*`. - `pp_renderer_gl` owns the tested `OpenGlInitialState` startup depth/blend policy and dispatch application consumed by `App::init`, tested runtime - version/vendor/renderer/GLSL string query dispatch, plus renderer API to - OpenGL token mapping and command-planning contracts used by the OpenGL parity - work. + version/vendor/renderer/GLSL string query dispatch, tested default clear + color/buffer dispatch consumed by `App::clear`, 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 a9cc301..1c860b2 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -521,6 +521,9 @@ operation order. OpenGL runtime version/vendor/renderer/GLSL string queries now also use a tested `pp_renderer_gl` dispatch contract, leaving `App::init` to log the result while the backend owns the query set and order. +The default app clear color and color-buffer clear operation now dispatch +through `pp_renderer_gl` as well, moving another direct OpenGL operation out +of `App::clear` while preserving the current gray clear 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.cpp b/src/app.cpp index 7c9b184..adc3ac4 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -55,6 +55,16 @@ void set_opengl_blend_equation_separate(std::uint32_t color_equation, std::uint3 glBlendEquationSeparate(static_cast(color_equation), static_cast(alpha_equation)); } +void clear_opengl_color(float r, float g, float b, float a) noexcept +{ + glClearColor(r, g, b, a); +} + +void clear_opengl_buffers(std::uint32_t mask) noexcept +{ + glClear(static_cast(mask)); +} + [[nodiscard]] GLint rgba8_internal_format() noexcept { return static_cast(pp::renderer::gl::rgba8_internal_format()); @@ -85,11 +95,6 @@ void set_opengl_blend_equation_separate(std::uint32_t color_equation, std::uint3 return static_cast(pp::renderer::gl::default_framebuffer_id()); } -[[nodiscard]] GLbitfield framebuffer_color_buffer_mask() noexcept -{ - return static_cast(pp::renderer::gl::framebuffer_color_buffer_mask()); -} - } std::thread App::render_thread; std::thread::id App::render_thread_id; @@ -208,8 +213,13 @@ bool App::request_close() void App::clear() { - glClearColor(.1f, .1f, .1f, 1.f); - glClear(framebuffer_color_buffer_mask()); + const auto status = pp::renderer::gl::clear_panopainter_default_target( + pp::renderer::gl::OpenGlClearDispatch { + .clear_color = clear_opengl_color, + .clear = clear_opengl_buffers, + }); + if (!status.ok()) + LOG("OpenGL clear failed: %s", status.message); } void App::initAssets() diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 008caf6..894bcd3 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -261,6 +261,26 @@ pp::foundation::Result query_opengl_runtime_info( }); } +OpenGlDefaultClear panopainter_default_clear() noexcept +{ + return OpenGlDefaultClear { + .color = { 0.1F, 0.1F, 0.1F, 1.0F }, + .mask = framebuffer_color_buffer_mask(), + }; +} + +pp::foundation::Status clear_panopainter_default_target(OpenGlClearDispatch dispatch) noexcept +{ + if (dispatch.clear_color == nullptr || dispatch.clear == nullptr) { + return pp::foundation::Status::invalid_argument("OpenGL clear dispatch callbacks must not be null"); + } + + const auto clear = panopainter_default_clear(); + dispatch.clear_color(clear.color[0], clear.color[1], clear.color[2], clear.color[3]); + dispatch.clear(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 06b4759..6da45b2 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -148,6 +148,19 @@ struct OpenGlRuntimeInfoDispatch { OpenGlStringQueryFn get_string = nullptr; }; +struct OpenGlDefaultClear { + std::array color {}; + std::uint32_t mask = 0; +}; + +using OpenGlClearColorFn = void (*)(float r, float g, float b, float a) noexcept; +using OpenGlClearFn = void (*)(std::uint32_t mask) noexcept; + +struct OpenGlClearDispatch { + OpenGlClearColorFn clear_color = nullptr; + OpenGlClearFn clear = nullptr; +}; + [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( std::span extensions, OpenGlRuntime runtime) noexcept; @@ -157,6 +170,8 @@ struct OpenGlRuntimeInfoDispatch { [[nodiscard]] pp::foundation::Status apply_panopainter_initial_state(OpenGlStateDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Result query_opengl_runtime_info( OpenGlRuntimeInfoDispatch dispatch) noexcept; +[[nodiscard]] OpenGlDefaultClear panopainter_default_clear() noexcept; +[[nodiscard]] pp::foundation::Status clear_panopainter_default_target(OpenGlClearDispatch 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 cdfa33e..76d5364 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -26,6 +26,7 @@ struct RecordedOpenGlStateCall { std::vector recorded_state_calls; std::vector recorded_string_queries; +std::vector recorded_clear_calls; void record_enable(std::uint32_t state) noexcept { @@ -78,6 +79,20 @@ const char* record_string_query(std::uint32_t name) noexcept } } +void record_clear_color(float r, float g, float b, float a) noexcept +{ + recorded_clear_calls.push_back(pp::renderer::gl::OpenGlDefaultClear { + .color = { r, g, b, a }, + }); +} + +void record_clear(std::uint32_t mask) noexcept +{ + recorded_clear_calls.push_back(pp::renderer::gl::OpenGlDefaultClear { + .mask = mask, + }); +} + void detects_common_extension_capabilities(pp::tests::Harness& h) { constexpr std::array extensions { @@ -785,6 +800,44 @@ void rejects_incomplete_app_runtime_info_dispatch(pp::tests::Harness& h) PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument); } +void clears_app_default_target(pp::tests::Harness& h) +{ + recorded_clear_calls.clear(); + + const auto clear = pp::renderer::gl::panopainter_default_clear(); + PP_EXPECT(h, clear.color[0] == 0.1F); + PP_EXPECT(h, clear.color[1] == 0.1F); + PP_EXPECT(h, clear.color[2] == 0.1F); + PP_EXPECT(h, clear.color[3] == 1.0F); + PP_EXPECT(h, clear.mask == 0x00004000U); + + const auto status = pp::renderer::gl::clear_panopainter_default_target( + pp::renderer::gl::OpenGlClearDispatch { + .clear_color = record_clear_color, + .clear = record_clear, + }); + + PP_EXPECT(h, status.ok()); + PP_EXPECT(h, recorded_clear_calls.size() == 2U); + PP_EXPECT(h, recorded_clear_calls[0].color[0] == 0.1F); + PP_EXPECT(h, recorded_clear_calls[0].color[1] == 0.1F); + PP_EXPECT(h, recorded_clear_calls[0].color[2] == 0.1F); + PP_EXPECT(h, recorded_clear_calls[0].color[3] == 1.0F); + PP_EXPECT(h, recorded_clear_calls[0].mask == 0U); + PP_EXPECT(h, recorded_clear_calls[1].mask == 0x00004000U); +} + +void rejects_incomplete_app_clear_dispatch(pp::tests::Harness& h) +{ + const auto status = pp::renderer::gl::clear_panopainter_default_target( + pp::renderer::gl::OpenGlClearDispatch { + .clear_color = record_clear_color, + }); + + 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( @@ -1163,6 +1216,8 @@ int main() harness.run("rejects_incomplete_app_initialization_state_dispatch", rejects_incomplete_app_initialization_state_dispatch); harness.run("queries_app_runtime_info", queries_app_runtime_info); harness.run("rejects_incomplete_app_runtime_info_dispatch", rejects_incomplete_app_runtime_info_dispatch); + harness.run("clears_app_default_target", clears_app_default_target); + harness.run("rejects_incomplete_app_clear_dispatch", rejects_incomplete_app_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);