diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 63c35e2..87cd010 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -129,7 +129,8 @@ Known local toolchain state: GLES float/half-float extensions, WebGL exclusion behavior, and the 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)`, and + invalid channel counts rejected by `Texture2D::create(Image)`, RGBA8/RGBA32F + readback formats and byte-count math used by `RTT` and `PBO` readbacks, and framebuffer status naming used by `RTT` diagnostics. It also owns and validates framebuffer blit color mask and linear/nearest filters used by `RTT::resize` and `RTT::copy`, plus the default linear clamp-to-edge diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 7ce5b5f..7d8fade 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -390,9 +390,11 @@ 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, plus image channel-count to texture format mapping for `Texture2D` image uploads and framebuffer status naming for -`RTT` diagnostics. The framebuffer blit color mask and linear/nearest filter -tokens used by `RTT::resize` and `RTT::copy`, plus the default render-target -texture parameters used by `RTT::create`, also live in `pp_renderer_gl`. +`RTT` diagnostics. RGBA8/RGBA32F readback formats and byte-count math used by +`RTT` and `PBO` readbacks now live in `pp_renderer_gl`. The framebuffer blit +color mask and linear/nearest filter tokens used by `RTT::resize` and +`RTT::copy`, plus the default render-target texture parameters used by +`RTT::create`, also live in `pp_renderer_gl`. Mesh index-type and primitive-mode decisions used by legacy `Shape` drawing and the PanoPainter cube-face to OpenGL texture-target mapping used by `TextureCube` also live in @@ -646,8 +648,9 @@ Results: alignment, desktop GL core float support, GLES float/half-float extensions, WebGL exclusion behavior, upload types for RGBA8/RGBA16F/RGBA32F internal formats, image channel-count format mapping including invalid counts, and - framebuffer status names, framebuffer blit color mask and linear/nearest - filters, plus Shape index-type and fill/stroke primitive mode mapping, + RGBA8/RGBA32F readback format and byte-count mapping, framebuffer status + names, framebuffer blit color mask and linear/nearest filters, plus Shape + index-type and fill/stroke primitive mode mapping, PanoPainter cube-face texture-target order, and the linear clamp-to-edge render-target texture parameter set used by `RTT::create`. Sampler parameter validation covers wrap S/T/R plus min/mag filter ordering diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 5f9bfed..89756f5 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -123,6 +123,32 @@ OpenGlPixelFormat texture_format_for_channel_count(std::uint32_t channel_count) } } +OpenGlReadbackFormat rgba8_readback_format() noexcept +{ + return OpenGlReadbackFormat { + .pixel_format = gl_rgba, + .component_type = gl_unsigned_byte, + .bytes_per_pixel = 4U, + }; +} + +OpenGlReadbackFormat rgba32f_readback_format() noexcept +{ + return OpenGlReadbackFormat { + .pixel_format = gl_rgba, + .component_type = gl_float, + .bytes_per_pixel = 16U, + }; +} + +std::uint64_t readback_byte_count( + OpenGlReadbackFormat format, + std::uint32_t width, + std::uint32_t height) noexcept +{ + return static_cast(width) * height * format.bytes_per_pixel; +} + const char* framebuffer_status_name(std::uint32_t status) noexcept { switch (status) { diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index e750b04..be4cc65 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -32,12 +32,24 @@ struct OpenGlTextureParameter { std::uint32_t value = 0; }; +struct OpenGlReadbackFormat { + std::uint32_t pixel_format = 0; + std::uint32_t component_type = 0; + std::uint32_t bytes_per_pixel = 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; +[[nodiscard]] OpenGlReadbackFormat rgba8_readback_format() noexcept; +[[nodiscard]] OpenGlReadbackFormat rgba32f_readback_format() noexcept; +[[nodiscard]] std::uint64_t readback_byte_count( + OpenGlReadbackFormat format, + std::uint32_t width, + std::uint32_t height) noexcept; [[nodiscard]] const char* framebuffer_status_name(std::uint32_t status) noexcept; [[nodiscard]] std::uint32_t framebuffer_color_buffer_mask() noexcept; [[nodiscard]] std::uint32_t framebuffer_blit_filter(bool linear) noexcept; diff --git a/src/rtt.cpp b/src/rtt.cpp index 2b02749..874a025 100644 --- a/src/rtt.cpp +++ b/src/rtt.cpp @@ -358,10 +358,18 @@ uint8_t* RTT::readTextureData(uint8_t* buffer) const noexcept buffer = createBuffer(); App::I->render_task([&] { + const auto readback = pp::renderer::gl::rgba8_readback_format(); GLint old; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old); glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID); - glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + glReadPixels( + 0, + 0, + w, + h, + static_cast(readback.pixel_format), + static_cast(readback.component_type), + buffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, old); }); return buffer; @@ -375,10 +383,18 @@ float* RTT::readTextureDataFloat(float* buffer) const noexcept buffer = createBufferFloat(); App::I->render_task([&] { + const auto readback = pp::renderer::gl::rgba32f_readback_format(); GLint old; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old); glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID); - glReadPixels(0, 0, w, h, GL_RGBA, GL_FLOAT, buffer); + glReadPixels( + 0, + 0, + w, + h, + static_cast(readback.pixel_format), + static_cast(readback.component_type), + buffer); glBindFramebuffer(GL_READ_FRAMEBUFFER, old); }); return buffer; @@ -386,12 +402,21 @@ float* RTT::readTextureDataFloat(float* buffer) const noexcept uint8_t* RTT::createBuffer() const noexcept { - return new uint8_t[w * h * 4]; + const auto readback = pp::renderer::gl::rgba8_readback_format(); + return new uint8_t[pp::renderer::gl::readback_byte_count( + readback, + static_cast(w), + static_cast(h))]; } float * RTT::createBufferFloat() const noexcept { - return new float[w * h * 4]; + const auto readback = pp::renderer::gl::rgba32f_readback_format(); + return new float[pp::renderer::gl::readback_byte_count( + readback, + static_cast(w), + static_cast(h)) + / sizeof(float)]; } void RTT::bindTexture() @@ -467,11 +492,26 @@ bool PBO::create(RTT& rtt) noexcept App::I->render_task([this, &rtt] { width = rtt.getWidth(); height = rtt.getHeight(); + const auto readback = pp::renderer::gl::rgba8_readback_format(); rtt.bindFramebuffer(); glGenBuffers(1, &buffer_id); glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_id); - glBufferData(GL_PIXEL_PACK_BUFFER, width * height * 4, 0, GL_STREAM_READ); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glBufferData( + GL_PIXEL_PACK_BUFFER, + static_cast(pp::renderer::gl::readback_byte_count( + readback, + static_cast(width), + static_cast(height))), + 0, + GL_STREAM_READ); + glReadPixels( + 0, + 0, + width, + height, + static_cast(readback.pixel_format), + static_cast(readback.component_type), + 0); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); rtt.unbindFramebuffer(); }); @@ -513,9 +553,14 @@ void PBO::unbind() noexcept glm::uint8_t* PBO::map() noexcept { App::I->render_task([this] { + const auto readback = pp::renderer::gl::rgba8_readback_format(); glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_id); mapped_ptr = (GLubyte*)glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, - width * height * 4, GL_MAP_READ_BIT); + static_cast(pp::renderer::gl::readback_byte_count( + readback, + static_cast(width), + static_cast(height))), + GL_MAP_READ_BIT); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); }); return mapped_ptr; diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 0549e10..99f0ceb 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -116,6 +116,22 @@ void maps_image_channel_count_to_texture_format(pp::tests::Harness& h) PP_EXPECT(h, invalid.pixel_format == 0U); } +void maps_readback_formats(pp::tests::Harness& h) +{ + const auto rgba8 = pp::renderer::gl::rgba8_readback_format(); + const auto rgba32f = pp::renderer::gl::rgba32f_readback_format(); + + PP_EXPECT(h, rgba8.pixel_format == 0x1908U); + PP_EXPECT(h, rgba8.component_type == 0x1401U); + PP_EXPECT(h, rgba8.bytes_per_pixel == 4U); + PP_EXPECT(h, pp::renderer::gl::readback_byte_count(rgba8, 3U, 5U) == 60U); + + PP_EXPECT(h, rgba32f.pixel_format == 0x1908U); + PP_EXPECT(h, rgba32f.component_type == 0x1406U); + PP_EXPECT(h, rgba32f.bytes_per_pixel == 16U); + PP_EXPECT(h, pp::renderer::gl::readback_byte_count(rgba32f, 3U, 5U) == 240U); +} + void names_framebuffer_status_codes(pp::tests::Harness& h) { PP_EXPECT(h, pp::renderer::gl::framebuffer_status_name(0x8CD5U) == std::string_view("GL_FRAMEBUFFER_COMPLETE")); @@ -322,6 +338,7 @@ int main() 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); + harness.run("maps_readback_formats", maps_readback_formats); harness.run("names_framebuffer_status_codes", names_framebuffer_status_codes); harness.run("maps_framebuffer_blit_parameters", maps_framebuffer_blit_parameters); harness.run("maps_shape_index_and_primitive_modes", maps_shape_index_and_primitive_modes);