From 1057dd488a12b3abbe6b2efc9a2209c36eb7118e Mon Sep 17 00:00:00 2001 From: omigamedev Date: Thu, 4 Jun 2026 19:39:06 +0200 Subject: [PATCH] Move OpenGL extension query into renderer backend --- docs/modernization/build-inventory.md | 4 +- docs/modernization/roadmap.md | 4 ++ src/app_shaders.cpp | 48 +++++++------ src/renderer_gl/opengl_capabilities.cpp | 25 +++++++ src/renderer_gl/opengl_capabilities.h | 10 +++ tests/renderer_gl/capabilities_tests.cpp | 89 ++++++++++++++++++++++++ 6 files changed, 159 insertions(+), 21 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 7862013..9796783 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -791,7 +791,9 @@ Known warnings after the current CMake app build: `pp_renderer_gl` now owns OpenGL runtime build-target classification through CMake target compile definitions and `opengl_runtime_for_current_build()`, so app shader startup asks the backend for desktop GL/GLES/WebGL policy - instead of carrying local platform branches. + instead of carrying local platform branches. It also owns headless-tested + OpenGL extension enumeration through `query_opengl_extensions`, moving the + extension count/string query loop out of `app_shaders.cpp`. - `pp_legacy_ui_core` is an object-library containment boundary because the retained base `Node` controls still depend on legacy renderer and app headers. It should shrink as layout parsing, colors, generic controls, and diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 5ccf257..61d2d98 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -676,6 +676,10 @@ OpenGL runtime build-target classification now lives in `pp_renderer_gl` through CMake-owned compile definitions and `opengl_runtime_for_current_build()`, so `app_shaders.cpp` no longer decides desktop GL/GLES/WebGL capability policy with local platform branches. +OpenGL extension enumeration now also lives in `pp_renderer_gl` through a +dispatch-tested `query_opengl_extensions` helper; shader startup still logs and +applies the resulting feature flags, but the GL extension query loop is no +longer app-owned. Prepared-file save/download handoff is now also part of the service contract, so iOS/Web export completion routes through `PlatformServices` after the app writes the temporary/exported payload. diff --git a/src/app_shaders.cpp b/src/app_shaders.cpp index 53e603e..949538c 100644 --- a/src/app_shaders.cpp +++ b/src/app_shaders.cpp @@ -6,16 +6,6 @@ namespace { -[[nodiscard]] GLenum extension_count_query() noexcept -{ - return static_cast(pp::renderer::gl::extension_count_query()); -} - -[[nodiscard]] GLenum extension_string_name() noexcept -{ - return static_cast(pp::renderer::gl::extension_string_name()); -} - [[nodiscard]] pp::renderer::gl::OpenGlCapabilities shader_manager_capabilities() noexcept { pp::renderer::gl::OpenGlCapabilities capabilities; @@ -27,6 +17,19 @@ namespace { return capabilities; } +void query_gl_integer(std::uint32_t name, std::int32_t* value) noexcept +{ + GLint queried_value = 0; + glGetIntegerv(static_cast(name), &queried_value); + *value = static_cast(queried_value); +} + +const char* query_gl_string_indexed(std::uint32_t name, std::uint32_t index) noexcept +{ + return reinterpret_cast( + glGetStringi(static_cast(name), static_cast(index))); +} + } void App::initShaders() @@ -37,17 +40,22 @@ void App::initShaders() #endif // _DEBUG render_task([] { - GLint n_exts; - glGetIntegerv(extension_count_query(), &n_exts); - std::vector extension_storage; + const auto extensions_result = pp::renderer::gl::query_opengl_extensions( + pp::renderer::gl::OpenGlExtensionQueryDispatch { + .get_integer = query_gl_integer, + .get_string_indexed = query_gl_string_indexed, + }); + if (!extensions_result.ok()) { + LOG("OpenGL extension query failed: %s", extensions_result.status().message); + return; + } + + const auto& extension_storage = extensions_result.value(); std::vector extension_views; - extension_storage.reserve(n_exts); - extension_views.reserve(n_exts); - for (int i = 0; i < n_exts; i++) - { - extension_storage.emplace_back((const char*)glGetStringi(extension_string_name(), i)); - extension_views.push_back(extension_storage.back()); - LOG("EXT: %s", extension_storage.back().c_str()); + extension_views.reserve(extension_storage.size()); + for (const auto& extension : extension_storage) { + extension_views.push_back(extension); + LOG("EXT: %s", extension.c_str()); } const auto runtime = pp::renderer::gl::opengl_runtime_for_current_build(); diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 5569a74..a04ef7a 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -344,6 +344,31 @@ pp::foundation::Result query_opengl_runtime_info( }); } +pp::foundation::Result> query_opengl_extensions( + OpenGlExtensionQueryDispatch dispatch) +{ + if (dispatch.get_integer == nullptr || dispatch.get_string_indexed == nullptr) { + return pp::foundation::Result>::failure( + pp::foundation::Status::invalid_argument("OpenGL extension query callbacks must not be null")); + } + + std::int32_t extension_count = 0; + dispatch.get_integer(extension_count_query(), &extension_count); + if (extension_count <= 0) { + return pp::foundation::Result>::success({}); + } + + std::vector extensions; + extensions.reserve(static_cast(extension_count)); + for (std::int32_t index = 0; index < extension_count; ++index) { + const char* extension = dispatch.get_string_indexed( + extension_string_name(), + static_cast(index)); + extensions.emplace_back(extension != nullptr ? extension : ""); + } + return pp::foundation::Result>::success(std::move(extensions)); +} + OpenGlDefaultClear panopainter_default_clear() noexcept { return OpenGlDefaultClear { diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index 3f9bcea..9465506 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include namespace pp::renderer::gl { @@ -459,11 +461,17 @@ struct OpenGlRuntimeInfo { }; using OpenGlStringQueryFn = const char* (*)(std::uint32_t name) noexcept; +using OpenGlIndexedStringQueryFn = const char* (*)(std::uint32_t name, std::uint32_t index) noexcept; struct OpenGlRuntimeInfoDispatch { OpenGlStringQueryFn get_string = nullptr; }; +struct OpenGlExtensionQueryDispatch { + OpenGlGetIntegerFn get_integer = nullptr; + OpenGlIndexedStringQueryFn get_string_indexed = nullptr; +}; + struct OpenGlDefaultClear { std::array color {}; std::uint32_t mask = 0; @@ -690,6 +698,8 @@ struct OpenGlMeshDeleteDispatch { OpenGlStateRestoreDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Result query_opengl_runtime_info( OpenGlRuntimeInfoDispatch dispatch) noexcept; +[[nodiscard]] pp::foundation::Result> query_opengl_extensions( + OpenGlExtensionQueryDispatch dispatch); [[nodiscard]] OpenGlDefaultClear panopainter_default_clear() noexcept; [[nodiscard]] pp::foundation::Status clear_panopainter_default_target(OpenGlClearDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Status apply_opengl_viewport( diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 74659d3..f2e890d 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -167,6 +167,7 @@ struct RecordedOpenGlMeshDrawCall { std::vector recorded_state_calls; std::vector recorded_string_queries; +std::vector recorded_indexed_string_queries; std::vector recorded_clear_calls; std::vector recorded_viewport_calls; std::vector recorded_scissor_calls; @@ -188,6 +189,7 @@ std::vector recorded_generated_sampler_counts; std::vector recorded_sampler_parameter_calls; std::vector recorded_sampler_border_calls; std::vector recorded_deleted_programs; +std::int32_t recorded_extension_count = 3; std::vector recorded_uniform_vector_calls; std::vector recorded_uniform_scalar_calls; std::vector recorded_attrib_location_programs; @@ -278,6 +280,25 @@ const char* record_string_query(std::uint32_t name) noexcept } } +const char* record_indexed_string_query(std::uint32_t name, std::uint32_t index) noexcept +{ + recorded_indexed_string_queries.push_back(name); + if (name != 0x1F03U) { + return "unexpected"; + } + + switch (index) { + case 0U: + return "GL_EXT_shader_framebuffer_fetch"; + case 1U: + return "GL_EXT_map_buffer_alignment"; + case 2U: + return "GL_OES_texture_float"; + default: + return nullptr; + } +} + void record_clear_color(float r, float g, float b, float a) noexcept { recorded_clear_calls.push_back(pp::renderer::gl::OpenGlDefaultClear { @@ -331,6 +352,9 @@ void record_get_integer(std::uint32_t name, std::int32_t* value) noexcept { recorded_integer_queries.push_back(name); switch (name) { + case 0x821DU: + *value = recorded_extension_count; + break; case 0x0BA2U: value[0] = 2; value[1] = 4; @@ -1805,6 +1829,67 @@ void rejects_incomplete_app_runtime_info_dispatch(pp::tests::Harness& h) PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument); } +void queries_app_extensions(pp::tests::Harness& h) +{ + recorded_integer_queries.clear(); + recorded_indexed_string_queries.clear(); + recorded_extension_count = 3; + + const auto result = pp::renderer::gl::query_opengl_extensions( + pp::renderer::gl::OpenGlExtensionQueryDispatch { + .get_integer = record_get_integer, + .get_string_indexed = record_indexed_string_query, + }); + + PP_EXPECT(h, result.ok()); + PP_EXPECT(h, recorded_integer_queries.size() == 1U); + PP_EXPECT(h, recorded_integer_queries[0] == 0x821DU); + PP_EXPECT(h, recorded_indexed_string_queries.size() == 3U); + PP_EXPECT(h, result.value().size() == 3U); + PP_EXPECT(h, result.value()[0] == "GL_EXT_shader_framebuffer_fetch"); + PP_EXPECT(h, result.value()[1] == "GL_EXT_map_buffer_alignment"); + PP_EXPECT(h, result.value()[2] == "GL_OES_texture_float"); +} + +void converts_null_extension_names_to_empty_strings(pp::tests::Harness& h) +{ + recorded_extension_count = 4; + + const auto result = pp::renderer::gl::query_opengl_extensions( + pp::renderer::gl::OpenGlExtensionQueryDispatch { + .get_integer = record_get_integer, + .get_string_indexed = record_indexed_string_query, + }); + + PP_EXPECT(h, result.ok()); + PP_EXPECT(h, result.value().size() == 4U); + PP_EXPECT(h, result.value()[3].empty()); +} + +void treats_negative_extension_count_as_empty(pp::tests::Harness& h) +{ + recorded_indexed_string_queries.clear(); + recorded_extension_count = -7; + + const auto result = pp::renderer::gl::query_opengl_extensions( + pp::renderer::gl::OpenGlExtensionQueryDispatch { + .get_integer = record_get_integer, + .get_string_indexed = record_indexed_string_query, + }); + + PP_EXPECT(h, result.ok()); + PP_EXPECT(h, result.value().empty()); + PP_EXPECT(h, recorded_indexed_string_queries.empty()); +} + +void rejects_incomplete_extension_query_dispatch(pp::tests::Harness& h) +{ + const auto result = pp::renderer::gl::query_opengl_extensions(pp::renderer::gl::OpenGlExtensionQueryDispatch {}); + + PP_EXPECT(h, !result.ok()); + PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument); +} + void clears_app_default_target(pp::tests::Harness& h) { recorded_clear_calls.clear(); @@ -4130,6 +4215,10 @@ int main() harness.run("rejects_incomplete_gl_state_restore_dispatch", rejects_incomplete_gl_state_restore_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("queries_app_extensions", queries_app_extensions); + harness.run("converts_null_extension_names_to_empty_strings", converts_null_extension_names_to_empty_strings); + harness.run("treats_negative_extension_count_as_empty", treats_negative_extension_count_as_empty); + harness.run("rejects_incomplete_extension_query_dispatch", rejects_incomplete_extension_query_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("applies_viewport_dispatch", applies_viewport_dispatch);