diff --git a/CMakeLists.txt b/CMakeLists.txt index 85c8e27..dbf34ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,6 +162,20 @@ target_link_libraries(pp_renderer_api PRIVATE pp_project_warnings) +if(PP_ENABLE_OPENGL) + add_library(pp_renderer_gl STATIC + src/renderer_gl/opengl_capabilities.cpp) + target_include_directories(pp_renderer_gl + PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/src") + target_link_libraries(pp_renderer_gl + PUBLIC + pp_renderer_api + pp_project_options + PRIVATE + pp_project_warnings) +endif() + add_library(pp_paint_renderer STATIC src/paint_renderer/compositor.cpp) target_include_directories(pp_paint_renderer @@ -212,6 +226,9 @@ if(PP_BUILD_APP) PRIVATE pp_renderer_api pp_project_warnings) + if(TARGET pp_renderer_gl) + target_link_libraries(pp_legacy_app PRIVATE pp_renderer_gl) + endif() target_include_directories(pp_legacy_app PUBLIC diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 47e24ff..c5e71b5 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -88,13 +88,14 @@ Known local toolchain state: treating `windows-clangcl-asan` as a passing sanitizer gate. - Android arm64 headless configure/build passes through root CMake and the `platform-build` automation wrapper for `pp_foundation`, `pp_assets`, - `pp_paint`, `pp_document`, `pp_renderer_api`, `pp_paint_renderer`, + `pp_paint`, `pp_document`, `pp_renderer_api`, `pp_renderer_gl`, + `pp_paint_renderer`, `pp_ui_core`, `pano_cli`, and their current headless test binaries, including foundation event/logging/task queue coverage, PNG metadata and decode, PPI header/layout, settings document, document snapshot/per-layer-frame/move/duration/face-pixel coverage, paint - brush/stroke/stroke-script coverage, renderer shader descriptor coverage, - UI color parsing, and layout XML parse coverage. + brush/stroke/stroke-script coverage, renderer shader descriptor and OpenGL + capability coverage, UI color parsing, and layout XML parse coverage. - `pano_cli inspect-image` reports PNG IHDR metadata as JSON and is covered by `pano_cli_inspect_png_metadata_smoke` with a tiny IHDR fixture. - `pano_cli inspect-project` reports validated PPI thumbnail/body byte layout, @@ -122,6 +123,10 @@ Known local toolchain state: - `pp_renderer_api` owns the canonical PanoPainter shader catalog consumed by the legacy OpenGL app initialization path; `pp_renderer_api_tests` validates catalog size, key entries, duplicate rejection, and bad path rejection. +- `pp_renderer_gl` owns headless OpenGL runtime capability detection consumed + by the legacy app initialization path; `pp_renderer_gl_capabilities_tests` + validates framebuffer fetch, map-buffer alignment, desktop GL float support, + GLES float/half-float extensions, and WebGL exclusion behavior. - `windows-msvc-vcpkg-headless` validates manifest install/configure/build/test for the current headless component matrix; see DEBT-0007 for remaining app and platform triplet migration. diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 63065d3..be7c5c2 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -384,8 +384,12 @@ Status: started. `pp_renderer_api` exists as a headless renderer-neutral target with texture descriptor, byte-size, viewport, mesh, readback bounds, command context, render device, shader program descriptor, mesh, render target, readback, trace interface validation, and the canonical PanoPainter shader -catalog now consumed by the legacy OpenGL app initialization path. OpenGL -classes are not yet behind these interfaces. +catalog now consumed by the legacy OpenGL app initialization path. +`pp_renderer_gl` now exists as the first OpenGL backend library and owns pure +OpenGL capability detection for framebuffer fetch, map-buffer alignment, and +float texture support. The legacy app delegates extension interpretation to +that backend library, but the existing renderer classes are not yet fully +behind the renderer interfaces. Implementation tasks: @@ -404,6 +408,8 @@ Implementation tasks: - `Sampler` - `ShaderManager` - `Shape` +- Keep OpenGL runtime capability decisions in `pp_renderer_gl` with headless + tests before moving GL object lifetimes behind backend types. - Preserve current shader behavior and asset paths. - Add deterministic GPU tests: - clear @@ -619,6 +625,10 @@ Results: aggregate stroke-script counts/distances. - `panopainter_validate_shaders` passed, validating 25 shader programs and 7 shader includes for stage markers and include graph integrity. +- `pp_renderer_gl_capabilities_tests` passed on default MSVC, vcpkg-headless, + and Android arm64 configure/build, covering framebuffer fetch, map-buffer + alignment, desktop GL core float support, GLES float/half-float extensions, + and WebGL exclusion behavior. - PowerShell analyze automation returns JSON summaries and includes the shader validation target. - `windows-msvc-vcpkg-headless` configured through the Visual Studio bundled diff --git a/scripts/automation/platform-build.ps1 b/scripts/automation/platform-build.ps1 index eb343c4..705e0bc 100644 --- a/scripts/automation/platform-build.ps1 +++ b/scripts/automation/platform-build.ps1 @@ -1,7 +1,7 @@ [CmdletBinding()] param( [string[]]$Presets = @("android-arm64"), - [string[]]$Targets = @("pp_foundation", "pp_assets", "pp_paint", "pp_document", "pp_renderer_api", "pp_paint_renderer", "pp_ui_core", "pano_cli", "pp_foundation_binary_stream_tests", "pp_foundation_event_tests", "pp_foundation_log_tests", "pp_foundation_parse_tests", "pp_foundation_task_queue_tests", "pp_foundation_trace_tests", "pp_assets_image_format_tests", "pp_assets_image_metadata_tests", "pp_assets_image_pixels_tests", "pp_assets_ppi_header_tests", "pp_assets_settings_document_tests", "pp_paint_brush_tests", "pp_paint_blend_tests", "pp_paint_stroke_tests", "pp_paint_stroke_script_tests", "pp_document_tests", "pp_document_ppi_import_tests", "pp_renderer_api_tests", "pp_paint_renderer_compositor_tests", "pp_ui_core_color_tests", "pp_ui_core_layout_value_tests", "pp_ui_core_layout_xml_tests") + [string[]]$Targets = @("pp_foundation", "pp_assets", "pp_paint", "pp_document", "pp_renderer_api", "pp_renderer_gl", "pp_paint_renderer", "pp_ui_core", "pano_cli", "pp_foundation_binary_stream_tests", "pp_foundation_event_tests", "pp_foundation_log_tests", "pp_foundation_parse_tests", "pp_foundation_task_queue_tests", "pp_foundation_trace_tests", "pp_assets_image_format_tests", "pp_assets_image_metadata_tests", "pp_assets_image_pixels_tests", "pp_assets_ppi_header_tests", "pp_assets_settings_document_tests", "pp_paint_brush_tests", "pp_paint_blend_tests", "pp_paint_stroke_tests", "pp_paint_stroke_script_tests", "pp_document_tests", "pp_document_ppi_import_tests", "pp_renderer_api_tests", "pp_renderer_gl_capabilities_tests", "pp_paint_renderer_compositor_tests", "pp_ui_core_color_tests", "pp_ui_core_layout_value_tests", "pp_ui_core_layout_xml_tests") ) $ErrorActionPreference = "Stop" diff --git a/scripts/automation/platform-build.sh b/scripts/automation/platform-build.sh index b367c14..f87f5fc 100644 --- a/scripts/automation/platform-build.sh +++ b/scripts/automation/platform-build.sh @@ -3,7 +3,7 @@ set -u preset="${1:-android-arm64}" shift || true -targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_paint_renderer pp_ui_core pano_cli pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_image_metadata_tests pp_assets_image_pixels_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_brush_tests pp_paint_blend_tests pp_paint_stroke_tests pp_paint_stroke_script_tests pp_document_tests pp_document_ppi_import_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests}" +targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_renderer_gl pp_paint_renderer pp_ui_core pano_cli pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_image_metadata_tests pp_assets_image_pixels_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_brush_tests pp_paint_blend_tests pp_paint_stroke_tests pp_paint_stroke_script_tests pp_document_tests pp_document_ppi_import_tests pp_renderer_api_tests pp_renderer_gl_capabilities_tests pp_paint_renderer_compositor_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests}" start="$(date +%s)" cmake --preset "$preset" diff --git a/src/app_shaders.cpp b/src/app_shaders.cpp index 008dd02..fc35e9a 100644 --- a/src/app_shaders.cpp +++ b/src/app_shaders.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "app.h" #include "renderer_api/shader_catalog.h" +#include "renderer_gl/opengl_capabilities.h" #include "shader.h" void App::initShaders() @@ -13,27 +14,33 @@ void App::initShaders() render_task([] { GLint n_exts; glGetIntegerv(GL_NUM_EXTENSIONS, &n_exts); + std::vector extension_storage; + std::vector extension_views; + extension_storage.reserve(n_exts); + extension_views.reserve(n_exts); for (int i = 0; i < n_exts; i++) { - std::string ext = (const char*)glGetStringi(GL_EXTENSIONS, i); - if (ext.find("shader_framebuffer_fetch") != std::string::npos) - ShaderManager::ext_framebuffer_fetch = true; - if (ext.find("map_buffer_alignment") != std::string::npos) - ShaderManager::ext_map_aligned = true; -#if __GLES__ && !__WEB__ - if (ext.find("texture_float") != std::string::npos) - ShaderManager::ext_float32 = true; - if (ext.find("texture_float_linear") != std::string::npos) - ShaderManager::ext_float32_linear = true; - if (ext.find("color_buffer_float") != std::string::npos) - ShaderManager::ext_float32 = true; - if (ext.find("texture_half_float") != std::string::npos) - ShaderManager::ext_float16 = true; - if (ext.find("color_buffer_half_float") != std::string::npos) - ShaderManager::ext_float16 = true; -#endif - LOG("EXT: %s", ext.c_str()); + extension_storage.emplace_back((const char*)glGetStringi(GL_EXTENSIONS, i)); + extension_views.push_back(extension_storage.back()); + LOG("EXT: %s", extension_storage.back().c_str()); } + + pp::renderer::gl::OpenGlRuntime runtime; +#if __GL__ + runtime.desktop_gl = true; +#endif +#if __GLES__ + runtime.gles = true; +#endif +#if __WEB__ + runtime.web = true; +#endif + const auto capabilities = pp::renderer::gl::detect_opengl_capabilities(extension_views, runtime); + ShaderManager::ext_framebuffer_fetch = capabilities.framebuffer_fetch; + ShaderManager::ext_map_aligned = capabilities.map_buffer_alignment; + ShaderManager::ext_float32 = capabilities.float32_textures; + ShaderManager::ext_float32_linear = capabilities.float32_linear; + ShaderManager::ext_float16 = capabilities.float16_textures; }); #if __GL__ diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp new file mode 100644 index 0000000..43e9b0c --- /dev/null +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -0,0 +1,53 @@ +#include "renderer_gl/opengl_capabilities.h" + +namespace pp::renderer::gl { + +namespace { + +[[nodiscard]] bool contains(std::string_view text, std::string_view needle) noexcept +{ + return text.find(needle) != std::string_view::npos; +} + +} + +OpenGlCapabilities detect_opengl_capabilities( + std::span extensions, + OpenGlRuntime runtime) noexcept +{ + OpenGlCapabilities capabilities; + + if (runtime.desktop_gl) { + capabilities.float32_textures = true; + capabilities.float32_linear = true; + capabilities.float16_textures = true; + } + + for (const auto extension : extensions) { + if (contains(extension, "shader_framebuffer_fetch")) { + capabilities.framebuffer_fetch = true; + } + + if (contains(extension, "map_buffer_alignment")) { + capabilities.map_buffer_alignment = true; + } + + if (runtime.gles && !runtime.web) { + if (contains(extension, "texture_float") || contains(extension, "color_buffer_float")) { + capabilities.float32_textures = true; + } + + if (contains(extension, "texture_float_linear")) { + capabilities.float32_linear = true; + } + + if (contains(extension, "texture_half_float") || contains(extension, "color_buffer_half_float")) { + capabilities.float16_textures = true; + } + } + } + + return capabilities; +} + +} diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h new file mode 100644 index 0000000..d429c51 --- /dev/null +++ b/src/renderer_gl/opengl_capabilities.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace pp::renderer::gl { + +struct OpenGlRuntime { + bool desktop_gl = false; + bool gles = false; + bool web = false; +}; + +struct OpenGlCapabilities { + bool framebuffer_fetch = false; + bool map_buffer_alignment = false; + bool float32_textures = false; + bool float32_linear = false; + bool float16_textures = false; +}; + +[[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( + std::span extensions, + OpenGlRuntime runtime) noexcept; + +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f83dbd3..e3a003f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -186,6 +186,18 @@ add_test(NAME pp_renderer_api_tests COMMAND pp_renderer_api_tests) set_tests_properties(pp_renderer_api_tests PROPERTIES LABELS "renderer;desktop-fast") +if(TARGET pp_renderer_gl) + add_executable(pp_renderer_gl_capabilities_tests + renderer_gl/capabilities_tests.cpp) + target_link_libraries(pp_renderer_gl_capabilities_tests PRIVATE + pp_renderer_gl + pp_test_harness) + + add_test(NAME pp_renderer_gl_capabilities_tests COMMAND pp_renderer_gl_capabilities_tests) + set_tests_properties(pp_renderer_gl_capabilities_tests PROPERTIES + LABELS "renderer;desktop-fast") +endif() + add_executable(pp_paint_renderer_compositor_tests paint_renderer/compositor_tests.cpp) target_link_libraries(pp_paint_renderer_compositor_tests PRIVATE diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp new file mode 100644 index 0000000..635b5a0 --- /dev/null +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -0,0 +1,81 @@ +#include "renderer_gl/opengl_capabilities.h" +#include "test_harness.h" + +#include +#include + +namespace { + +void detects_common_extension_capabilities(pp::tests::Harness& h) +{ + constexpr std::array extensions { + "GL_EXT_shader_framebuffer_fetch", + "GL_ARB_map_buffer_alignment", + }; + + const auto capabilities = pp::renderer::gl::detect_opengl_capabilities( + extensions, + pp::renderer::gl::OpenGlRuntime {}); + + PP_EXPECT(h, capabilities.framebuffer_fetch); + PP_EXPECT(h, capabilities.map_buffer_alignment); + PP_EXPECT(h, !capabilities.float32_textures); + PP_EXPECT(h, !capabilities.float16_textures); +} + +void treats_desktop_gl_float_rendering_as_core(pp::tests::Harness& h) +{ + const auto capabilities = pp::renderer::gl::detect_opengl_capabilities( + {}, + pp::renderer::gl::OpenGlRuntime { .desktop_gl = true }); + + PP_EXPECT(h, capabilities.float32_textures); + PP_EXPECT(h, capabilities.float32_linear); + PP_EXPECT(h, capabilities.float16_textures); +} + +void detects_gles_texture_float_extensions(pp::tests::Harness& h) +{ + constexpr std::array extensions { + "GL_OES_texture_float", + "GL_OES_texture_float_linear", + "GL_EXT_color_buffer_half_float", + }; + + const auto capabilities = pp::renderer::gl::detect_opengl_capabilities( + extensions, + pp::renderer::gl::OpenGlRuntime { .gles = true }); + + PP_EXPECT(h, capabilities.float32_textures); + PP_EXPECT(h, capabilities.float32_linear); + PP_EXPECT(h, capabilities.float16_textures); +} + +void ignores_gles_texture_extensions_for_webgl_runtime(pp::tests::Harness& h) +{ + constexpr std::array extensions { + "GL_OES_texture_float", + "GL_OES_texture_float_linear", + "GL_EXT_color_buffer_half_float", + }; + + const auto capabilities = pp::renderer::gl::detect_opengl_capabilities( + extensions, + pp::renderer::gl::OpenGlRuntime { .gles = true, .web = true }); + + PP_EXPECT(h, !capabilities.float32_textures); + PP_EXPECT(h, !capabilities.float32_linear); + PP_EXPECT(h, !capabilities.float16_textures); +} + +} + +int main() +{ + pp::tests::Harness harness; + harness.run("detects_common_extension_capabilities", detects_common_extension_capabilities); + harness.run("treats_desktop_gl_float_rendering_as_core", treats_desktop_gl_float_rendering_as_core); + harness.run("detects_gles_texture_float_extensions", detects_gles_texture_float_extensions); + harness.run("ignores_gles_texture_extensions_for_webgl_runtime", ignores_gles_texture_extensions_for_webgl_runtime); + return harness.finish(); +}