From c331591c878539a37e047070fd5673706dba02d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 31 Mar 2025 11:58:35 +0200 Subject: [PATCH] Shaders: add {DistanceField}VectorGL::Flag::TextureArrays. Apart from instancing, which isn't done yet, this achieves feature parity with the Phong and Flat shaders. Compared to those, the texture layer can be supplied also from the attribute, which is what the Text library needs. (And lack of texture array use in the text library was until now a reason why those two shaders didn't have texture array support so far.) --- src/Magnum/Shaders/DistanceFieldVector.frag | 24 +- src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 49 +- src/Magnum/Shaders/DistanceFieldVectorGL.h | 104 +++- .../Test/DistanceFieldVectorGLTest.cpp | 577 +++++++++++++++--- .../Test/DistanceFieldVectorGL_Test.cpp | 4 +- src/Magnum/Shaders/Test/VectorGLTest.cpp | 573 ++++++++++++++--- src/Magnum/Shaders/Test/VectorGL_Test.cpp | 4 +- src/Magnum/Shaders/Vector.frag | 20 +- src/Magnum/Shaders/Vector.vert | 49 +- src/Magnum/Shaders/VectorGL.cpp | 49 +- src/Magnum/Shaders/VectorGL.h | 100 ++- 11 files changed, 1359 insertions(+), 194 deletions(-) diff --git a/src/Magnum/Shaders/DistanceFieldVector.frag b/src/Magnum/Shaders/DistanceFieldVector.frag index be552430a..efa98292c 100644 --- a/src/Magnum/Shaders/DistanceFieldVector.frag +++ b/src/Magnum/Shaders/DistanceFieldVector.frag @@ -42,7 +42,7 @@ #ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 2) +layout(location = 3) #endif uniform lowp vec4 color #ifndef GL_ES @@ -51,12 +51,12 @@ uniform lowp vec4 color ; #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 3) +layout(location = 4) #endif uniform lowp vec4 outlineColor; /* defaults to zero */ #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 4) +layout(location = 5) #endif uniform lowp vec2 outlineRange #ifndef GL_ES @@ -65,7 +65,7 @@ uniform lowp vec2 outlineRange ; #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 5) +layout(location = 6) #endif uniform lowp float smoothness #ifndef GL_ES @@ -141,11 +141,23 @@ layout(std140 #ifdef EXPLICIT_BINDING layout(binding = 6) #endif -uniform lowp sampler2D vectorTexture; +uniform lowp + #ifndef TEXTURE_ARRAYS + sampler2D + #else + sampler2DArray + #endif + vectorTexture; /* Inputs */ -in mediump vec2 interpolatedTextureCoordinates; +in mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + interpolatedTextureCoordinates; #ifdef MULTI_DRAW flat in highp uint drawId; diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index 7718d636b..8408ecd89 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -45,6 +45,7 @@ #include #include "Magnum/GL/Buffer.h" +#include "Magnum/GL/TextureArray.h" #endif #ifdef MAGNUM_BUILD_STATIC @@ -110,6 +111,10 @@ template typename DistanceFieldVectorGL::Com #endif } #endif + #ifndef MAGNUM_TARGET_GLES + if(configuration.flags() >= Flag::TextureArrays) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::EXT::texture_array); + #endif #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ @@ -133,6 +138,9 @@ template typename DistanceFieldVectorGL::Com GL::Shader vert{version, GL::Shader::Type::Vertex}; vert.addSource(rs.getString("compatibility.glsl"_s)) .addSource(configuration.flags() & Flag::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n"_s : ""_s) + #ifndef MAGNUM_TARGET_GLES2 + .addSource(configuration.flags() & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n"_s : ""_s) + #endif .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n"_s : "#define THREE_DIMENSIONS\n"_s); #ifndef MAGNUM_TARGET_GLES2 if(configuration.flags() >= Flag::UniformBuffers) { @@ -159,7 +167,11 @@ template typename DistanceFieldVectorGL::Com .submitCompile(); GL::Shader frag{version, GL::Shader::Type::Fragment}; - frag.addSource(rs.getString("compatibility.glsl"_s)); + frag.addSource(rs.getString("compatibility.glsl"_s)) + #ifndef MAGNUM_TARGET_GLES2 + .addSource(configuration.flags() & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n"_s : ""_s) + #endif + ; #ifndef MAGNUM_TARGET_GLES2 if(configuration.flags() >= Flag::UniformBuffers) { #ifndef MAGNUM_TARGET_WEBGL @@ -259,6 +271,10 @@ template DistanceFieldVectorGL::DistanceFiel _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"_s); if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"_s); + #ifndef MAGNUM_TARGET_GLES2 + if(_flags & Flag::TextureArrays) + _textureLayerUniform = uniformLocation("textureLayer"_s); + #endif _colorUniform = uniformLocation("color"_s); _outlineColorUniform = uniformLocation("outlineColor"_s); _outlineRangeUniform = uniformLocation("outlineRange"_s); @@ -300,6 +316,7 @@ template DistanceFieldVectorGL::DistanceFiel setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); + /* Texture layer is zero by default */ setColor(Color4{1.0f}); /* Outline color is zero by default */ setOutlineRange(0.5f, 1.0f); @@ -344,6 +361,17 @@ template DistanceFieldVectorGL& DistanceFiel return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template DistanceFieldVectorGL& DistanceFieldVectorGL::setTextureLayer(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setTextureLayer(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::DistanceFieldVectorGL::setTextureLayer(): the shader was not created with texture arrays enabled", *this); + setUniform(_textureLayerUniform, id); + return *this; +} +#endif + template DistanceFieldVectorGL& DistanceFieldVectorGL::setColor(const Color4& color) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), @@ -493,10 +521,25 @@ template DistanceFieldVectorGL& DistanceFiel #endif template DistanceFieldVectorGL& DistanceFieldVectorGL::bindVectorTexture(GL::Texture2D& texture) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags & Flag::TextureArrays), + "Shaders::DistanceFieldVectorGL::bindVectorTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead", *this); + #endif texture.bind(TextureUnit); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindVectorTexture(GL::Texture2DArray& texture) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::DistanceFieldVectorGL::bindVectorTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead", *this); + #endif + texture.bind(TextureUnit); + return *this; +} +#endif + template class MAGNUM_SHADERS_EXPORT DistanceFieldVectorGL<2>; template class MAGNUM_SHADERS_EXPORT DistanceFieldVectorGL<3>; @@ -523,6 +566,7 @@ Debug& operator<<(Debug& debug, const DistanceFieldVectorGLFlag value) { _c(ShaderStorageBuffers) #endif _c(MultiDraw) + _c(TextureArrays) #endif #undef _c /* LCOV_EXCL_STOP */ @@ -545,7 +589,8 @@ Debug& operator<<(Debug& debug, const DistanceFieldVectorGLFlags value) { #ifndef MAGNUM_TARGET_WEBGL DistanceFieldVectorGLFlag::ShaderStorageBuffers, /* Superset of UniformBuffers */ #endif - DistanceFieldVectorGLFlag::UniformBuffers + DistanceFieldVectorGLFlag::UniformBuffers, + DistanceFieldVectorGLFlag::TextureArrays #endif }); } diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index a81ece073..8b25b2e08 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -50,7 +50,8 @@ namespace Implementation { #ifndef MAGNUM_TARGET_WEBGL ShaderStorageBuffers = UniformBuffers|(1 << 3), #endif - MultiDraw = UniformBuffers|(1 << 2) + MultiDraw = UniformBuffers|(1 << 2), + TextureArrays = 1 << 4 #endif }; typedef Containers::EnumSet DistanceFieldVectorGLFlags; @@ -89,6 +90,12 @@ Common rendering setup: @snippet Shaders-gl.cpp DistanceFieldVectorGL-usage2 +If @ref Flag::TextureArrays is enabled, pass a @ref GL::Texture2DArray instance +instead of @ref GL::Texture2D. The layer is taken from the third coordinate of +@ref TextureArrayCoordinates, if used instead of @ref TextureCoordinates, +otherwise layer @cpp 0 @ce is picked. Additionally, the value of +@ref setTextureLayer(), which is @cpp 0 @ce by default, is added to the layer. + @section Shaders-DistanceFieldVectorGL-ubo Uniform buffers See @ref shaders-usage-ubo for a high-level overview that applies to all @@ -148,10 +155,26 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @brief 2D texture coordinates * * @ref shaders-generic "Generic attribute", - * @relativeref{Magnum,Vector2}. + * @relativeref{Magnum,Vector2}. Use either this or the + * @ref TextureArrayCoordinates attribute. */ typedef typename GenericGL::TextureCoordinates TextureCoordinates; + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief 2D array texture coordinates + * + * @ref shaders-generic "Generic attribute", + * @relativeref{Magnum,Vector3}. Use either this or the + * @ref TextureCoordinates attribute. The third component is used only + * if @ref Flag::TextureArrays is set. + * @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. + */ + typedef typename GenericGL::TextureArrayCoordinates TextureArrayCoordinates; + #endif + enum: UnsignedInt { /** * Color shader output. @ref shaders-generic "Generic output", @@ -233,7 +256,27 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * relies on uniform buffers, which require WebGL 2.0. * @m_since_latest */ - MultiDraw = UniformBuffers|(1 << 2) + MultiDraw = UniformBuffers|(1 << 2), + + /** + * Use 2D texture arrays. Expects that the texture is supplied via + * @ref bindVectorTexture(GL::Texture2DArray&) instead of + * @ref bindVectorTexture(GL::Texture2D&). The layer is taken from + * the third coordinate of @ref TextureArrayCoordinates, if used + * instead of @ref TextureCoordinates, otherwise layer @cpp 0 @ce + * is picked. Additionally, if @ref Flag::UniformBuffers is not + * enabled, the value of @ref setTextureLayer() is added to the + * layer; if @ref Flag::UniformBuffers is enabled and + * @ref Flag::TextureTransformation is enabled as well, the value + * of @ref TextureTransformationUniform::layer is added to the + * layer. + * @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. + * @m_since_latest + */ + TextureArrays = 1 << 4, #endif }; @@ -428,6 +471,28 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector */ DistanceFieldVectorGL& setTextureMatrix(const Matrix3& matrix); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set texture array layer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with @ref Flag::TextureArrays + * enabled. Initial value is @cpp 0 @ce. If a three-component + * @ref TextureArrayCoordinates attribute is used instead of + * @ref TextureCoordinates, this value is added to the layer coming + * from the third component. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::layer and call + * @ref bindTextureTransformationBuffer() instead. + * @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. + */ + DistanceFieldVectorGL& setTextureLayer(UnsignedInt layer); + #endif + /** * @brief Set fill color * @return Reference to self (for method chaining) @@ -622,10 +687,32 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @brief Bind a vector texture * @return Reference to self (for method chaining) * + * If @ref Flag::TextureArrays is enabled, use + * @ref bindVectorTexture(GL::Texture2DArray&) instead. * @see @ref Flag::TextureTransformation, @ref setTextureMatrix() */ DistanceFieldVectorGL& bindVectorTexture(GL::Texture2D& texture); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Bind a vector array texture + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with @ref Flag::TextureArrays + * enabled. The layer is taken from the third coordinate of + * @ref TextureArrayCoordinates, if used instead of + * @ref TextureCoordinates, otherwise layer @cpp 0 @ce is picked. + * Additionally, if @ref Flag::UniformBuffers is not enabled, the layer + * index is offset with the value set in @ref setTextureLayer(); if + * @ref Flag::UniformBuffers is enabled and + * @ref Flag::TextureTransformation is enabled as well, the layer index + * is offset with @ref TextureTransformationUniform::layer. + * @see @ref Flag::TextureTransformation, @ref setTextureMatrix() + */ + DistanceFieldVectorGL& bindVectorTexture(GL::Texture2DArray& texture); + #endif + /** * @} */ @@ -643,10 +730,13 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, - _colorUniform{2}, - _outlineColorUniform{3}, - _outlineRangeUniform{4}, - _smoothnessUniform{5}; + #ifndef MAGNUM_TARGET_GLES2 + _textureLayerUniform{2}, + #endif + _colorUniform{3}, + _outlineColorUniform{4}, + _outlineRangeUniform{5}, + _smoothnessUniform{6}; #ifndef MAGNUM_TARGET_GLES2 /* Used instead of all other uniforms when Flag::UniformBuffers is set, so it can alias them */ diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index 3d321f239..1003bffb3 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -63,6 +63,7 @@ #include "Magnum/GL/Extensions.h" #include "Magnum/GL/MeshView.h" +#include "Magnum/GL/TextureArray.h" #include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/GenerateIndices.h" #include "Magnum/Primitives/Circle.h" @@ -100,9 +101,12 @@ struct DistanceFieldVectorGLTest: GL::OpenGLTester { #ifndef MAGNUM_TARGET_GLES2 template void setUniformUniformBuffersEnabled(); template void bindBufferUniformBuffersNotEnabled(); + template void bindTextureInvalid(); + template void bindTextureArrayInvalid(); #endif template void setTextureMatrixNotEnabled(); #ifndef MAGNUM_TARGET_GLES2 + template void setTextureLayerNotArray(); template void bindTextureTransformBufferNotEnabled(); #endif #ifndef MAGNUM_TARGET_GLES2 @@ -167,7 +171,11 @@ constexpr struct { DistanceFieldVectorGL2D::Flags flags; } ConstructData[]{ {"", {}}, - {"texture transformation", DistanceFieldVectorGL2D::Flag::TextureTransformation} + {"texture transformation", DistanceFieldVectorGL2D::Flag::TextureTransformation}, + #ifndef MAGNUM_TARGET_GLES2 + {"texture arrays", DistanceFieldVectorGL2D::Flag::TextureArrays}, + {"texture transformation + texture arrays", DistanceFieldVectorGL2D::Flag::TextureTransformation|DistanceFieldVectorGL2D::Flag::TextureArrays}, + #endif }; #ifndef MAGNUM_TARGET_GLES2 @@ -179,12 +187,14 @@ constexpr struct { {"classic fallback", {}, 1, 1}, {"", DistanceFieldVectorGL2D::Flag::UniformBuffers, 1, 1}, {"texture transformation", DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation, 1, 1}, + {"texture arrays", DistanceFieldVectorGL2D::Flag::TextureArrays, 1, 1}, + {"texture transformation + texture arrays", DistanceFieldVectorGL2D::Flag::TextureTransformation|DistanceFieldVectorGL2D::Flag::TextureArrays, 1, 1}, /* SwiftShader has 256 uniform vectors at most, per-draw is 4+1 in 3D case and 3+1 in 2D, per-material 4 */ {"multiple materials, draws", DistanceFieldVectorGL2D::Flag::UniformBuffers, 16, 48}, - {"multidraw with all the things", DistanceFieldVectorGL2D::Flag::MultiDraw|DistanceFieldVectorGL2D::Flag::TextureTransformation, 16, 48}, + {"multidraw with all the things", DistanceFieldVectorGL2D::Flag::MultiDraw|DistanceFieldVectorGL2D::Flag::TextureTransformation|DistanceFieldVectorGL2D::Flag::TextureArrays, 16, 48}, #ifndef MAGNUM_TARGET_WEBGL - {"shader storage + multidraw with all the things", DistanceFieldVectorGL2D::Flag::ShaderStorageBuffers|DistanceFieldVectorGL2D::Flag::MultiDraw|DistanceFieldVectorGL2D::Flag::TextureTransformation, 0, 0} + {"shader storage + multidraw with all the things", DistanceFieldVectorGL2D::Flag::ShaderStorageBuffers|DistanceFieldVectorGL2D::Flag::MultiDraw|DistanceFieldVectorGL2D::Flag::TextureTransformation|DistanceFieldVectorGL2D::Flag::TextureArrays, 0, 0} #endif }; @@ -206,22 +216,77 @@ const struct { const char* name; DistanceFieldVectorGL2D::Flags flags; Matrix3 textureTransformation; + bool arrayTextureCoordinates; + Int layerAttribute, layerUniform; Color4 color, outlineColor; Float outlineRangeStart, outlineRangeEnd, smoothness; const char* file2D; const char* file3D; bool flip; } RenderData[] { - {"texture transformation", DistanceFieldVectorGL2D::Flag::TextureTransformation, + {"texture transformation", + DistanceFieldVectorGL2D::Flag::TextureTransformation, Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), - 0xffffff_rgbf, 0x00000000_rgbaf, 0.5f, 1.0f, 0.04f, + false, 0, 0, 0xffffff_rgbf, 0x00000000_rgbaf, 0.5f, 1.0f, 0.04f, "defaults-distancefield.tga", "defaults-distancefield.tga", true}, - {"smooth0.1", {}, {}, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.1f, + {"smooth0.1", + {}, {}, + false, 0, 0, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.1f, "smooth0.1-2D.tga", "smooth0.1-3D.tga", false}, - {"smooth0.2", {}, {}, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.2f, + {"smooth0.2", + {}, {}, + false, 0, 0, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.2f, "smooth0.2-2D.tga", "smooth0.2-3D.tga", false}, - {"outline", {}, {}, 0xffff99_rgbf, 0x9999ff_rgbf, 0.6f, 0.45f, 0.05f, - "outline2D.tga", "outline3D.tga", false} + {"outline", + {}, {}, + false, 0, 0, 0xffff99_rgbf, 0x9999ff_rgbf, 0.6f, 0.45f, 0.05f, + "outline2D.tga", "outline3D.tga", false}, + #ifndef MAGNUM_TARGET_GLES2 + {"array texture, 2D coordinates, first layer", + DistanceFieldVectorGL2D::Flag::TextureArrays, {}, + false, 0, 0, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.1f, + "smooth0.1-2D.tga", "smooth0.1-3D.tga", false}, + {"array texture, 2D coordinates, arbitrary layer from uniform", + DistanceFieldVectorGL2D::Flag::TextureArrays, {}, + false, 0, 6, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.1f, + "smooth0.1-2D.tga", "smooth0.1-3D.tga", false}, + {"array texture, 2D coordinates, texture transformation, arbitrary layer from uniform", + DistanceFieldVectorGL2D::Flag::TextureArrays|DistanceFieldVectorGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + false, 0, 6, 0xffffff_rgbf, 0x00000000_rgbaf, 0.5f, 1.0f, 0.04f, + "defaults-distancefield.tga", "defaults-distancefield.tga", true}, + {"array texture, array coordinates, first layer", + DistanceFieldVectorGL2D::Flag::TextureArrays, {}, + true, 0, 0, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.1f, + "smooth0.1-2D.tga", "smooth0.1-3D.tga", false}, + {"array texture, array coordinates, arbitrary layer from attribute", + DistanceFieldVectorGL2D::Flag::TextureArrays, {}, + true, 6, 0, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.1f, + "smooth0.1-2D.tga", "smooth0.1-3D.tga", false}, + {"array texture, array coordinates, arbitrary layer from uniform", + DistanceFieldVectorGL2D::Flag::TextureArrays, {}, + true, 0, 6, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.1f, + "smooth0.1-2D.tga", "smooth0.1-3D.tga", false}, + {"array texture, array coordinates, arbitrary layer from both", + DistanceFieldVectorGL2D::Flag::TextureArrays, {}, + true, 2, 4, 0xffff99_rgbf, 0x9999ff_rgbf, 0.5f, 1.0f, 0.1f, + "smooth0.1-2D.tga", "smooth0.1-3D.tga", false}, + {"array texture, array coordinates, texture transformation, arbitrary layer from attribute", + DistanceFieldVectorGL2D::Flag::TextureArrays|DistanceFieldVectorGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + true, 6, 0, 0xffffff_rgbf, 0x00000000_rgbaf, 0.5f, 1.0f, 0.04f, + "defaults-distancefield.tga", "defaults-distancefield.tga", true}, + {"array texture, array coordinates, texture transformation, arbitrary layer from uniform", + DistanceFieldVectorGL2D::Flag::TextureArrays|DistanceFieldVectorGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + true, 0, 6, 0xffffff_rgbf, 0x00000000_rgbaf, 0.5f, 1.0f, 0.04f, + "defaults-distancefield.tga", "defaults-distancefield.tga", true}, + {"array texture, array coordinates, texture transformation, arbitrary layer from both", + DistanceFieldVectorGL2D::Flag::TextureArrays|DistanceFieldVectorGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + true, 2, 4, 0xffffff_rgbf, 0x00000000_rgbaf, 0.5f, 1.0f, 0.04f, + "defaults-distancefield.tga", "defaults-distancefield.tga", true}, + #endif }; #ifndef MAGNUM_TARGET_GLES2 @@ -239,31 +304,55 @@ constexpr struct { {}, 1, 1, true, 16, /* Minor differences on ARM Mali */ 1.67f, 0.012f}, + {"bind with offset, texture array", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + DistanceFieldVectorGL2D::Flag::TextureArrays, 1, 1, true, 16, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, #ifndef MAGNUM_TARGET_WEBGL {"bind with offset, shader storage", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", DistanceFieldVectorGL2D::Flag::ShaderStorageBuffers, 0, 0, true, 16, /* Minor differences on ARM Mali */ 1.67f, 0.012f}, + {"bind with offset, texture array, shader storage", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + DistanceFieldVectorGL2D::Flag::TextureArrays|DistanceFieldVectorGL2D::Flag::ShaderStorageBuffers, 0, 0, true, 16, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, #endif {"draw offset", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", {}, 2, 3, false, 1, /* Minor differences on ARM Mali */ 1.67f, 0.012f}, + {"draw offset, texture array", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + DistanceFieldVectorGL2D::Flag::TextureArrays, 2, 3, false, 1, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, #ifndef MAGNUM_TARGET_WEBGL {"draw offset, shader storage", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", DistanceFieldVectorGL2D::Flag::ShaderStorageBuffers, 0, 0, false, 1, /* Minor differences on ARM Mali */ 1.67f, 0.012f}, + {"draw offset, texture array, shader storage", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + DistanceFieldVectorGL2D::Flag::TextureArrays|DistanceFieldVectorGL2D::Flag::ShaderStorageBuffers, 0, 0, false, 1, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, #endif {"multidraw", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", DistanceFieldVectorGL2D::Flag::MultiDraw, 2, 3, false, 1, /* Minor differences on ARM Mali */ 1.67f, 0.012f}, + {"multidraw, texture array", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + DistanceFieldVectorGL2D::Flag::TextureArrays|DistanceFieldVectorGL2D::Flag::MultiDraw, 2, 3, false, 1, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, #ifndef MAGNUM_TARGET_WEBGL {"multidraw, shader storage", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", DistanceFieldVectorGL2D::Flag::ShaderStorageBuffers|DistanceFieldVectorGL2D::Flag::MultiDraw, 0, 0, false, 1, /* Minor differences on ARM Mali */ 1.67f, 0.012f}, + {"multidraw, texture array, shader storage", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + DistanceFieldVectorGL2D::Flag::TextureArrays|DistanceFieldVectorGL2D::Flag::ShaderStorageBuffers|DistanceFieldVectorGL2D::Flag::MultiDraw, 0, 0, false, 1, + /* Minor differences on ARM Mali */ + 1.67f, 0.012f}, #endif }; #endif @@ -312,10 +401,16 @@ DistanceFieldVectorGLTest::DistanceFieldVectorGLTest() { &DistanceFieldVectorGLTest::setUniformUniformBuffersEnabled<3>, &DistanceFieldVectorGLTest::bindBufferUniformBuffersNotEnabled<2>, &DistanceFieldVectorGLTest::bindBufferUniformBuffersNotEnabled<3>, + &DistanceFieldVectorGLTest::bindTextureInvalid<2>, + &DistanceFieldVectorGLTest::bindTextureInvalid<3>, + &DistanceFieldVectorGLTest::bindTextureArrayInvalid<2>, + &DistanceFieldVectorGLTest::bindTextureArrayInvalid<3>, #endif &DistanceFieldVectorGLTest::setTextureMatrixNotEnabled<2>, &DistanceFieldVectorGLTest::setTextureMatrixNotEnabled<3>, #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::setTextureLayerNotArray<2>, + &DistanceFieldVectorGLTest::setTextureLayerNotArray<3>, &DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled<2>, &DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled<3>, #endif @@ -633,6 +728,7 @@ template void DistanceFieldVectorGLTest::setUniformUnifo Error redirectError{&out}; shader.setTransformationProjectionMatrix({}) .setTextureMatrix({}) + .setTextureLayer({}) .setColor({}) .setOutlineColor({}) .setOutlineRange({}, {}) @@ -640,6 +736,7 @@ template void DistanceFieldVectorGLTest::setUniformUnifo CORRADE_COMPARE(out, "Shaders::DistanceFieldVectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setTextureLayer(): the shader was created with uniform buffers enabled\n" "Shaders::DistanceFieldVectorGL::setColor(): the shader was created with uniform buffers enabled\n" "Shaders::DistanceFieldVectorGL::setOutlineColor(): the shader was created with uniform buffers enabled\n" "Shaders::DistanceFieldVectorGL::setOutlineRange(): the shader was created with uniform buffers enabled\n" @@ -676,6 +773,45 @@ template void DistanceFieldVectorGLTest::bindBufferUnifo "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::DistanceFieldVectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } + +template void DistanceFieldVectorGLTest::bindTextureInvalid() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + GL::Texture2D texture; + DistanceFieldVectorGL shader{typename DistanceFieldVectorGL::Configuration{} + .setFlags(DistanceFieldVectorGL::Flag::TextureArrays)}; + + Containers::String out; + Error redirectError{&out}; + shader.bindVectorTexture(texture); + CORRADE_COMPARE(out, "Shaders::DistanceFieldVectorGL::bindVectorTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead\n"); +} + +template void DistanceFieldVectorGLTest::bindTextureArrayInvalid() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + GL::Texture2DArray texture; + DistanceFieldVectorGL shader; + + Containers::String out; + Error redirectError{&out}; + shader.bindVectorTexture(texture); + CORRADE_COMPARE(out, "Shaders::DistanceFieldVectorGL::bindVectorTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead\n"); +} #endif template void DistanceFieldVectorGLTest::setTextureMatrixNotEnabled() { @@ -693,6 +829,25 @@ template void DistanceFieldVectorGLTest::setTextureMatri } #ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::setTextureLayerNotArray() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + GL::Texture2D texture; + DistanceFieldVectorGL shader; + + Containers::String out; + Error redirectError{&out}; + shader.setTextureLayer(37); + CORRADE_COMPARE(out, "Shaders::DistanceFieldVectorGL::setTextureLayer(): the shader was not created with texture arrays enabled\n"); +} + template void DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -1032,36 +1187,89 @@ template void DistanceFieldVectorGLTest::ren !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh square = MeshTools::compile(Primitives::squareSolid(Primitives::SquareFlag::TextureCoordinates)); + struct Vertex { + Vector2 position; + Vector3 textureCoords; + } squareData[] { + {{ 1.0f, -1.0f}, {1.0f, 0.0f, Float(data.layerAttribute)}}, + {{ 1.0f, 1.0f}, {1.0f, 1.0f, Float(data.layerAttribute)}}, + {{-1.0f, -1.0f}, {0.0f, 0.0f, Float(data.layerAttribute)}}, + {{-1.0f, 1.0f}, {0.0f, 1.0f, Float(data.layerAttribute)}} + }; + GL::Mesh square{GL::MeshPrimitive::TriangleStrip}; + #ifndef MAGNUM_TARGET_GLES2 + if(data.arrayTextureCoordinates) { + square.addVertexBuffer(GL::Buffer{squareData}, 0, + GenericGL2D::Position{}, + GenericGL2D::TextureArrayCoordinates{}); + } else + #endif + { + square.addVertexBuffer(GL::Buffer{squareData}, 0, + GenericGL2D::Position{}, + GenericGL2D::TextureCoordinates{}, + sizeof(Float)); + } + square.setCount(4); + + DistanceFieldVectorGL2D::Flags flags = data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag & DistanceFieldVectorGL2D::Flag::UniformBuffers && (data.flags & DistanceFieldVectorGL2D::Flag::TextureArrays) && !(data.flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) && data.layerUniform) { + CORRADE_INFO("Texture arrays with layer passed from a uniform currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= DistanceFieldVectorGL2D::Flag::TextureTransformation; + } + #endif + DistanceFieldVectorGL2D shader{DistanceFieldVectorGL2D::Configuration{} + .setFlags(flags)}; Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); - GL::Texture2D texture; + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + #endif Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Path::join(_testDir, "TestFiles/vector-distancefield.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge); - - #ifdef MAGNUM_TARGET_GLES2 - /* Don't want to bother with the fiasco of single-channel formats and - texture storage extensions on ES2 */ - texture.setImage(0, TextureFormatR, *image); - #else - texture.setStorage(1, TextureFormatR, image->size()) - .setSubImage(0, {}, *image); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & DistanceFieldVectorGL2D::Flag::TextureArrays) { + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, {image->size(), data.layerUniform + data.layerAttribute + 1}) + .setSubImage(0, {0, 0, data.layerUniform + data.layerAttribute}, ImageView2D{*image}); + + shader.bindVectorTexture(textureArray); + } else #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge); + + #ifdef MAGNUM_TARGET_GLES2 + /* Don't want to bother with the fiasco of single-channel formats and + texture storage extensions on ES2 */ + texture.setImage(0, TextureFormatR, *image); + #else + texture.setStorage(1, TextureFormatR, image->size()) + .setSubImage(0, {}, *image); + #endif - DistanceFieldVectorGL2D shader{DistanceFieldVectorGL2D::Configuration{} - .setFlags(data.flags|flag)}; - shader.bindVectorTexture(texture); + shader.bindVectorTexture(texture); + } if(flag == DistanceFieldVectorGL2D::Flag{}) { if(data.textureTransformation != Matrix3{}) shader.setTextureMatrix(data.textureTransformation); else shader.setTransformationProjectionMatrix( Matrix3::projection({2.1f, 2.1f})); + #ifndef MAGNUM_TARGET_GLES2 + if(data.layerUniform != 0) /* to verify the default */ + shader.setTextureLayer(data.layerUniform); + #endif shader.setColor(data.color) .setOutlineColor(data.outlineColor) .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) @@ -1095,8 +1303,9 @@ template void DistanceFieldVectorGLTest::ren GL::Buffer textureTransformationlUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(data.textureTransformation) + .setLayer(data.layerUniform) }}; - if(data.flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) + if(flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) shader.bindTextureTransformationBuffer(textureTransformationlUniform); shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) @@ -1163,30 +1372,79 @@ template void DistanceFieldVectorGLTest::ren !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh plane = MeshTools::compile(Primitives::planeSolid(Primitives::PlaneFlag::TextureCoordinates)); + struct Vertex { + Vector3 position; + Vector3 textureCoords; + } planeData[] { + {{ 1.0f, -1.0f, 0.0f}, {1.0f, 0.0f, Float(data.layerAttribute)}}, + {{ 1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, Float(data.layerAttribute)}}, + {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, Float(data.layerAttribute)}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, Float(data.layerAttribute)}} + }; + GL::Mesh plane{GL::MeshPrimitive::TriangleStrip}; + #ifndef MAGNUM_TARGET_GLES2 + if(data.arrayTextureCoordinates) { + plane.addVertexBuffer(GL::Buffer{planeData}, 0, + GenericGL3D::Position{}, + GenericGL3D::TextureArrayCoordinates{}); + } else + #endif + { + plane.addVertexBuffer(GL::Buffer{planeData}, 0, + GenericGL3D::Position{}, + GenericGL3D::TextureCoordinates{}, + sizeof(Float)); + } + plane.setCount(4); + + DistanceFieldVectorGL3D::Flags flags = data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag & DistanceFieldVectorGL3D::Flag::UniformBuffers && (data.flags & DistanceFieldVectorGL3D::Flag::TextureArrays) && !(data.flags & DistanceFieldVectorGL3D::Flag::TextureTransformation) && data.layerUniform) { + CORRADE_INFO("Texture arrays with layer passed from a uniform currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= DistanceFieldVectorGL3D::Flag::TextureTransformation; + } + #endif + DistanceFieldVectorGL3D shader{DistanceFieldVectorGL3D::Configuration{} + .setFlags(flags)}; Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); - GL::Texture2D texture; + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + #endif Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Path::join(_testDir, "TestFiles/vector-distancefield.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge); - - #ifdef MAGNUM_TARGET_GLES2 - /* Don't want to bother with the fiasco of single-channel formats and - texture storage extensions on ES2 */ - texture.setImage(0, TextureFormatR, *image); - #else - texture.setStorage(1, TextureFormatR, image->size()) - .setSubImage(0, {}, *image); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & DistanceFieldVectorGL3D::Flag::TextureArrays) { + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, {image->size(), data.layerUniform + data.layerAttribute + 1}) + .setSubImage(0, {0, 0, data.layerUniform + data.layerAttribute}, ImageView2D{*image}); + + shader.bindVectorTexture(textureArray); + } else #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge); + + #ifdef MAGNUM_TARGET_GLES2 + /* Don't want to bother with the fiasco of single-channel formats and + texture storage extensions on ES2 */ + texture.setImage(0, TextureFormatR, *image); + #else + texture.setStorage(1, TextureFormatR, image->size()) + .setSubImage(0, {}, *image); + #endif - DistanceFieldVectorGL3D shader{DistanceFieldVectorGL3D::Configuration{} - .setFlags(data.flags|flag)}; - shader.bindVectorTexture(texture); + shader.bindVectorTexture(texture); + } if(flag == DistanceFieldVectorGL3D::Flag{}) { if(data.textureTransformation != Matrix3{}) @@ -1196,6 +1454,10 @@ template void DistanceFieldVectorGLTest::ren Matrix4::translation(Vector3::zAxis(-2.15f))* Matrix4::rotationY(-15.0_degf)* Matrix4::rotationZ(15.0_degf)); + #ifndef MAGNUM_TARGET_GLES2 + if(data.layerUniform != 0) /* to verify the default */ + shader.setTextureLayer(data.layerUniform); + #endif shader.setColor(data.color) .setOutlineColor(data.outlineColor) .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) @@ -1232,8 +1494,9 @@ template void DistanceFieldVectorGLTest::ren GL::Buffer textureTransformationlUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(data.textureTransformation) + .setLayer(data.layerUniform) }}; - if(data.flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) + if(flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) shader.bindTextureTransformationBuffer(textureTransformationlUniform); shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) @@ -1308,6 +1571,11 @@ void DistanceFieldVectorGLTest::renderMulti2D() { CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); #endif + DistanceFieldVectorGL2D shader{DistanceFieldVectorGL2D::Configuration{} + .setFlags(DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation|data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount)}; + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1317,12 +1585,36 @@ void DistanceFieldVectorGLTest::renderMulti2D() { Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Path::join(_testDir, "TestFiles/vector-distancefield.tga")) && (image = importer->image2D(0))); - GL::Texture2D vector; - vector.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, GL::TextureFormat::R8, image->size()) - .setSubImage(0, {}, *image); + + /* For arrays we the original image three times to different offsets in + three different slices */ + GL::Texture2D vector{NoCreate}; + GL::Texture2DArray vectorArray{NoCreate}; + if(data.flags & DistanceFieldVectorGL2D::Flag::TextureArrays) { + Vector3i size{image->size().x(), image->size().y()*2, 6}; + + vectorArray = GL::Texture2DArray{}; + vectorArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, size) + /* Clear to all zeros for reproducible output */ + .setSubImage(0, {}, Image3D{PixelFormat::R8Unorm, size, Containers::Array{ValueInit, std::size_t(size.product())}}) + .setSubImage(0, {0, size.y()/4, 1}, ImageView2D{*image}) + .setSubImage(0, {0, size.y()/2, 3}, ImageView2D{*image}) + .setSubImage(0, {0, 0, 5}, ImageView2D{*image}); + + shader.bindVectorTexture(vectorArray); + } else { + vector = GL::Texture2D{}; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + shader.bindVectorTexture(vector); + } /* Circle is a fan, plane is a strip, make it indexed first */ Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32, @@ -1331,7 +1623,47 @@ void DistanceFieldVectorGLTest::renderMulti2D() { Primitives::SquareFlag::TextureCoordinates)); Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3, Primitives::Circle2DFlag::TextureCoordinates)); - GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + + /* Assuming the texture coordinates are the last attribute, add a four-byte + padding after, which we subsequently abuse as the layer index */ + /** @todo clean this up once MeshData (and primitives?) support array + coordinates directly */ + Trade::MeshData meshData = MeshTools::interleave( + MeshTools::concatenate({circleData, squareData, triangleData}), + {Trade::MeshAttributeData{4}}); + CORRADE_COMPARE(meshData.attributeCount(), 2); + CORRADE_COMPARE(meshData.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(meshData.attributeName(1), Trade::MeshAttribute::TextureCoordinates); + /* Manual cast because the real attribute type is Vector2 */ + const Containers::StridedArrayView1D textureCoordinates = Containers::arrayCast(meshData.mutableAttribute(Trade::MeshAttribute::TextureCoordinates)); + + /* The circle will use the last slice, coming from just the attribute + alone */ + for(UnsignedInt i = 0; i != circleData.vertexCount(); ++i) + textureCoordinates[i].z() = 5; + /* The square will use the third slice, coming from both the attribute and + the uniform */ + for(UnsignedInt i = 0; i != squareData.vertexCount(); ++i) + textureCoordinates[circleData.vertexCount() + i].z() = 1; + /* The triangle will use the second slice, coming from just the uniform. + The memory isn't initialized by default however, so set the attribute to + 0. */ + for(UnsignedInt i = 0; i != triangleData.vertexCount(); ++i) + textureCoordinates[circleData.vertexCount() + squareData.vertexCount() + i].z() = 0; + + /* Making some assumptions about the layout for simplicity */ + CORRADE_COMPARE(meshData.attributeStride(0), sizeof(Vector2) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeStride(1), sizeof(Vector2) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeOffset(0), 0); + CORRADE_COMPARE(meshData.attributeOffset(1), sizeof(Vector2)); + GL::Mesh mesh; + mesh.addVertexBuffer(GL::Buffer{meshData.vertexData()}, 0, + GenericGL2D::Position{}, + GenericGL2D::TextureArrayCoordinates{}) + .setIndexBuffer(GL::Buffer{GL::Buffer::TargetHint::ElementArray, meshData.indexData()}, 0, + meshData.indexType()) + .setCount(meshData.indexCount()); + GL::MeshView circle{mesh}; circle.setCount(circleData.indexCount()); GL::MeshView square{mesh}; @@ -1379,17 +1711,30 @@ void DistanceFieldVectorGLTest::renderMulti2D() { Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & DistanceFieldVectorGL2D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.0f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})* Matrix3::translation({0.5f, 0.5f})* Matrix3::rotation(180.0_degf)* - Matrix3::translation({-0.5f, -0.5f}) - ); + Matrix3::translation({-0.5f, -0.5f})) + .setLayer(0); /* ignored if not array */ textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & DistanceFieldVectorGL2D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.5f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})* Matrix3::translation(Vector2::xAxis(1.0f))* - Matrix3::scaling(Vector2::xScale(-1.0f)) - ); + Matrix3::scaling(Vector2::xScale(-1.0f))) + .setLayer(2); /* ignored if not array */ textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} - .setTextureMatrix(Matrix3{}); + .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & DistanceFieldVectorGL2D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.25f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})) + .setLayer(1); /* ignored if not array */ GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; Containers::Array drawData{2*data.uniformIncrement + 1}; @@ -1403,12 +1748,6 @@ void DistanceFieldVectorGLTest::renderMulti2D() { .setMaterialId(data.bindWithOffset ? 0 : 0); GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; - DistanceFieldVectorGL2D shader{DistanceFieldVectorGL2D::Configuration{} - .setFlags(DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation|data.flags) - .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; - shader.bindVectorTexture(vector); - /* Rebinding UBOs / SSBOs each time */ if(data.bindWithOffset) { shader.bindMaterialBuffer(materialUniform, @@ -1528,6 +1867,11 @@ void DistanceFieldVectorGLTest::renderMulti3D() { CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); #endif + DistanceFieldVectorGL3D shader{DistanceFieldVectorGL3D::Configuration{} + .setFlags(DistanceFieldVectorGL3D::Flag::UniformBuffers|DistanceFieldVectorGL3D::Flag::TextureTransformation|data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount)}; + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1537,12 +1881,36 @@ void DistanceFieldVectorGLTest::renderMulti3D() { Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Path::join(_testDir, "TestFiles/vector-distancefield.tga")) && (image = importer->image2D(0))); - GL::Texture2D vector; - vector.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, GL::TextureFormat::R8, image->size()) - .setSubImage(0, {}, *image); + + /* For arrays we the original image three times to different offsets in + three different slices */ + GL::Texture2D vector{NoCreate}; + GL::Texture2DArray vectorArray{NoCreate}; + if(data.flags & DistanceFieldVectorGL3D::Flag::TextureArrays) { + Vector3i size{image->size().x(), image->size().y()*2, 6}; + + vectorArray = GL::Texture2DArray{}; + vectorArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, size) + /* Clear to all zeros for reproducible output */ + .setSubImage(0, {}, Image3D{PixelFormat::R8Unorm, size, Containers::Array{ValueInit, std::size_t(size.product())}}) + .setSubImage(0, {0, size.y()/4, 1}, ImageView2D{*image}) + .setSubImage(0, {0, size.y()/2, 3}, ImageView2D{*image}) + .setSubImage(0, {0, 0, 5}, ImageView2D{*image}); + + shader.bindVectorTexture(vectorArray); + } else { + vector = GL::Texture2D{}; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + shader.bindVectorTexture(vector); + } Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates); @@ -1551,7 +1919,51 @@ void DistanceFieldVectorGLTest::renderMulti3D() { Primitives::PlaneFlag::TextureCoordinates)); Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, Primitives::ConeFlag::TextureCoordinates); - GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + + /* Assuming the texture coordinates are the last attribute, add a four-byte + padding after, which we subsequently abuse as the layer index */ + /** @todo clean this up once MeshData (and primitives?) support array + coordinates directly */ + Trade::MeshData meshData = MeshTools::interleave( + MeshTools::concatenate({sphereData, planeData, coneData}), + {Trade::MeshAttributeData{4}}); + CORRADE_COMPARE(meshData.attributeCount(), 3); + CORRADE_COMPARE(meshData.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(meshData.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(meshData.attributeName(2), Trade::MeshAttribute::TextureCoordinates); + /* Manual cast because the real attribute type is Vector2 */ + const Containers::StridedArrayView1D textureCoordinates = Containers::arrayCast(meshData.mutableAttribute(Trade::MeshAttribute::TextureCoordinates)); + + /* The sphere will use the last slice, coming from just the attribute + alone */ + for(UnsignedInt i = 0; i != sphereData.vertexCount(); ++i) + textureCoordinates[i].z() = 5; + /* The plane will use the third slice, coming from both the attribute and + the uniform */ + for(UnsignedInt i = 0; i != planeData.vertexCount(); ++i) + textureCoordinates[sphereData.vertexCount() + i].z() = 1; + /* The cone will use the first slice, coming from just the uniform. The + memory isn't initialized by default however, so set the attribute to + 0. */ + for(UnsignedInt i = 0; i != coneData.vertexCount(); ++i) + textureCoordinates[sphereData.vertexCount() + planeData.vertexCount() + i].z() = 0; + + /* Making some assumptions about the layout for simplicity */ + CORRADE_COMPARE(meshData.attributeStride(0), sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeStride(1), sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeStride(2), sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeOffset(0), 0); + CORRADE_COMPARE(meshData.attributeOffset(1), sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeOffset(2), sizeof(Vector3) + sizeof(Vector3)); + GL::Mesh mesh; + mesh.addVertexBuffer(GL::Buffer{meshData.vertexData()}, 0, + GenericGL3D::Position{}, + GenericGL3D::Normal{}, + GenericGL3D::TextureArrayCoordinates{}) + .setIndexBuffer(GL::Buffer{GL::Buffer::TargetHint::ElementArray, meshData.indexData()}, 0, + meshData.indexType()) + .setCount(meshData.indexCount()); + GL::MeshView sphere{mesh}; sphere.setCount(sphereData.indexCount()); GL::MeshView plane{mesh}; @@ -1604,17 +2016,30 @@ void DistanceFieldVectorGLTest::renderMulti3D() { Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & DistanceFieldVectorGL3D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.0f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})* Matrix3::translation({0.5f, 0.5f})* Matrix3::rotation(180.0_degf)* - Matrix3::translation({-0.5f, -0.5f}) - ); + Matrix3::translation({-0.5f, -0.5f})) + .setLayer(0); /* ignored if not array */ textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & DistanceFieldVectorGL3D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.5f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})* Matrix3::translation(Vector2::xAxis(1.0f))* - Matrix3::scaling(Vector2::xScale(-1.0f)) - ); + Matrix3::scaling(Vector2::xScale(-1.0f))) + .setLayer(2); /* ignored if not array */ textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} - .setTextureMatrix(Matrix3{}); + .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & DistanceFieldVectorGL3D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.25f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})) + .setLayer(1); /* ignored if not array */ GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; Containers::Array drawData{2*data.uniformIncrement + 1}; @@ -1628,12 +2053,6 @@ void DistanceFieldVectorGLTest::renderMulti3D() { .setMaterialId(data.bindWithOffset ? 0 : 0); GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; - DistanceFieldVectorGL3D shader{DistanceFieldVectorGL3D::Configuration{} - .setFlags(DistanceFieldVectorGL3D::Flag::UniformBuffers|DistanceFieldVectorGL3D::Flag::TextureTransformation|data.flags) - .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; - shader.bindVectorTexture(vector); - /* Rebinding UBOs / SSBOs each time */ if(data.bindWithOffset) { shader.bindMaterialBuffer(materialUniform, diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGL_Test.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGL_Test.cpp index c7fa26382..1d6fbd1b9 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGL_Test.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGL_Test.cpp @@ -92,8 +92,8 @@ void DistanceFieldVectorGL_Test::debugFlag() { void DistanceFieldVectorGL_Test::debugFlags() { Containers::String out; - Debug{&out} << DistanceFieldVectorGL3D::Flags{DistanceFieldVectorGL3D::Flag::TextureTransformation|DistanceFieldVectorGL3D::Flag(0xf0)} << DistanceFieldVectorGL3D::Flags{}; - CORRADE_COMPARE(out, "Shaders::DistanceFieldVectorGL::Flag::TextureTransformation|Shaders::DistanceFieldVectorGL::Flag(0xf0) Shaders::DistanceFieldVectorGL::Flags{}\n"); + Debug{&out} << DistanceFieldVectorGL3D::Flags{DistanceFieldVectorGL3D::Flag::TextureTransformation|DistanceFieldVectorGL3D::Flag(0x80)} << DistanceFieldVectorGL3D::Flags{}; + CORRADE_COMPARE(out, "Shaders::DistanceFieldVectorGL::Flag::TextureTransformation|Shaders::DistanceFieldVectorGL::Flag(0x80) Shaders::DistanceFieldVectorGL::Flags{}\n"); } #ifndef MAGNUM_TARGET_GLES2 diff --git a/src/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index 9c4278397..bf7c6ef1f 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -61,6 +61,7 @@ #ifndef MAGNUM_TARGET_GLES2 #include "Magnum/GL/Extensions.h" #include "Magnum/GL/MeshView.h" +#include "Magnum/GL/TextureArray.h" #include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/GenerateIndices.h" #include "Magnum/Primitives/Circle.h" @@ -99,9 +100,12 @@ struct VectorGLTest: GL::OpenGLTester { #ifndef MAGNUM_TARGET_GLES2 template void setUniformUniformBuffersEnabled(); template void bindBufferUniformBuffersNotEnabled(); + template void bindTextureInvalid(); + template void bindTextureArrayInvalid(); #endif template void setTextureMatrixNotEnabled(); #ifndef MAGNUM_TARGET_GLES2 + template void setTextureLayerNotArray(); template void bindTextureTransformBufferNotEnabled(); #endif #ifndef MAGNUM_TARGET_GLES2 @@ -166,7 +170,11 @@ constexpr struct { VectorGL2D::Flags flags; } ConstructData[]{ {"", {}}, - {"texture transformation", VectorGL2D::Flag::TextureTransformation} + {"texture transformation", VectorGL2D::Flag::TextureTransformation}, + #ifndef MAGNUM_TARGET_GLES2 + {"texture arrays", VectorGL2D::Flag::TextureArrays}, + {"texture transformation + texture arrays", VectorGL2D::Flag::TextureTransformation|VectorGL2D::Flag::TextureArrays}, + #endif }; #ifndef MAGNUM_TARGET_GLES2 @@ -178,12 +186,14 @@ constexpr struct { {"classic fallback", {}, 1, 1}, {"", VectorGL2D::Flag::UniformBuffers, 1, 1}, {"texture transformation", VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 1, 1}, + {"texture arrays", VectorGL2D::Flag::TextureArrays, 1, 1}, + {"texture transformation + texture arrays", VectorGL2D::Flag::TextureTransformation|VectorGL2D::Flag::TextureArrays, 1, 1}, /* SwiftShader has 256 uniform vectors at most, per-draw is 4+1 in 3D case and 3+1 in 2D, per-material 3 */ {"multiple materials, draws", VectorGL2D::Flag::UniformBuffers, 15, 42}, - {"multidraw with all the things", VectorGL2D::Flag::MultiDraw|VectorGL2D::Flag::TextureTransformation, 15, 42}, + {"multidraw with all the things", VectorGL2D::Flag::MultiDraw|VectorGL2D::Flag::TextureTransformation|VectorGL2D::Flag::TextureArrays, 15, 42}, #ifndef MAGNUM_TARGET_WEBGL - {"shader storage + multidraw with all the things", VectorGL2D::Flag::ShaderStorageBuffers|VectorGL2D::Flag::MultiDraw|VectorGL2D::Flag::TextureTransformation, 0, 0} + {"shader storage + multidraw with all the things", VectorGL2D::Flag::ShaderStorageBuffers|VectorGL2D::Flag::MultiDraw|VectorGL2D::Flag::TextureTransformation|VectorGL2D::Flag::TextureArrays, 0, 0} #endif }; #endif @@ -207,17 +217,68 @@ const struct { const char* name; VectorGL2D::Flags flags; Matrix3 textureTransformation; + bool arrayTextureCoordinates; + Int layerAttribute, layerUniform; Color4 backgroundColor, color; const char* file2D; const char* file3D; bool flip; } RenderData[] { - {"texture transformation", VectorGL2D::Flag::TextureTransformation, + {"texture transformation", + VectorGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + false, 0, 0, 0x00000000_rgbaf, 0xffffff_rgbf, + "defaults.tga", "defaults.tga", true}, + {"", + {}, {}, + false, 0, 0, 0x9999ff_rgbf, 0xffff99_rgbf, + "vector2D.tga", "vector3D.tga", false}, + #ifndef MAGNUM_TARGET_GLES2 + {"array texture, 2D coordinates, first layer", + VectorGL2D::Flag::TextureArrays, {}, + false, 0, 0, 0x9999ff_rgbf, 0xffff99_rgbf, + "vector2D.tga", "vector3D.tga", false}, + {"array texture, 2D coordinates, arbitrary layer from uniform", + VectorGL2D::Flag::TextureArrays, {}, + false, 0, 6, 0x9999ff_rgbf, 0xffff99_rgbf, + "vector2D.tga", "vector3D.tga", false}, + {"array texture, 2D coordinates, texture transformation, arbitrary layer from uniform", + VectorGL2D::Flag::TextureArrays|VectorGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + false, 0, 6, 0x00000000_rgbaf, 0xffffff_rgbf, + "defaults.tga", "defaults.tga", true}, + {"array texture, array coordinates, first layer", + VectorGL2D::Flag::TextureArrays, {}, + true, 0, 0, 0x9999ff_rgbf, 0xffff99_rgbf, + "vector2D.tga", "vector3D.tga", false}, + {"array texture, array coordinates, arbitrary layer from attribute", + VectorGL2D::Flag::TextureArrays, {}, + true, 6, 0, 0x9999ff_rgbf, 0xffff99_rgbf, + "vector2D.tga", "vector3D.tga", false}, + {"array texture, array coordinates, arbitrary layer from uniform", + VectorGL2D::Flag::TextureArrays, {}, + true, 0, 6, 0x9999ff_rgbf, 0xffff99_rgbf, + "vector2D.tga", "vector3D.tga", false}, + {"array texture, array coordinates, arbitrary layer from both", + VectorGL2D::Flag::TextureArrays, {}, + true, 2, 4, 0x9999ff_rgbf, 0xffff99_rgbf, + "vector2D.tga", "vector3D.tga", false}, + {"array texture, array coordinates, texture transformation, arbitrary layer from attribute", + VectorGL2D::Flag::TextureArrays|VectorGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + true, 6, 0, 0x00000000_rgbaf, 0xffffff_rgbf, + "defaults.tga", "defaults.tga", true}, + {"array texture, array coordinates, texture transformation, arbitrary layer from uniform", + VectorGL2D::Flag::TextureArrays|VectorGL2D::Flag::TextureTransformation, Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), - 0x00000000_rgbaf, 0xffffff_rgbf, + true, 0, 6, 0x00000000_rgbaf, 0xffffff_rgbf, "defaults.tga", "defaults.tga", true}, - {"", {}, {}, 0x9999ff_rgbf, 0xffff99_rgbf, - "vector2D.tga", "vector3D.tga", false} + {"array texture, array coordinates, texture transformation, arbitrary layer from both", + VectorGL2D::Flag::TextureArrays|VectorGL2D::Flag::TextureTransformation, + Matrix3::translation(Vector2{1.0f})*Matrix3::scaling(Vector2{-1.0f}), + true, 2, 4, 0x00000000_rgbaf, 0xffffff_rgbf, + "defaults.tga", "defaults.tga", true}, + #endif }; #ifndef MAGNUM_TARGET_GLES2 @@ -235,31 +296,55 @@ constexpr struct { {}, 1, 1, true, 16, /* Minor differences on ARM Mali */ 1.34f, 0.02f}, + {"bind with offset, texture array", "multidraw2D.tga", "multidraw3D.tga", + VectorGL2D::Flag::TextureArrays, 1, 1, true, 16, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, #ifndef MAGNUM_TARGET_WEBGL {"bind with offset, shader storage", "multidraw2D.tga", "multidraw3D.tga", VectorGL2D::Flag::ShaderStorageBuffers, 0, 0, true, 16, /* Minor differences on ARM Mali */ 1.34f, 0.02f}, + {"bind with offset, texture array, shader storage", "multidraw2D.tga", "multidraw3D.tga", + VectorGL2D::Flag::TextureArrays|VectorGL2D::Flag::ShaderStorageBuffers, 0, 0, true, 16, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, #endif {"draw offset", "multidraw2D.tga", "multidraw3D.tga", {}, 2, 3, false, 1, /* Minor differences on ARM Mali */ 1.34f, 0.02f}, + {"draw offset, texture array", "multidraw2D.tga", "multidraw3D.tga", + VectorGL2D::Flag::TextureArrays, 2, 3, false, 1, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, #ifndef MAGNUM_TARGET_WEBGL {"draw offset, shader storage", "multidraw2D.tga", "multidraw3D.tga", VectorGL2D::Flag::ShaderStorageBuffers, 0, 0, false, 1, /* Minor differences on ARM Mali */ 1.34f, 0.02f}, + {"draw offset, texture array, shader storage", "multidraw2D.tga", "multidraw3D.tga", + VectorGL2D::Flag::TextureArrays|VectorGL2D::Flag::ShaderStorageBuffers, 0, 0, false, 1, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, #endif {"multidraw", "multidraw2D.tga", "multidraw3D.tga", VectorGL2D::Flag::MultiDraw, 2, 3, false, 1, /* Minor differences on ARM Mali */ 1.34f, 0.02f}, + {"multidraw, texture array", "multidraw2D.tga", "multidraw3D.tga", + VectorGL2D::Flag::TextureArrays|VectorGL2D::Flag::MultiDraw, 2, 3, false, 1, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, #ifndef MAGNUM_TARGET_WEBGL {"multidraw, shader storage", "multidraw2D.tga", "multidraw3D.tga", VectorGL2D::Flag::ShaderStorageBuffers|VectorGL2D::Flag::MultiDraw, 0, 0, false, 1, /* Minor differences on ARM Mali */ 1.34f, 0.02f}, + {"multidraw, texture array, shader storage", "multidraw2D.tga", "multidraw3D.tga", + VectorGL2D::Flag::TextureArrays|VectorGL2D::Flag::ShaderStorageBuffers|VectorGL2D::Flag::MultiDraw, 0, 0, false, 1, + /* Minor differences on ARM Mali */ + 1.34f, 0.02f}, #endif }; #endif @@ -308,10 +393,16 @@ VectorGLTest::VectorGLTest() { &VectorGLTest::setUniformUniformBuffersEnabled<3>, &VectorGLTest::bindBufferUniformBuffersNotEnabled<2>, &VectorGLTest::bindBufferUniformBuffersNotEnabled<3>, + &VectorGLTest::bindTextureInvalid<2>, + &VectorGLTest::bindTextureInvalid<3>, + &VectorGLTest::bindTextureArrayInvalid<2>, + &VectorGLTest::bindTextureArrayInvalid<3>, #endif &VectorGLTest::setTextureMatrixNotEnabled<2>, &VectorGLTest::setTextureMatrixNotEnabled<3>, #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::setTextureLayerNotArray<2>, + &VectorGLTest::setTextureLayerNotArray<3>, &VectorGLTest::bindTextureTransformBufferNotEnabled<2>, &VectorGLTest::bindTextureTransformBufferNotEnabled<3>, #endif @@ -628,11 +719,13 @@ template void VectorGLTest::setUniformUniformBuffersEnab Error redirectError{&out}; shader.setTransformationProjectionMatrix({}) .setTextureMatrix({}) + .setTextureLayer({}) .setBackgroundColor({}) .setColor({}); CORRADE_COMPARE(out, "Shaders::VectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" "Shaders::VectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::VectorGL::setTextureLayer(): the shader was created with uniform buffers enabled\n" "Shaders::VectorGL::setBackgroundColor(): the shader was created with uniform buffers enabled\n" "Shaders::VectorGL::setColor(): the shader was created with uniform buffers enabled\n"); } @@ -667,6 +760,45 @@ template void VectorGLTest::bindBufferUniformBuffersNotE "Shaders::VectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::VectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } + +template void VectorGLTest::bindTextureInvalid() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + GL::Texture2D texture; + VectorGL shader{typename VectorGL::Configuration{} + .setFlags(VectorGL::Flag::TextureArrays)}; + + Containers::String out; + Error redirectError{&out}; + shader.bindVectorTexture(texture); + CORRADE_COMPARE(out, "Shaders::VectorGL::bindVectorTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead\n"); +} + +template void VectorGLTest::bindTextureArrayInvalid() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + GL::Texture2DArray texture; + VectorGL shader; + + Containers::String out; + Error redirectError{&out}; + shader.bindVectorTexture(texture); + CORRADE_COMPARE(out, "Shaders::VectorGL::bindVectorTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead\n"); +} #endif template void VectorGLTest::setTextureMatrixNotEnabled() { @@ -684,6 +816,25 @@ template void VectorGLTest::setTextureMatrixNotEnabled() } #ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::setTextureLayerNotArray() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + GL::Texture2D texture; + VectorGL shader; + + Containers::String out; + Error redirectError{&out}; + shader.setTextureLayer(37); + CORRADE_COMPARE(out, "Shaders::VectorGL::setTextureLayer(): the shader was not created with texture arrays enabled\n"); +} + template void VectorGLTest::bindTextureTransformBufferNotEnabled() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -1007,30 +1158,81 @@ template void VectorGLTest::render2D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh square = MeshTools::compile(Primitives::squareSolid(Primitives::SquareFlag::TextureCoordinates)); + /** @todo clean this up once MeshData (and primitives?) support array + coordinates directly */ + struct Vertex { + Vector2 position; + Vector3 textureCoords; + } squareData[] { + {{ 1.0f, -1.0f}, {1.0f, 0.0f, Float(data.layerAttribute)}}, + {{ 1.0f, 1.0f}, {1.0f, 1.0f, Float(data.layerAttribute)}}, + {{-1.0f, -1.0f}, {0.0f, 0.0f, Float(data.layerAttribute)}}, + {{-1.0f, 1.0f}, {0.0f, 1.0f, Float(data.layerAttribute)}} + }; + GL::Mesh square{GL::MeshPrimitive::TriangleStrip}; + #ifndef MAGNUM_TARGET_GLES2 + if(data.arrayTextureCoordinates) { + square.addVertexBuffer(GL::Buffer{squareData}, 0, + GenericGL2D::Position{}, + GenericGL2D::TextureArrayCoordinates{}); + } else + #endif + { + square.addVertexBuffer(GL::Buffer{squareData}, 0, + GenericGL2D::Position{}, + GenericGL2D::TextureCoordinates{}, + sizeof(Float)); + } + square.setCount(4); + + VectorGL2D::Flags flags = data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag & VectorGL2D::Flag::UniformBuffers && (data.flags & VectorGL2D::Flag::TextureArrays) && !(data.flags & VectorGL2D::Flag::TextureTransformation) && data.layerUniform) { + CORRADE_INFO("Texture arrays with layer passed from a uniform currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= VectorGL2D::Flag::TextureTransformation; + } + #endif + VectorGL2D shader{VectorGL2D::Configuration{} + .setFlags(flags)}; Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); - GL::Texture2D texture; + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + #endif Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Path::join(_testDir, "TestFiles/vector.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge); - - #ifdef MAGNUM_TARGET_GLES2 - /* Don't want to bother with the fiasco of single-channel formats and - texture storage extensions on ES2 */ - texture.setImage(0, TextureFormatR, *image); - #else - texture.setStorage(1, TextureFormatR, image->size()) - .setSubImage(0, {}, *image); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & VectorGL2D::Flag::TextureArrays) { + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatR, {image->size(), data.layerUniform + data.layerAttribute + 1}) + .setSubImage(0, {0, 0, data.layerUniform + data.layerAttribute}, ImageView2D{*image}); + + shader.bindVectorTexture(textureArray); + } else #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge); + + #ifdef MAGNUM_TARGET_GLES2 + /* Don't want to bother with the fiasco of single-channel formats and + texture storage extensions on ES2 */ + texture.setImage(0, TextureFormatR, *image); + #else + texture.setStorage(1, TextureFormatR, image->size()) + .setSubImage(0, {}, *image); + #endif - VectorGL2D shader{VectorGL2D::Configuration{} - .setFlags(data.flags|flag)}; - shader.bindVectorTexture(texture); + shader.bindVectorTexture(texture); + } if(flag == VectorGL2D::Flag{}) { shader.setBackgroundColor(data.backgroundColor) @@ -1040,6 +1242,10 @@ template void VectorGLTest::render2D() { else shader.setTransformationProjectionMatrix( Matrix3::projection({2.1f, 2.1f})* Matrix3::rotation(5.0_degf)); + #ifndef MAGNUM_TARGET_GLES2 + if(data.layerUniform != 0) /* to verify the default */ + shader.setTextureLayer(data.layerUniform); + #endif shader.draw(square); } #ifndef MAGNUM_TARGET_GLES2 @@ -1063,13 +1269,14 @@ template void VectorGLTest::render2D() { GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(data.textureTransformation) + .setLayer(data.layerUniform) }}; GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { VectorMaterialUniform{} .setBackgroundColor(data.backgroundColor) .setColor(data.color) }}; - if(data.flags & VectorGL2D::Flag::TextureTransformation) + if(flags & VectorGL2D::Flag::TextureTransformation) shader.bindTextureTransformationBuffer(textureTransformationUniform); shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) @@ -1135,30 +1342,79 @@ template void VectorGLTest::render3D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); - GL::Mesh plane = MeshTools::compile(Primitives::planeSolid(Primitives::PlaneFlag::TextureCoordinates)); + struct Vertex { + Vector3 position; + Vector3 textureCoords; + } planeData[] { + {{ 1.0f, -1.0f, 0.0f}, {1.0f, 0.0f, Float(data.layerAttribute)}}, + {{ 1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, Float(data.layerAttribute)}}, + {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, Float(data.layerAttribute)}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, Float(data.layerAttribute)}} + }; + GL::Mesh plane{GL::MeshPrimitive::TriangleStrip}; + #ifndef MAGNUM_TARGET_GLES2 + if(data.arrayTextureCoordinates) { + plane.addVertexBuffer(GL::Buffer{planeData}, 0, + GenericGL3D::Position{}, + GenericGL3D::TextureArrayCoordinates{}); + } else + #endif + { + plane.addVertexBuffer(GL::Buffer{planeData}, 0, + GenericGL3D::Position{}, + GenericGL3D::TextureCoordinates{}, + sizeof(Float)); + } + plane.setCount(4); + + VectorGL3D::Flags flags = data.flags|flag; + #ifndef MAGNUM_TARGET_GLES2 + if(flag & VectorGL3D::Flag::UniformBuffers && (data.flags & VectorGL3D::Flag::TextureArrays) && !(data.flags & VectorGL3D::Flag::TextureTransformation) && data.layerUniform) { + CORRADE_INFO("Texture arrays with layer passed from a uniform currently require texture transformation if UBOs are used, enabling implicitly."); + flags |= VectorGL3D::Flag::TextureTransformation; + } + #endif + VectorGL3D shader{VectorGL3D::Configuration{} + .setFlags(flags)}; Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); - GL::Texture2D texture; + GL::Texture2D texture{NoCreate}; + #ifndef MAGNUM_TARGET_GLES2 + GL::Texture2DArray textureArray{NoCreate}; + #endif Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Path::join(_testDir, "TestFiles/vector.tga")) && (image = importer->image2D(0))); - texture.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge); - - #ifdef MAGNUM_TARGET_GLES2 - /* Don't want to bother with the fiasco of single-channel formats and - texture storage extensions on ES2 */ - texture.setImage(0, TextureFormatR, *image); - #else - texture.setStorage(1, TextureFormatR, image->size()) - .setSubImage(0, {}, *image); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & VectorGL3D::Flag::TextureArrays) { + textureArray = GL::Texture2DArray{}; + textureArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, TextureFormatR, {image->size(), data.layerUniform + data.layerAttribute + 1}) + .setSubImage(0, {0, 0, data.layerUniform + data.layerAttribute}, ImageView2D{*image}); + + shader.bindVectorTexture(textureArray); + } else #endif + { + texture = GL::Texture2D{}; + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge); + + #ifdef MAGNUM_TARGET_GLES2 + /* Don't want to bother with the fiasco of single-channel formats and + texture storage extensions on ES2 */ + texture.setImage(0, TextureFormatR, *image); + #else + texture.setStorage(1, TextureFormatR, image->size()) + .setSubImage(0, {}, *image); + #endif - VectorGL3D shader{VectorGL3D::Configuration{} - .setFlags(data.flags|flag)}; - shader.bindVectorTexture(texture); + shader.bindVectorTexture(texture); + } if(flag == VectorGL3D::Flag{}) { shader.setBackgroundColor(data.backgroundColor) @@ -1170,6 +1426,10 @@ template void VectorGLTest::render3D() { Matrix4::translation(Vector3::zAxis(-2.15f))* Matrix4::rotationY(-15.0_degf)* Matrix4::rotationZ(15.0_degf)); + #ifndef MAGNUM_TARGET_GLES2 + if(data.layerUniform != 0) /* to verify the default */ + shader.setTextureLayer(data.layerUniform); + #endif shader.draw(plane); } #ifndef MAGNUM_TARGET_GLES2 @@ -1195,13 +1455,14 @@ template void VectorGLTest::render3D() { GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(data.textureTransformation) + .setLayer(data.layerUniform) }}; GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { VectorMaterialUniform{} .setBackgroundColor(data.backgroundColor) .setColor(data.color) }}; - if(data.flags & VectorGL3D::Flag::TextureTransformation) + if(flags & VectorGL3D::Flag::TextureTransformation) shader.bindTextureTransformationBuffer(textureTransformationUniform); shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) @@ -1275,6 +1536,11 @@ void VectorGLTest::renderMulti2D() { CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); #endif + VectorGL2D shader{VectorGL2D::Configuration{} + .setFlags(VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation|data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount)}; + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1284,21 +1550,85 @@ void VectorGLTest::renderMulti2D() { Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Path::join(_testDir, "TestFiles/vector.tga")) && (image = importer->image2D(0))); - GL::Texture2D vector; - vector.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, GL::TextureFormat::R8, image->size()) - .setSubImage(0, {}, *image); - /* Circle is a fan, plane is a strip, make it indexed first */ + /* For arrays we the original image three times to different offsets in + three different slices */ + GL::Texture2D vector{NoCreate}; + GL::Texture2DArray vectorArray{NoCreate}; + if(data.flags & VectorGL2D::Flag::TextureArrays) { + Vector3i size{image->size().x(), image->size().y()*2, 6}; + + vectorArray = GL::Texture2DArray{}; + vectorArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, size) + /* Clear to all zeros for reproducible output */ + .setSubImage(0, {}, Image3D{PixelFormat::R8Unorm, size, Containers::Array{ValueInit, std::size_t(size.product())}}) + .setSubImage(0, {0, size.y()/4, 1}, ImageView2D{*image}) + .setSubImage(0, {0, size.y()/2, 3}, ImageView2D{*image}) + .setSubImage(0, {0, 0, 5}, ImageView2D{*image}); + + shader.bindVectorTexture(vectorArray); + } else { + vector = GL::Texture2D{}; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + shader.bindVectorTexture(vector); + } + + /* Circle is a fan, square is a strip, make it indexed first */ Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates)); Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid( Primitives::SquareFlag::TextureCoordinates)); Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3, Primitives::Circle2DFlag::TextureCoordinates)); - GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + + /* Assuming the texture coordinates are the last attribute, add a four-byte + padding after, which we subsequently abuse as the layer index */ + /** @todo clean this up once MeshData (and primitives?) support array + coordinates directly */ + Trade::MeshData meshData = MeshTools::interleave( + MeshTools::concatenate({circleData, squareData, triangleData}), + {Trade::MeshAttributeData{4}}); + CORRADE_COMPARE(meshData.attributeCount(), 2); + CORRADE_COMPARE(meshData.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(meshData.attributeName(1), Trade::MeshAttribute::TextureCoordinates); + /* Manual cast because the real attribute type is Vector2 */ + const Containers::StridedArrayView1D textureCoordinates = Containers::arrayCast(meshData.mutableAttribute(Trade::MeshAttribute::TextureCoordinates)); + + /* The circle will use the last slice, coming from just the attribute + alone */ + for(UnsignedInt i = 0; i != circleData.vertexCount(); ++i) + textureCoordinates[i].z() = 5; + /* The square will use the third slice, coming from both the attribute and + the uniform */ + for(UnsignedInt i = 0; i != squareData.vertexCount(); ++i) + textureCoordinates[circleData.vertexCount() + i].z() = 1; + /* The triangle will use the second slice, coming from just the uniform. + The memory isn't initialized by default however, so set the attribute to + 0. */ + for(UnsignedInt i = 0; i != triangleData.vertexCount(); ++i) + textureCoordinates[circleData.vertexCount() + squareData.vertexCount() + i].z() = 0; + + /* Making some assumptions about the layout for simplicity */ + CORRADE_COMPARE(meshData.attributeStride(0), sizeof(Vector2) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeStride(1), sizeof(Vector2) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeOffset(0), 0); + CORRADE_COMPARE(meshData.attributeOffset(1), sizeof(Vector2)); + GL::Mesh mesh; + mesh.addVertexBuffer(GL::Buffer{meshData.vertexData()}, 0, + GenericGL2D::Position{}, + GenericGL2D::TextureArrayCoordinates{}) + .setIndexBuffer(GL::Buffer{GL::Buffer::TargetHint::ElementArray, meshData.indexData()}, 0, + meshData.indexType()) + .setCount(meshData.indexCount()); + GL::MeshView circle{mesh}; circle.setCount(circleData.indexCount()); GL::MeshView square{mesh}; @@ -1346,17 +1676,30 @@ void VectorGLTest::renderMulti2D() { Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & VectorGL2D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.0f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})* Matrix3::translation({0.5f, 0.5f})* Matrix3::rotation(180.0_degf)* - Matrix3::translation({-0.5f, -0.5f}) - ); + Matrix3::translation({-0.5f, -0.5f})) + .setLayer(0); /* ignored if not array */ textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & VectorGL2D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.5f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})* Matrix3::translation(Vector2::xAxis(1.0f))* - Matrix3::scaling(Vector2::xScale(-1.0f)) - ); + Matrix3::scaling(Vector2::xScale(-1.0f))) + .setLayer(2); /* ignored if not array */ textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} - .setTextureMatrix(Matrix3{}); + .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & VectorGL2D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.25f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})) + .setLayer(1); /* ignored if not array */ GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; Containers::Array drawData{2*data.uniformIncrement + 1}; @@ -1370,12 +1713,6 @@ void VectorGLTest::renderMulti2D() { .setMaterialId(data.bindWithOffset ? 0 : 1); GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; - VectorGL2D shader{VectorGL2D::Configuration{} - .setFlags(VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation|data.flags) - .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; - shader.bindVectorTexture(vector); - /* Rebinding UBOs / SSBOs each time */ if(data.bindWithOffset) { shader.bindMaterialBuffer(materialUniform, @@ -1495,6 +1832,11 @@ void VectorGLTest::renderMulti3D() { CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); #endif + VectorGL3D shader{VectorGL3D::Configuration{} + .setFlags(VectorGL3D::Flag::UniformBuffers|VectorGL3D::Flag::TextureTransformation|data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount)}; + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1504,12 +1846,36 @@ void VectorGLTest::renderMulti3D() { Containers::Optional image; CORRADE_VERIFY(importer->openFile(Utility::Path::join(_testDir, "TestFiles/vector.tga")) && (image = importer->image2D(0))); - GL::Texture2D vector; - vector.setMinificationFilter(GL::SamplerFilter::Linear) - .setMagnificationFilter(GL::SamplerFilter::Linear) - .setWrapping(GL::SamplerWrapping::ClampToEdge) - .setStorage(1, GL::TextureFormat::R8, image->size()) - .setSubImage(0, {}, *image); + + /* For arrays we the original image three times to different offsets in + three different slices */ + GL::Texture2D vector{NoCreate}; + GL::Texture2DArray vectorArray{NoCreate}; + if(data.flags & VectorGL2D::Flag::TextureArrays) { + Vector3i size{image->size().x(), image->size().y()*2, 6}; + + vectorArray = GL::Texture2DArray{}; + vectorArray.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, size) + /* Clear to all zeros for reproducible output */ + .setSubImage(0, {}, Image3D{PixelFormat::R8Unorm, size, Containers::Array{ValueInit, std::size_t(size.product())}}) + .setSubImage(0, {0, size.y()/4, 1}, ImageView2D{*image}) + .setSubImage(0, {0, size.y()/2, 3}, ImageView2D{*image}) + .setSubImage(0, {0, 0, 5}, ImageView2D{*image}); + + shader.bindVectorTexture(vectorArray); + } else { + vector = GL::Texture2D{}; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + shader.bindVectorTexture(vector); + } Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates); @@ -1518,7 +1884,51 @@ void VectorGLTest::renderMulti3D() { Primitives::PlaneFlag::TextureCoordinates)); Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, Primitives::ConeFlag::TextureCoordinates); - GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + + /* Assuming the texture coordinates are the last attribute, add a four-byte + padding after, which we subsequently abuse as the layer index */ + /** @todo clean this up once MeshData (and primitives?) support array + coordinates directly */ + Trade::MeshData meshData = MeshTools::interleave( + MeshTools::concatenate({sphereData, planeData, coneData}), + {Trade::MeshAttributeData{4}}); + CORRADE_COMPARE(meshData.attributeCount(), 3); + CORRADE_COMPARE(meshData.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(meshData.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(meshData.attributeName(2), Trade::MeshAttribute::TextureCoordinates); + /* Manual cast because the real attribute type is Vector2 */ + const Containers::StridedArrayView1D textureCoordinates = Containers::arrayCast(meshData.mutableAttribute(Trade::MeshAttribute::TextureCoordinates)); + + /* The sphere will use the last slice, coming from just the attribute + alone */ + for(UnsignedInt i = 0; i != sphereData.vertexCount(); ++i) + textureCoordinates[i].z() = 5; + /* The plane will use the third slice, coming from both the attribute and + the uniform */ + for(UnsignedInt i = 0; i != planeData.vertexCount(); ++i) + textureCoordinates[sphereData.vertexCount() + i].z() = 1; + /* The cone will use the first slice, coming from just the uniform. The + memory isn't initialized by default however, so set the attribute to + 0. */ + for(UnsignedInt i = 0; i != coneData.vertexCount(); ++i) + textureCoordinates[sphereData.vertexCount() + planeData.vertexCount() + i].z() = 0; + + /* Making some assumptions about the layout for simplicity */ + CORRADE_COMPARE(meshData.attributeStride(0), sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeStride(1), sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeStride(2), sizeof(Vector3) + sizeof(Vector3) + sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeOffset(0), 0); + CORRADE_COMPARE(meshData.attributeOffset(1), sizeof(Vector3)); + CORRADE_COMPARE(meshData.attributeOffset(2), sizeof(Vector3) + sizeof(Vector3)); + GL::Mesh mesh; + mesh.addVertexBuffer(GL::Buffer{meshData.vertexData()}, 0, + GenericGL3D::Position{}, + GenericGL3D::Normal{}, + GenericGL3D::TextureArrayCoordinates{}) + .setIndexBuffer(GL::Buffer{GL::Buffer::TargetHint::ElementArray, meshData.indexData()}, 0, + meshData.indexType()) + .setCount(meshData.indexCount()); + GL::MeshView sphere{mesh}; sphere.setCount(sphereData.indexCount()); GL::MeshView plane{mesh}; @@ -1571,17 +1981,30 @@ void VectorGLTest::renderMulti3D() { Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & VectorGL3D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.0f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})* Matrix3::translation({0.5f, 0.5f})* Matrix3::rotation(180.0_degf)* - Matrix3::translation({-0.5f, -0.5f}) - ); + Matrix3::translation({-0.5f, -0.5f})) + .setLayer(0); /* ignored if not array */ textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & VectorGL3D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.5f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})* Matrix3::translation(Vector2::xAxis(1.0f))* - Matrix3::scaling(Vector2::xScale(-1.0f)) - ); + Matrix3::scaling(Vector2::xScale(-1.0f))) + .setLayer(2); /* ignored if not array */ textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} - .setTextureMatrix(Matrix3{}); + .setTextureMatrix( + /* Additional Y shift + scale in the array slice */ + (data.flags & VectorGL3D::Flag::TextureArrays ? + Matrix3::translation(Vector2::yAxis(0.25f))* + Matrix3::scaling(Vector2::yScale(0.5f)) : Matrix3{})) + .setLayer(1); /* ignored if not array */ GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; Containers::Array drawData{2*data.uniformIncrement + 1}; @@ -1595,12 +2018,6 @@ void VectorGLTest::renderMulti3D() { .setMaterialId(data.bindWithOffset ? 0 : 1); GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; - VectorGL3D shader{VectorGL3D::Configuration{} - .setFlags(VectorGL3D::Flag::UniformBuffers|VectorGL3D::Flag::TextureTransformation|data.flags) - .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; - shader.bindVectorTexture(vector); - /* Rebinding UBOs / SSBOs each time */ if(data.bindWithOffset) { shader.bindMaterialBuffer(materialUniform, diff --git a/src/Magnum/Shaders/Test/VectorGL_Test.cpp b/src/Magnum/Shaders/Test/VectorGL_Test.cpp index 7f1fb5c77..584db160d 100644 --- a/src/Magnum/Shaders/Test/VectorGL_Test.cpp +++ b/src/Magnum/Shaders/Test/VectorGL_Test.cpp @@ -92,8 +92,8 @@ void VectorGL_Test::debugFlag() { void VectorGL_Test::debugFlags() { Containers::String out; - Debug{&out} << VectorGL3D::Flags{VectorGL3D::Flag::TextureTransformation|VectorGL3D::Flag(0xf0)} << VectorGL3D::Flags{}; - CORRADE_COMPARE(out, "Shaders::VectorGL::Flag::TextureTransformation|Shaders::VectorGL::Flag(0xf0) Shaders::VectorGL::Flags{}\n"); + Debug{&out} << VectorGL3D::Flags{VectorGL3D::Flag::TextureTransformation|VectorGL3D::Flag(0x80)} << VectorGL3D::Flags{}; + CORRADE_COMPARE(out, "Shaders::VectorGL::Flag::TextureTransformation|Shaders::VectorGL::Flag(0x80) Shaders::VectorGL::Flags{}\n"); } #ifndef MAGNUM_TARGET_GLES2 diff --git a/src/Magnum/Shaders/Vector.frag b/src/Magnum/Shaders/Vector.frag index 4f5c1e64a..c248de88e 100644 --- a/src/Magnum/Shaders/Vector.frag +++ b/src/Magnum/Shaders/Vector.frag @@ -42,12 +42,12 @@ #ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 2) +layout(location = 3) #endif uniform lowp vec4 backgroundColor; /* defaults to zero */ #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 3) +layout(location = 4) #endif uniform lowp vec4 color #ifndef GL_ES @@ -120,11 +120,23 @@ layout(std140 #ifdef EXPLICIT_BINDING layout(binding = 6) #endif -uniform lowp sampler2D vectorTexture; +uniform lowp + #ifndef TEXTURE_ARRAYS + sampler2D + #else + sampler2DArray + #endif + vectorTexture; /* Inputs */ -in mediump vec2 interpolatedTextureCoordinates; +in mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + interpolatedTextureCoordinates; #ifdef MULTI_DRAW flat in highp uint drawId; diff --git a/src/Magnum/Shaders/Vector.vert b/src/Magnum/Shaders/Vector.vert index e6277303f..17f807487 100644 --- a/src/Magnum/Shaders/Vector.vert +++ b/src/Magnum/Shaders/Vector.vert @@ -24,6 +24,10 @@ DEALINGS IN THE SOFTWARE. */ +#if defined(UNIFORM_BUFFERS) && defined(TEXTURE_ARRAYS) && !defined(GL_ES) +#extension GL_ARB_shader_bit_encoding: require +#endif + #if defined(SHADER_STORAGE_BUFFERS) && !defined(GL_ES) #extension GL_ARB_shader_storage_buffer_object: require #endif @@ -78,6 +82,14 @@ uniform mediump mat3 textureMatrix ; #endif +#ifdef TEXTURE_ARRAYS +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 2) +#endif +/* mediump is just 2^10, which might not be enough, this is 2^16 */ +uniform highp uint textureLayer; /* defaults to zero */ +#endif + /* Uniform / shader storage buffers */ #else @@ -127,8 +139,9 @@ layout(std140 #ifdef TEXTURE_TRANSFORMATION struct TextureTransformationUniform { highp vec4 rotationScaling; - highp vec4 offsetReservedReserved; - #define textureTransformation_offset offsetReservedReserved.xy + highp vec4 offsetLayerReserved; + #define textureTransformation_offset offsetLayerReserved.xy + #define textureTransformation_layer offsetLayerReserved.z }; layout(std140 @@ -157,11 +170,23 @@ in highp vec4 position; #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = TEXTURECOORDINATES_ATTRIBUTE_LOCATION) #endif -in mediump vec2 textureCoordinates; +in mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + textureCoordinates; /* Outputs */ -out mediump vec2 interpolatedTextureCoordinates; +out mediump + #ifndef TEXTURE_ARRAYS + vec2 + #else + vec3 + #endif + interpolatedTextureCoordinates; #ifdef MULTI_DRAW flat out highp uint drawId; @@ -190,6 +215,9 @@ void main() { #endif #ifdef TEXTURE_TRANSFORMATION mediump const mat3 textureMatrix = mat3(textureTransformations[drawId].rotationScaling.xy, 0.0, textureTransformations[drawId].rotationScaling.zw, 0.0, textureTransformations[drawId].textureTransformation_offset, 1.0); + #ifdef TEXTURE_ARRAYS + highp const uint textureLayer = floatBitsToUint(textureTransformations[drawId].textureTransformation_layer); + #endif #endif #endif @@ -201,11 +229,18 @@ void main() { #error #endif - interpolatedTextureCoordinates = + interpolatedTextureCoordinates.xy = #ifdef TEXTURE_TRANSFORMATION - (textureMatrix*vec3(textureCoordinates, 1.0)).xy + (textureMatrix*vec3(textureCoordinates.xy, 1.0)).xy #else - textureCoordinates + textureCoordinates.xy #endif ; + #ifdef TEXTURE_ARRAYS + interpolatedTextureCoordinates.z = textureCoordinates.z + #if !defined(UNIFORM_BUFFERS) || defined(TEXTURE_TRANSFORMATION) + + float(textureLayer) + #endif + ; + #endif } diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index 8289753c8..4b576ac29 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -45,6 +45,7 @@ #include #include "Magnum/GL/Buffer.h" +#include "Magnum/GL/TextureArray.h" #endif #ifdef MAGNUM_BUILD_STATIC @@ -110,6 +111,10 @@ template typename VectorGL::CompileState Vec #endif } #endif + #ifndef MAGNUM_TARGET_GLES + if(configuration.flags() >= Flag::TextureArrays) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::EXT::texture_array); + #endif #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ @@ -133,6 +138,9 @@ template typename VectorGL::CompileState Vec GL::Shader vert{version, GL::Shader::Type::Vertex}; vert.addSource(rs.getString("compatibility.glsl"_s)) .addSource(configuration.flags() & Flag::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n"_s : ""_s) + #ifndef MAGNUM_TARGET_GLES2 + .addSource(configuration.flags() & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n"_s : ""_s) + #endif .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n"_s : "#define THREE_DIMENSIONS\n"_s); #ifndef MAGNUM_TARGET_GLES2 if(configuration.flags() >= Flag::UniformBuffers) { @@ -159,7 +167,11 @@ template typename VectorGL::CompileState Vec .submitCompile(); GL::Shader frag{version, GL::Shader::Type::Fragment}; - frag.addSource(rs.getString("compatibility.glsl"_s)); + frag.addSource(rs.getString("compatibility.glsl"_s)) + #ifndef MAGNUM_TARGET_GLES2 + .addSource(configuration.flags() & Flag::TextureArrays ? "#define TEXTURE_ARRAYS\n"_s : ""_s) + #endif + ; #ifndef MAGNUM_TARGET_GLES2 if(configuration.flags() >= Flag::UniformBuffers) { #ifndef MAGNUM_TARGET_WEBGL @@ -260,6 +272,10 @@ template VectorGL::VectorGL(CompileState&& s _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"_s); if(_flags & Flag::TextureTransformation) _textureMatrixUniform = uniformLocation("textureMatrix"_s); + #ifndef MAGNUM_TARGET_GLES2 + if(_flags & Flag::TextureArrays) + _textureLayerUniform = uniformLocation("textureLayer"_s); + #endif _backgroundColorUniform = uniformLocation("backgroundColor"_s); _colorUniform = uniformLocation("color"_s); } @@ -299,6 +315,7 @@ template VectorGL::VectorGL(CompileState&& s setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); if(_flags & Flag::TextureTransformation) setTextureMatrix(Matrix3{Math::IdentityInit}); + /* Texture layer is zero by default */ /* Background color is zero by default */ setColor(Color4{1.0f}); } @@ -341,6 +358,17 @@ template VectorGL& VectorGL::set return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template VectorGL& VectorGL::setTextureLayer(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setTextureLayer(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::VectorGL::setTextureLayer(): the shader was not created with texture arrays enabled", *this); + setUniform(_textureLayerUniform, id); + return *this; +} +#endif + template VectorGL& VectorGL::setBackgroundColor(const Color4& color) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), @@ -472,10 +500,25 @@ template VectorGL& VectorGL::bin #endif template VectorGL& VectorGL::bindVectorTexture(GL::Texture2D& texture) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags & Flag::TextureArrays), + "Shaders::VectorGL::bindVectorTexture(): the shader was created with texture arrays enabled, use a Texture2DArray instead", *this); + #endif texture.bind(TextureUnit); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template VectorGL& VectorGL::bindVectorTexture(GL::Texture2DArray& texture) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(_flags & Flag::TextureArrays, + "Shaders::VectorGL::bindVectorTexture(): the shader was not created with texture arrays enabled, use a Texture2D instead", *this); + #endif + texture.bind(TextureUnit); + return *this; +} +#endif + template class MAGNUM_SHADERS_EXPORT VectorGL<2>; template class MAGNUM_SHADERS_EXPORT VectorGL<3>; @@ -502,6 +545,7 @@ Debug& operator<<(Debug& debug, const VectorGLFlag value) { _c(ShaderStorageBuffers) #endif _c(MultiDraw) + _c(TextureArrays) #endif #undef _c /* LCOV_EXCL_STOP */ @@ -524,7 +568,8 @@ Debug& operator<<(Debug& debug, const VectorGLFlags value) { #ifndef MAGNUM_TARGET_WEBGL VectorGLFlag::ShaderStorageBuffers, /* Superset of UniformBuffers */ #endif - VectorGLFlag::UniformBuffers + VectorGLFlag::UniformBuffers, + VectorGLFlag::TextureArrays #endif }); } diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index e1e935a02..3bbf67f96 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -50,7 +50,8 @@ namespace Implementation { #ifndef MAGNUM_TARGET_WEBGL ShaderStorageBuffers = UniformBuffers|(1 << 3), #endif - MultiDraw = UniformBuffers|(1 << 2) + MultiDraw = UniformBuffers|(1 << 2), + TextureArrays = 1 << 4 #endif }; typedef Containers::EnumSet VectorGLFlags; @@ -87,6 +88,12 @@ Common rendering setup: @snippet Shaders-gl.cpp VectorGL-usage2 +If @ref Flag::TextureArrays is enabled, pass a @ref GL::Texture2DArray instance +instead of @ref GL::Texture2D. The layer is taken from the third coordinate of +@ref TextureArrayCoordinates, if used instead of @ref TextureCoordinates, +otherwise layer @cpp 0 @ce is picked. Additionally, the value of +@ref setTextureLayer(), which is @cpp 0 @ce by default, is added to the layer. + @section Shaders-VectorGL-ubo Uniform buffers See @ref shaders-usage-ubo for a high-level overview that applies to all @@ -142,10 +149,26 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @brief 2D texture coordinates * * @ref shaders-generic "Generic attribute", - * @relativeref{Magnum,Vector2}. + * @relativeref{Magnum,Vector2}. Use either this or the + * @ref TextureArrayCoordinates attribute. */ typedef typename GenericGL::TextureCoordinates TextureCoordinates; + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief 2D array texture coordinates + * + * @ref shaders-generic "Generic attribute", + * @relativeref{Magnum,Vector3}. Use either this or the + * @ref TextureCoordinates attribute. The third component is used only + * if @ref Flag::TextureArrays is set. + * @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. + */ + typedef typename GenericGL::TextureArrayCoordinates TextureArrayCoordinates; + #endif + enum: UnsignedInt { /** * Color shader output. @ref shaders-generic "Generic output", @@ -227,7 +250,27 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * relies on uniform buffers, which require WebGL 2.0. * @m_since_latest */ - MultiDraw = UniformBuffers|(1 << 2) + MultiDraw = UniformBuffers|(1 << 2), + + /** + * Use 2D texture arrays. Expects that the texture is supplied via + * @ref bindVectorTexture(GL::Texture2DArray&) instead of + * @ref bindVectorTexture(GL::Texture2D&). The layer is taken from + * the third coordinate of @ref TextureArrayCoordinates, if used + * instead of @ref TextureCoordinates, otherwise layer @cpp 0 @ce + * is picked. Additionally, if @ref Flag::UniformBuffers is not + * enabled, the value of @ref setTextureLayer() is added to the + * layer; if @ref Flag::UniformBuffers is enabled and + * @ref Flag::TextureTransformation is enabled as well, the value + * of @ref TextureTransformationUniform::layer is added to the + * layer. + * @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. + * @m_since_latest + */ + TextureArrays = 1 << 4, #endif }; @@ -421,6 +464,28 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL */ VectorGL& setTextureMatrix(const Matrix3& matrix); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set texture array layer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with @ref Flag::TextureArrays + * enabled. Initial value is @cpp 0 @ce. If a three-component + * @ref TextureArrayCoordinates attribute is used instead of + * @ref TextureCoordinates, this value is added to the layer coming + * from the third component. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::layer and call + * @ref bindTextureTransformationBuffer() instead. + * @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. + */ + VectorGL& setTextureLayer(UnsignedInt layer); + #endif + /** * @brief Set background color * @return Reference to self (for method chaining) @@ -579,10 +644,32 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @brief Bind a vector texture * @return Reference to self (for method chaining) * + * If @ref Flag::TextureArrays is enabled, use + * @ref bindVectorTexture(GL::Texture2DArray&) instead. * @see @ref Flag::TextureTransformation, @ref setTextureMatrix() */ VectorGL& bindVectorTexture(GL::Texture2D& texture); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Bind a vector array texture + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that the shader was created with @ref Flag::TextureArrays + * enabled. The layer is taken from the third coordinate of + * @ref TextureArrayCoordinates, if used instead of + * @ref TextureCoordinates, otherwise layer @cpp 0 @ce is picked. + * Additionally, if @ref Flag::UniformBuffers is not enabled, the layer + * index is offset with the value set in @ref setTextureLayer(); if + * @ref Flag::UniformBuffers is enabled and + * @ref Flag::TextureTransformation is enabled as well, the layer index + * is offset with @ref TextureTransformationUniform::layer. + * @see @ref Flag::TextureTransformation, @ref setTextureMatrix() + */ + VectorGL& bindVectorTexture(GL::Texture2DArray& texture); + #endif + /** * @} */ @@ -600,8 +687,11 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, - _backgroundColorUniform{2}, - _colorUniform{3}; + #ifndef MAGNUM_TARGET_GLES2 + _textureLayerUniform{2}, + #endif + _backgroundColorUniform{3}, + _colorUniform{4}; #ifndef MAGNUM_TARGET_GLES2 /* Used instead of all other uniforms when Flag::UniformBuffers is set, so it can alias them */