Move OpenGL extension query into renderer backend

This commit is contained in:
2026-06-04 19:39:06 +02:00
parent 11c7d87330
commit 1057dd488a
6 changed files with 159 additions and 21 deletions

View File

@@ -791,7 +791,9 @@ Known warnings after the current CMake app build:
`pp_renderer_gl` now owns OpenGL runtime build-target classification through `pp_renderer_gl` now owns OpenGL runtime build-target classification through
CMake target compile definitions and `opengl_runtime_for_current_build()`, CMake target compile definitions and `opengl_runtime_for_current_build()`,
so app shader startup asks the backend for desktop GL/GLES/WebGL policy 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 - `pp_legacy_ui_core` is an object-library containment boundary because the
retained base `Node` controls still depend on legacy renderer and app retained base `Node` controls still depend on legacy renderer and app
headers. It should shrink as layout parsing, colors, generic controls, and headers. It should shrink as layout parsing, colors, generic controls, and

View File

@@ -676,6 +676,10 @@ OpenGL runtime build-target classification now lives in `pp_renderer_gl`
through CMake-owned compile definitions and through CMake-owned compile definitions and
`opengl_runtime_for_current_build()`, so `app_shaders.cpp` no longer decides `opengl_runtime_for_current_build()`, so `app_shaders.cpp` no longer decides
desktop GL/GLES/WebGL capability policy with local platform branches. 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, Prepared-file save/download handoff is now also part of the service contract,
so iOS/Web export completion routes through `PlatformServices` after the app so iOS/Web export completion routes through `PlatformServices` after the app
writes the temporary/exported payload. writes the temporary/exported payload.

View File

@@ -6,16 +6,6 @@
namespace { namespace {
[[nodiscard]] GLenum extension_count_query() noexcept
{
return static_cast<GLenum>(pp::renderer::gl::extension_count_query());
}
[[nodiscard]] GLenum extension_string_name() noexcept
{
return static_cast<GLenum>(pp::renderer::gl::extension_string_name());
}
[[nodiscard]] pp::renderer::gl::OpenGlCapabilities shader_manager_capabilities() noexcept [[nodiscard]] pp::renderer::gl::OpenGlCapabilities shader_manager_capabilities() noexcept
{ {
pp::renderer::gl::OpenGlCapabilities capabilities; pp::renderer::gl::OpenGlCapabilities capabilities;
@@ -27,6 +17,19 @@ namespace {
return capabilities; return capabilities;
} }
void query_gl_integer(std::uint32_t name, std::int32_t* value) noexcept
{
GLint queried_value = 0;
glGetIntegerv(static_cast<GLenum>(name), &queried_value);
*value = static_cast<std::int32_t>(queried_value);
}
const char* query_gl_string_indexed(std::uint32_t name, std::uint32_t index) noexcept
{
return reinterpret_cast<const char*>(
glGetStringi(static_cast<GLenum>(name), static_cast<GLuint>(index)));
}
} }
void App::initShaders() void App::initShaders()
@@ -37,17 +40,22 @@ void App::initShaders()
#endif // _DEBUG #endif // _DEBUG
render_task([] { render_task([] {
GLint n_exts; const auto extensions_result = pp::renderer::gl::query_opengl_extensions(
glGetIntegerv(extension_count_query(), &n_exts); pp::renderer::gl::OpenGlExtensionQueryDispatch {
std::vector<std::string> extension_storage; .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<std::string_view> extension_views; std::vector<std::string_view> extension_views;
extension_storage.reserve(n_exts); extension_views.reserve(extension_storage.size());
extension_views.reserve(n_exts); for (const auto& extension : extension_storage) {
for (int i = 0; i < n_exts; i++) extension_views.push_back(extension);
{ LOG("EXT: %s", extension.c_str());
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());
} }
const auto runtime = pp::renderer::gl::opengl_runtime_for_current_build(); const auto runtime = pp::renderer::gl::opengl_runtime_for_current_build();

View File

@@ -344,6 +344,31 @@ pp::foundation::Result<OpenGlRuntimeInfo> query_opengl_runtime_info(
}); });
} }
pp::foundation::Result<std::vector<std::string>> query_opengl_extensions(
OpenGlExtensionQueryDispatch dispatch)
{
if (dispatch.get_integer == nullptr || dispatch.get_string_indexed == nullptr) {
return pp::foundation::Result<std::vector<std::string>>::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<std::vector<std::string>>::success({});
}
std::vector<std::string> extensions;
extensions.reserve(static_cast<std::size_t>(extension_count));
for (std::int32_t index = 0; index < extension_count; ++index) {
const char* extension = dispatch.get_string_indexed(
extension_string_name(),
static_cast<std::uint32_t>(index));
extensions.emplace_back(extension != nullptr ? extension : "");
}
return pp::foundation::Result<std::vector<std::string>>::success(std::move(extensions));
}
OpenGlDefaultClear panopainter_default_clear() noexcept OpenGlDefaultClear panopainter_default_clear() noexcept
{ {
return OpenGlDefaultClear { return OpenGlDefaultClear {

View File

@@ -5,7 +5,9 @@
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include <span> #include <span>
#include <string>
#include <string_view> #include <string_view>
#include <vector>
namespace pp::renderer::gl { namespace pp::renderer::gl {
@@ -459,11 +461,17 @@ struct OpenGlRuntimeInfo {
}; };
using OpenGlStringQueryFn = const char* (*)(std::uint32_t name) noexcept; using OpenGlStringQueryFn = const char* (*)(std::uint32_t name) noexcept;
using OpenGlIndexedStringQueryFn = const char* (*)(std::uint32_t name, std::uint32_t index) noexcept;
struct OpenGlRuntimeInfoDispatch { struct OpenGlRuntimeInfoDispatch {
OpenGlStringQueryFn get_string = nullptr; OpenGlStringQueryFn get_string = nullptr;
}; };
struct OpenGlExtensionQueryDispatch {
OpenGlGetIntegerFn get_integer = nullptr;
OpenGlIndexedStringQueryFn get_string_indexed = nullptr;
};
struct OpenGlDefaultClear { struct OpenGlDefaultClear {
std::array<float, 4> color {}; std::array<float, 4> color {};
std::uint32_t mask = 0; std::uint32_t mask = 0;
@@ -690,6 +698,8 @@ struct OpenGlMeshDeleteDispatch {
OpenGlStateRestoreDispatch dispatch) noexcept; OpenGlStateRestoreDispatch dispatch) noexcept;
[[nodiscard]] pp::foundation::Result<OpenGlRuntimeInfo> query_opengl_runtime_info( [[nodiscard]] pp::foundation::Result<OpenGlRuntimeInfo> query_opengl_runtime_info(
OpenGlRuntimeInfoDispatch dispatch) noexcept; OpenGlRuntimeInfoDispatch dispatch) noexcept;
[[nodiscard]] pp::foundation::Result<std::vector<std::string>> query_opengl_extensions(
OpenGlExtensionQueryDispatch dispatch);
[[nodiscard]] OpenGlDefaultClear panopainter_default_clear() noexcept; [[nodiscard]] OpenGlDefaultClear panopainter_default_clear() noexcept;
[[nodiscard]] pp::foundation::Status clear_panopainter_default_target(OpenGlClearDispatch dispatch) noexcept; [[nodiscard]] pp::foundation::Status clear_panopainter_default_target(OpenGlClearDispatch dispatch) noexcept;
[[nodiscard]] pp::foundation::Status apply_opengl_viewport( [[nodiscard]] pp::foundation::Status apply_opengl_viewport(

View File

@@ -167,6 +167,7 @@ struct RecordedOpenGlMeshDrawCall {
std::vector<RecordedOpenGlStateCall> recorded_state_calls; std::vector<RecordedOpenGlStateCall> recorded_state_calls;
std::vector<std::uint32_t> recorded_string_queries; std::vector<std::uint32_t> recorded_string_queries;
std::vector<std::uint32_t> recorded_indexed_string_queries;
std::vector<pp::renderer::gl::OpenGlDefaultClear> recorded_clear_calls; std::vector<pp::renderer::gl::OpenGlDefaultClear> recorded_clear_calls;
std::vector<pp::renderer::gl::OpenGlViewportRect> recorded_viewport_calls; std::vector<pp::renderer::gl::OpenGlViewportRect> recorded_viewport_calls;
std::vector<pp::renderer::gl::OpenGlScissorRect> recorded_scissor_calls; std::vector<pp::renderer::gl::OpenGlScissorRect> recorded_scissor_calls;
@@ -188,6 +189,7 @@ std::vector<std::uint32_t> recorded_generated_sampler_counts;
std::vector<RecordedOpenGlSamplerParameterCall> recorded_sampler_parameter_calls; std::vector<RecordedOpenGlSamplerParameterCall> recorded_sampler_parameter_calls;
std::vector<RecordedOpenGlSamplerBorderCall> recorded_sampler_border_calls; std::vector<RecordedOpenGlSamplerBorderCall> recorded_sampler_border_calls;
std::vector<std::uint32_t> recorded_deleted_programs; std::vector<std::uint32_t> recorded_deleted_programs;
std::int32_t recorded_extension_count = 3;
std::vector<RecordedOpenGlUniformFloatVectorCall> recorded_uniform_vector_calls; std::vector<RecordedOpenGlUniformFloatVectorCall> recorded_uniform_vector_calls;
std::vector<RecordedOpenGlUniformScalarCall> recorded_uniform_scalar_calls; std::vector<RecordedOpenGlUniformScalarCall> recorded_uniform_scalar_calls;
std::vector<std::uint32_t> recorded_attrib_location_programs; std::vector<std::uint32_t> 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 void record_clear_color(float r, float g, float b, float a) noexcept
{ {
recorded_clear_calls.push_back(pp::renderer::gl::OpenGlDefaultClear { 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); recorded_integer_queries.push_back(name);
switch (name) { switch (name) {
case 0x821DU:
*value = recorded_extension_count;
break;
case 0x0BA2U: case 0x0BA2U:
value[0] = 2; value[0] = 2;
value[1] = 4; 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); 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) void clears_app_default_target(pp::tests::Harness& h)
{ {
recorded_clear_calls.clear(); 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("rejects_incomplete_gl_state_restore_dispatch", rejects_incomplete_gl_state_restore_dispatch);
harness.run("queries_app_runtime_info", queries_app_runtime_info); 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("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("clears_app_default_target", clears_app_default_target);
harness.run("rejects_incomplete_app_clear_dispatch", rejects_incomplete_app_clear_dispatch); harness.run("rejects_incomplete_app_clear_dispatch", rejects_incomplete_app_clear_dispatch);
harness.run("applies_viewport_dispatch", applies_viewport_dispatch); harness.run("applies_viewport_dispatch", applies_viewport_dispatch);