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