From 3cd8b9021bc2e4e2561bebfd62ae68b1226782fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 20 Apr 2025 15:21:34 +0200 Subject: [PATCH] TextureTools: DistanceFieldGL overload taking texture array as output. It just binds a layer of it to a framebuffer internally so no shader changes or extra construction flags are needed. Originally I thought about making the input an array as well, but ultimately that just doesn't make sense -- the processing would need to be done slice by slice anyway and you don't want to allocate the whole excessively sized texture just for it to be used once, and only a part of it every time. --- doc/changelog.dox | 2 + src/Magnum/TextureTools/DistanceFieldGL.cpp | 15 +++ src/Magnum/TextureTools/DistanceFieldGL.h | 34 ++++++ .../TextureTools/Test/DistanceFieldGLTest.cpp | 104 ++++++++++++++---- 4 files changed, 131 insertions(+), 24 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 6772db35e..0b697cc6e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -491,6 +491,8 @@ See also: easier ability to download the resulting image on OpenGL ES platforms; the @ref magnum-distancefieldconverter "magnum-distancefieldconverter" utility thus now compiles and works on OpenGL ES 3+ as well +- Added a @ref TextureTools::DistanceFieldGL::operator()() overload taking a + @ref GL::TextureArray as an output @subsubsection changelog-latest-new-trade Trade library diff --git a/src/Magnum/TextureTools/DistanceFieldGL.cpp b/src/Magnum/TextureTools/DistanceFieldGL.cpp index 7e0ed9b28..9ad418bb9 100644 --- a/src/Magnum/TextureTools/DistanceFieldGL.cpp +++ b/src/Magnum/TextureTools/DistanceFieldGL.cpp @@ -237,4 +237,19 @@ void DistanceFieldGL::operator()(GL::Texture2D& input, GL::Texture2D& output, co } #endif +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGL::operator()(GL::Texture2D& input, GL::Texture2DArray& output, const Int layer, const Range2Di& rectangle, const Vector2i& imageSize) { + GL::Framebuffer framebuffer{rectangle}; + framebuffer.attachTextureLayer(GL::Framebuffer::ColorAttachment{0}, output, 0, layer); + + operator()(input, framebuffer, rectangle, imageSize); +} + +#ifndef MAGNUM_TARGET_GLES +void DistanceFieldGL::operator()(GL::Texture2D& input, GL::Texture2DArray& output, const Int layer, const Range2Di& rectangle) { + return operator()(input, output, layer, rectangle, {}); +} +#endif +#endif + }} diff --git a/src/Magnum/TextureTools/DistanceFieldGL.h b/src/Magnum/TextureTools/DistanceFieldGL.h index 18509b8e1..18227b1de 100644 --- a/src/Magnum/TextureTools/DistanceFieldGL.h +++ b/src/Magnum/TextureTools/DistanceFieldGL.h @@ -296,6 +296,40 @@ class MAGNUM_TEXTURETOOLS_EXPORT DistanceFieldGL { #endif #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Calculate distance field to a texture array layer + * @param input Input texture + * @param output Output texture + * @param layer Layer in the output where to render + * @param rectangle Rectangle in the output where to render + * @param imageSize Input texture size. Mandatory on OpenGL ES and + * WebGL, on desktop GL if left at default the size is internally + * queried using @ref GL::Texture2D::imageSize() instead. + * @m_since_latest + * + * Convenience variant of @ref operator()(GL::Texture2D&, GL::Framebuffer&, const Range2Di&, const Vector2i&) + * that creates a temporary framebuffer with @p output @p layer + * attached and destroys it again after the operation. + * @requires_gl30 Extension @gl_extension{EXT,texture_array} + * @requires_gles30 Texture arrays are not available in OpenGL ES 2.0. + * @requires_webgl20 Texture arrays are not available in WebGL 1.0. + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + void operator()(GL::Texture2D& input, GL::Texture2DArray& output, Int layer, const Range2Di& rectangle, const Vector2i& imageSize + #ifndef MAGNUM_TARGET_GLES + = {} + #endif + ); + #else + /* To avoid having to include Vector2 */ + void operator()(GL::Texture2D& input, GL::Texture2DArray& output, Int layer, const Range2Di& rectangle, const Vector2i& imageSize); + #ifndef MAGNUM_TARGET_GLES + void operator()(GL::Texture2D& input, GL::Texture2DArray& output, Int layer, const Range2Di& rectangle); + #endif + #endif + #endif + private: struct State; Containers::Pointer _state; diff --git a/src/Magnum/TextureTools/Test/DistanceFieldGLTest.cpp b/src/Magnum/TextureTools/Test/DistanceFieldGLTest.cpp index ebcbed68e..e5f47c3fe 100644 --- a/src/Magnum/TextureTools/Test/DistanceFieldGLTest.cpp +++ b/src/Magnum/TextureTools/Test/DistanceFieldGLTest.cpp @@ -47,6 +47,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/TextureTools/DistanceFieldGL.h" #include "Magnum/Trade/AbstractImporter.h" @@ -81,34 +84,45 @@ using namespace Math::Literals; const struct { const char* name; - bool framebuffer, implicitOutputSize; + bool framebuffer, implicitOutputSize, array; + Int layer; Vector2i size; Vector2i offset; bool flipX, flipY; } RunData[]{ {"texture output", - false, false, {64, 64}, {}, false, false}, + false, false, false, 0, {64, 64}, {}, false, false}, {"texture output, flipped on X", - false, false, {64, 64}, {}, true, false}, + false, false, false, 0, {64, 64}, {}, true, false}, {"texture output, flipped on Y", - false, false, {64, 64}, {}, false, true}, + false, false, false, 0, {64, 64}, {}, false, true}, {"texture output, with offset", - false, false, {128, 96}, {64, 32}, false, false}, + false, false, false, 0, {128, 96}, {64, 32}, false, false}, #ifndef MAGNUM_TARGET_GLES {"texture output with implicit size", - false, true, {64, 64}, {}, false, false}, + false, true, false, 0, {64, 64}, {}, false, false}, + #endif + #ifndef MAGNUM_TARGET_GLES2 + {"texture array output, first layer", + false, false, true, 0, {64, 64}, {}, false, false}, + {"texture array output, arbitrary layer", + false, false, true, 3, {64, 64}, {}, false, false}, + #ifndef MAGNUM_TARGET_GLES + {"texture array output with implicit size, arbitrary layer", + false, true, true, 3, {64, 64}, {}, false, false}, + #endif #endif {"framebuffer output", - true, false, {64, 64}, {}, false, false}, + true, false, false, 0, {64, 64}, {}, false, false}, {"framebuffer output, flipped on X", - true, false, {64, 64}, {}, true, false}, + true, false, false, 0, {64, 64}, {}, true, false}, {"framebuffer output, flipped on Y", - true, false, {64, 64}, {}, false, true}, + true, false, false, 0, {64, 64}, {}, false, true}, {"framebuffer output, with offset", - true, false, {128, 96}, {64, 32}, false, false}, + true, false, false, 0, {128, 96}, {64, 32}, false, false}, #ifndef MAGNUM_TARGET_GLES {"framebuffer output with implicit size", - true, true, {64, 64}, {}, false, false}, + true, true, false, 0, {64, 64}, {}, false, false}, #endif }; @@ -262,16 +276,34 @@ void DistanceFieldGLTest::run() { #endif const GL::PixelType outputPixelType = GL::PixelType::UnsignedByte; - GL::Texture2D outputTexture; - outputTexture.setMinificationFilter(GL::SamplerFilter::Nearest, GL::SamplerMipmap::Base) - .setMagnificationFilter(GL::SamplerFilter::Nearest) - .setStorage(1, outputTextureFormat, data.size); - - /* Fill the texture with some data to verify they don't affect the output - and aren't accidentally overwritten when running on just a - subrectangle */ - outputTexture.setSubImage(0, {}, ImageView2D{outputPixelFormat, outputPixelType, data.size, - Containers::Array{DirectInit, std::size_t(data.size.product()*GL::pixelFormatSize(outputPixelFormat, outputPixelType)), '\x66'}}); + GL::Texture2D outputTexture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray outputTextureArray{NoCreate}; + if(data.array) { + outputTextureArray = GL::Texture2DArray{}; + outputTextureArray.setMinificationFilter(GL::SamplerFilter::Nearest, GL::SamplerMipmap::Base) + .setMagnificationFilter(GL::SamplerFilter::Nearest) + .setStorage(1, outputTextureFormat, {data.size, data.layer + 1}); + + /* Fill the texture with some data to verify they don't affect the + output and aren't accidentally overwritten when running on just a + subrectangle */ + outputTextureArray.setSubImage(0, {}, ImageView2D{outputPixelFormat, outputPixelType, data.size, + Containers::Array{DirectInit, std::size_t(data.size.product()*(data.layer + 1)*GL::pixelFormatSize(outputPixelFormat, outputPixelType)), '\x66'}}); + } else + #endif + { + outputTexture = GL::Texture2D{}; + outputTexture.setMinificationFilter(GL::SamplerFilter::Nearest, GL::SamplerMipmap::Base) + .setMagnificationFilter(GL::SamplerFilter::Nearest) + .setStorage(1, outputTextureFormat, data.size); + + /* Fill the texture with some data to verify they don't affect the + output and aren't accidentally overwritten when running on just a + subrectangle */ + outputTexture.setSubImage(0, {}, ImageView2D{outputPixelFormat, outputPixelType, data.size, + Containers::Array{DirectInit, std::size_t(data.size.product()*GL::pixelFormatSize(outputPixelFormat, outputPixelType)), '\x66'}}); + } GL::Framebuffer outputFramebuffer{NoCreate}; if(data.framebuffer) { @@ -296,7 +328,21 @@ void DistanceFieldGLTest::run() { distanceField(input, outputFramebuffer, Range2Di::fromSize(data.offset, Vector2i{64}), inputImage->size()); - } else { + } + #ifndef MAGNUM_TARGET_GLES2 + else if(data.array) { + #ifndef MAGNUM_TARGET_GLES + if(data.implicitOutputSize) + distanceField(input, outputTextureArray, data.layer, + Range2Di::fromSize(data.offset, Vector2i{64})); + else + #endif + distanceField(input, outputTextureArray, data.layer, + Range2Di::fromSize(data.offset, Vector2i{64}), + inputImage->size()); + } else + #endif + { #ifndef MAGNUM_TARGET_GLES if(data.implicitOutputSize) distanceField(input, outputTexture, @@ -327,14 +373,24 @@ void DistanceFieldGLTest::run() { /* Verify that the other data weren't overwritten if processing just a subrange -- it should still have the original data kept */ if(data.offset.product()) { - DebugTools::textureSubImage(outputTexture, 0, Range2Di::fromSize({}, Vector2i{1}), *actualOutputImage); + #ifndef MAGNUM_TARGET_GLES2 + if(data.array) + DebugTools::textureSubImage(outputTextureArray, 0, data.layer, Range2Di::fromSize({}, Vector2i{1}), *actualOutputImage); + else + #endif + DebugTools::textureSubImage(outputTexture, 0, Range2Di::fromSize({}, Vector2i{1}), *actualOutputImage); MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_COMPARE(actualOutputImage->data()[0], '\x66'); } - DebugTools::textureSubImage(outputTexture, 0, Range2Di::fromSize(data.offset, Vector2i{64}), *actualOutputImage); + #ifndef MAGNUM_TARGET_GLES2 + if(data.array) + DebugTools::textureSubImage(outputTextureArray, 0, data.layer, Range2Di::fromSize(data.offset, Vector2i{64}), *actualOutputImage); + else + #endif + DebugTools::textureSubImage(outputTexture, 0, Range2Di::fromSize(data.offset, Vector2i{64}), *actualOutputImage); MAGNUM_VERIFY_NO_GL_ERROR();