diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 3a24cff..60b5278 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -127,7 +127,9 @@ Known local toolchain state: 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, WebGL exclusion behavior, and the - upload-type mapping used by legacy `Texture2D` and `RTT` creation. + upload-type mapping used by legacy `Texture2D` and `RTT` creation. It also + validates image channel-count to OpenGL texture format mapping, including + invalid channel counts rejected by `Texture2D::create(Image)`. - `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 f5bee89..9bb3f71 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -388,9 +388,10 @@ 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. It also owns the OpenGL texture upload-type mapping used -by legacy `Texture2D` and `RTT` creation. The legacy app delegates extension -and upload-format interpretation to that backend library, but the existing -renderer classes are not yet fully behind the renderer interfaces. +by legacy `Texture2D` and `RTT` creation, plus image channel-count to texture +format mapping for `Texture2D` image uploads. The legacy app delegates +extension and upload-format interpretation to that backend library, but the +existing renderer classes are not yet fully behind the renderer interfaces. Implementation tasks: @@ -629,8 +630,8 @@ Results: - `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, - WebGL exclusion behavior, and upload types for RGBA8/RGBA16F/RGBA32F - internal formats. + WebGL exclusion behavior, upload types for RGBA8/RGBA16F/RGBA32F internal + formats, and image channel-count format mapping including invalid counts. - 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/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 6f0bbb8..aefe7a5 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -7,6 +7,14 @@ namespace { constexpr std::uint32_t gl_unsigned_byte = 0x1401U; constexpr std::uint32_t gl_float = 0x1406U; constexpr std::uint32_t gl_half_float = 0x140BU; +constexpr std::uint32_t gl_red = 0x1903U; +constexpr std::uint32_t gl_rgb = 0x1907U; +constexpr std::uint32_t gl_rgba = 0x1908U; +constexpr std::uint32_t gl_rg = 0x8227U; +constexpr std::uint32_t gl_r8 = 0x8229U; +constexpr std::uint32_t gl_rg8 = 0x822BU; +constexpr std::uint32_t gl_rgb8 = 0x8051U; +constexpr std::uint32_t gl_rgba8 = 0x8058U; constexpr std::uint32_t gl_rgba32f = 0x8814U; constexpr std::uint32_t gl_rgba16f = 0x881AU; @@ -68,4 +76,20 @@ std::uint32_t texture_upload_type_for_internal_format(std::uint32_t internal_for } } +OpenGlPixelFormat texture_format_for_channel_count(std::uint32_t channel_count) noexcept +{ + switch (channel_count) { + case 1: + return OpenGlPixelFormat { .internal_format = gl_r8, .pixel_format = gl_red, .channel_count = 1 }; + case 2: + return OpenGlPixelFormat { .internal_format = gl_rg8, .pixel_format = gl_rg, .channel_count = 2 }; + case 3: + return OpenGlPixelFormat { .internal_format = gl_rgb8, .pixel_format = gl_rgb, .channel_count = 3 }; + case 4: + return OpenGlPixelFormat { .internal_format = gl_rgba8, .pixel_format = gl_rgba, .channel_count = 4 }; + default: + return OpenGlPixelFormat {}; + } +} + } diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index 4c6114b..1e8b896 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -20,10 +20,17 @@ struct OpenGlCapabilities { bool float16_textures = false; }; +struct OpenGlPixelFormat { + std::uint32_t internal_format = 0; + std::uint32_t pixel_format = 0; + std::uint32_t channel_count = 0; +}; + [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( std::span extensions, OpenGlRuntime runtime) noexcept; [[nodiscard]] std::uint32_t texture_upload_type_for_internal_format(std::uint32_t internal_format) noexcept; +[[nodiscard]] OpenGlPixelFormat texture_format_for_channel_count(std::uint32_t channel_count) noexcept; } diff --git a/src/texture.cpp b/src/texture.cpp index d1ab740..a2df446 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -188,9 +188,16 @@ bool Texture2D::create(int width, int height, GLint internal_format, GLint forma } bool Texture2D::create(const Image& img) { - static GLint formats[] = { GL_RED, GL_RG, GL_RGB, GL_RGBA }; - static GLint iformats[] = { GL_R8, GL_RG8, GL_RGB8, GL_RGBA8 }; - return create(img.width, img.height, iformats[img.comp - 1], formats[img.comp - 1], img.data()); + const auto format = pp::renderer::gl::texture_format_for_channel_count(static_cast(img.comp)); + if (format.channel_count == 0U) + return false; + + return create( + img.width, + img.height, + static_cast(format.internal_format), + static_cast(format.pixel_format), + img.data()); } void Texture2D::create_mipmaps() diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index bd6e40e..58eab31 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -84,6 +84,36 @@ void selects_texture_upload_type_from_internal_format(pp::tests::Harness& h) PP_EXPECT(h, pp::renderer::gl::texture_upload_type_for_internal_format(0U) == gl_unsigned_byte); } +void maps_image_channel_count_to_texture_format(pp::tests::Harness& h) +{ + constexpr std::uint32_t gl_red = 0x1903U; + constexpr std::uint32_t gl_rgb = 0x1907U; + constexpr std::uint32_t gl_rgba = 0x1908U; + constexpr std::uint32_t gl_rg = 0x8227U; + constexpr std::uint32_t gl_r8 = 0x8229U; + constexpr std::uint32_t gl_rg8 = 0x822BU; + constexpr std::uint32_t gl_rgb8 = 0x8051U; + constexpr std::uint32_t gl_rgba8 = 0x8058U; + + const auto r = pp::renderer::gl::texture_format_for_channel_count(1U); + const auto rg = pp::renderer::gl::texture_format_for_channel_count(2U); + const auto rgb = pp::renderer::gl::texture_format_for_channel_count(3U); + const auto rgba = pp::renderer::gl::texture_format_for_channel_count(4U); + const auto invalid = pp::renderer::gl::texture_format_for_channel_count(5U); + + PP_EXPECT(h, r.internal_format == gl_r8); + PP_EXPECT(h, r.pixel_format == gl_red); + PP_EXPECT(h, rg.internal_format == gl_rg8); + PP_EXPECT(h, rg.pixel_format == gl_rg); + PP_EXPECT(h, rgb.internal_format == gl_rgb8); + PP_EXPECT(h, rgb.pixel_format == gl_rgb); + PP_EXPECT(h, rgba.internal_format == gl_rgba8); + PP_EXPECT(h, rgba.pixel_format == gl_rgba); + PP_EXPECT(h, invalid.channel_count == 0U); + PP_EXPECT(h, invalid.internal_format == 0U); + PP_EXPECT(h, invalid.pixel_format == 0U); +} + } int main() @@ -94,5 +124,6 @@ int main() 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); harness.run("selects_texture_upload_type_from_internal_format", selects_texture_upload_type_from_internal_format); + harness.run("maps_image_channel_count_to_texture_format", maps_image_channel_count_to_texture_format); return harness.finish(); }