From 46c37f27952cd57f9ec7bc69f8c35071ed125365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 19 Apr 2025 12:19:03 +0200 Subject: [PATCH] DebugTools: add textureSubImage() overload for GL::Texture2DArray. Wanted to add a proper variant that reads all layers at once into an Image3D, but that requires the Image APIs to be made less crappy first. So it's just this for noew because now I badly need it for Text::DistanceFieldGlyphCacheArrayGL tests. --- doc/changelog.dox | 2 + doc/snippets/DebugTools-gl.cpp | 15 ++++ .../DebugTools/Test/TextureImageGLTest.cpp | 80 ++++++++++++++++++- src/Magnum/DebugTools/TextureImage.cpp | 38 +++++++++ src/Magnum/DebugTools/TextureImage.h | 39 +++++++++ 5 files changed, 172 insertions(+), 2 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index e42e6820c..6772db35e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -641,6 +641,8 @@ See also: - @ref DebugTools::textureSubImage() now checks that the framebuffer is complete before attempting to read from it to avoid silent failures when the texture format isn't framebuffer readable +- Added a @ref DebugTools::textureSubImage() overload for + @ref GL::Texture2DArray @subsubsection changelog-latest-changes-gl GL library diff --git a/doc/snippets/DebugTools-gl.cpp b/doc/snippets/DebugTools-gl.cpp index 28349e596..8d79efdec 100644 --- a/doc/snippets/DebugTools-gl.cpp +++ b/doc/snippets/DebugTools-gl.cpp @@ -41,6 +41,9 @@ #include "Magnum/GL/Framebuffer.h" #include "Magnum/GL/CubeMapTexture.h" #include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#endif #include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Range.h" #include "Magnum/SceneGraph/Drawable.h" @@ -184,6 +187,18 @@ CORRADE_IGNORE_DEPRECATED_POP } #endif +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Texture2DArray texture; +Range2Di rect; +Int layer{}; +/* [textureSubImage-array-rvalue] */ +Image2D image = DebugTools::textureSubImage(texture, 0, layer, rect, + {PixelFormat::RGBA8Unorm}); +/* [textureSubImage-array-rvalue] */ +} +#endif + { GL::CubeMapTexture texture; Range2Di rect; diff --git a/src/Magnum/DebugTools/Test/TextureImageGLTest.cpp b/src/Magnum/DebugTools/Test/TextureImageGLTest.cpp index df16385b3..2a5d43d5d 100644 --- a/src/Magnum/DebugTools/Test/TextureImageGLTest.cpp +++ b/src/Magnum/DebugTools/Test/TextureImageGLTest.cpp @@ -37,6 +37,9 @@ #include "Magnum/GL/OpenGLTester.h" #include "Magnum/GL/PixelFormat.h" #include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#endif #include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Half.h" #include "Magnum/Math/Range.h" @@ -63,6 +66,15 @@ struct TextureImageGLTest: GL::OpenGLTester { #endif void subImage2DGeneric(); + #ifndef MAGNUM_TARGET_GLES2 + void subImage2DArray(); + void subImage2DArrayNotReadable(); + /* Unlike Texture2D the Texture2DArray overload has no codepath to + reinterpret float formats as integer, so no *Generic() test (that + verifies correct detection for generic formats as well) needs to + exist */ + #endif + void subImageCube(); void subImageCubeNotReadable(); #if defined(MAGNUM_BUILD_DEPRECATED) && !defined(MAGNUM_TARGET_GLES2) @@ -91,6 +103,16 @@ const struct { {"non-zero level", 3, 16}, }; +const struct { + const char* name; + Int level, layer, sizeMultiplier; +} LevelLayerData[]{ + {"", 0, 0, 1}, + {"non-zero level", 3, 0, 16}, + {"non-zero layer", 0, 2, 1}, + {"non-zero level and layer", 3, 2, 16}, +}; + TextureImageGLTest::TextureImageGLTest() { addInstancedTests({&TextureImageGLTest::subImage2D}, Containers::arraySize(LevelData)); @@ -102,9 +124,17 @@ TextureImageGLTest::TextureImageGLTest() { #endif }); - addInstancedTests({&TextureImageGLTest::subImage2DGeneric, + addInstancedTests({&TextureImageGLTest::subImage2DGeneric}, + Containers::arraySize(LevelData)); - &TextureImageGLTest::subImageCube}, + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&TextureImageGLTest::subImage2DArray}, + Containers::arraySize(LevelLayerData)); + + addTests({&TextureImageGLTest::subImage2DArrayNotReadable}); + #endif + + addInstancedTests({&TextureImageGLTest::subImageCube}, Containers::arraySize(LevelData)); addTests({&TextureImageGLTest::subImageCubeNotReadable, @@ -278,6 +308,52 @@ void TextureImageGLTest::subImage2DGeneric() { Containers::arrayView(Data2D), TestSuite::Compare::Container); } +#ifndef MAGNUM_TARGET_GLES2 +void TextureImageGLTest::subImage2DArray() { + auto&& data = LevelLayerData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + GL::Texture2DArray texture; + texture + .setStorage(data.level + 1, GL::TextureFormat::RGBA8, {Vector2i{2}*data.sizeMultiplier, data.layer + 1}) + .setSubImage(data.level, {0, 0, data.layer}, ImageView2D{GL::PixelFormat::RGBA, GL::PixelType::UnsignedByte, Vector2i{2}, Data2D}); + + Image2D image = textureSubImage(texture, data.level, data.layer, {{}, Vector2i{2}}, {GL::PixelFormat::RGBA, GL::PixelType::UnsignedByte}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.size(), Vector2i{2}); + CORRADE_COMPARE(image.format(), pixelFormatWrap(GL::PixelFormat::RGBA)); + CORRADE_COMPARE(GL::PixelType(image.formatExtra()), GL::PixelType::UnsignedByte); + CORRADE_COMPARE(image.pixelSize(), 4); + CORRADE_COMPARE_AS(Containers::arrayCast(image.data()), + Containers::arrayView(Data2D), TestSuite::Compare::Container); +} + +void TextureImageGLTest::subImage2DArrayNotReadable() { + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_shared_exponent::string() << "not supported, can't test"); + if(GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::get_texture_sub_image::string() << "supported, can't test"); + #endif + + GL::Texture2DArray texture; + texture.setImage(0, GL::TextureFormat::RGB9E5, ImageView2D{GL::PixelFormat::RGB, GL::PixelType::UnsignedInt5999Rev, Vector2i{2}, Data2D}); + + Containers::String out; + Error redirectError{&out}; + /* The read type doesn't have to match, it doesn't get that far */ + textureSubImage(texture, 0, 0, {{}, Vector2i{2}}, {GL::PixelFormat::RGBA, GL::PixelType::UnsignedByte}); + MAGNUM_VERIFY_NO_GL_ERROR(); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(out, "DebugTools::textureSubImage(): texture format not framebuffer-readable: GL::Framebuffer::Status::Unsupported\n"); + #else + CORRADE_COMPARE(out, "DebugTools::textureSubImage(): texture format not framebuffer-readable: GL::Framebuffer::Status::IncompleteAttachment\n"); + #endif +} +#endif + void TextureImageGLTest::subImageCube() { auto&& data = LevelData[testCaseInstanceId()]; setTestCaseDescription(data.name); diff --git a/src/Magnum/DebugTools/TextureImage.cpp b/src/Magnum/DebugTools/TextureImage.cpp index c7c496d6f..54b70bbd3 100644 --- a/src/Magnum/DebugTools/TextureImage.cpp +++ b/src/Magnum/DebugTools/TextureImage.cpp @@ -34,6 +34,9 @@ #include "Magnum/GL/Extensions.h" #include "Magnum/GL/Framebuffer.h" #include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#endif #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2) #include @@ -238,6 +241,41 @@ GL::BufferImage2D textureSubImage(GL::Texture2D& texture, const Int level, const } #endif +#ifndef MAGNUM_TARGET_GLES2 +void textureSubImage(GL::Texture2DArray& texture, const Int level, const Int layer, const Range2Di& range, Image2D& image) { + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) { + /* Make a temporary Image3D out of the input Image2D, read the pixels + into it, and then move its ownership back to the Image2D */ + /** @todo have this on the Image APIs itself, in particular a + rvalue-only ability to "updimension", and similarly a rvalue-only + dimension slice */ + Vector2i size = image.size(); + Containers::Array data = image.release(); + Image3D image3{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), {size, 1}, Utility::move(data)}; + texture.subImage(level, {{range.min(), layer}, {range.max(), layer + 1}}, image3); + Vector3i size3 = image3.size(); + Containers::Array data3 = image3.release(); + image = Image2D{image3.storage(), image3.format(), image3.formatExtra(), image3.pixelSize(), size3.xy(), Utility::move(data3)}; + return; + } + #endif + + GL::Framebuffer fb{range}; + fb.attachTextureLayer(GL::Framebuffer::ColorAttachment{0}, texture, level, layer); + + CORRADE_ASSERT(fb.checkStatus(GL::FramebufferTarget::Read) == GL::Framebuffer::Status::Complete, + "DebugTools::textureSubImage(): texture format not framebuffer-readable:" << fb.checkStatus(GL::FramebufferTarget::Read), ); + + fb.read(range, image); +} + +Image2D textureSubImage(GL::Texture2DArray& texture, const Int level, const Int layer, const Range2Di& range, Image2D&& image) { + textureSubImage(texture, level, layer, range, image); + return Utility::move(image); +} +#endif + void textureSubImage(GL::CubeMapTexture& texture, const GL::CubeMapCoordinate coordinate, const Int level, const Range2Di& range, Image2D& image) { /* Compared to textureSubImage(GL::CubeMapTexture&, ..., Image3D&), here's no ARB_get_texture_sub_image code path because it only works with 3D diff --git a/src/Magnum/DebugTools/TextureImage.h b/src/Magnum/DebugTools/TextureImage.h index be76b180c..6812bbb2d 100644 --- a/src/Magnum/DebugTools/TextureImage.h +++ b/src/Magnum/DebugTools/TextureImage.h @@ -79,6 +79,45 @@ Convenience alternative to the above, example usage: */ MAGNUM_DEBUGTOOLS_EXPORT Image2D textureSubImage(GL::Texture2D& texture, Int level, const Range2Di& range, Image2D&& image); +#ifndef MAGNUM_TARGET_GLES2 +/** +@brief Read a range of given texture array layer mip level to an image +@m_since_latest + +Emulates @ref GL::Texture2DArray::subImage() on OpenGL ES and WebGL platforms +by creating a framebuffer object and using @ref GL::Framebuffer::read(). On +desktop OpenGL, if @gl_extension{ARB,get_texture_sub_image} is available, it's +just an alias to @ref GL::Texture2DArray::subImage(). + +The function expects that @p texture has a @ref GL::TextureFormat that's +framebuffer-readable and that the @ref GL::PixelFormat and @ref GL::PixelType +combination or the generic @relativeref{Magnum,PixelFormat} is compatible with +it. +@requires_gl30 Extension @gl_extension{EXT,texture_array} +@requires_gles30 Array textures are not available in OpenGL ES 2.0. +@requires_webgl20 Array textures are not available in WebGL 1.0. + +@note This function is available only if Magnum is compiled with + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. +*/ +MAGNUM_DEBUGTOOLS_EXPORT void textureSubImage(GL::Texture2DArray& texture, Int level, Int layer, const Range2Di& range, Image2D& image); + +/** +@brief Read a range of given texture mip level to an image +@m_since_latest + +Convenience alternative to the above, example usage: + +@snippet DebugTools-gl.cpp textureSubImage-array-rvalue + +@note This function is available only if Magnum is compiled with + @ref MAGNUM_TARGET_GL "TARGET_GL" enabled (done by default). See + @ref building-features for more information. +*/ +MAGNUM_DEBUGTOOLS_EXPORT Image2D textureSubImage(GL::Texture2DArray& texture, Int level, Int layer, const Range2Di& range, Image2D&& image); +#endif + /** @brief Read a range of given cube map texture coordinate mip level to an image @m_since_latest