From a2f8a920dac5b7b60546d0edfe4ae765dff7ab6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 27 May 2021 16:34:47 +0200 Subject: [PATCH] Shaders: supply Flat material information in a separate UBO as well. While it's one additional indirection (that has an extra cost on Intel GPUs apparently, like with Phong and MeshVisualizer and DistanceFieldVector already), with the assumption that draws usually share the material info it allows to cram more draws into the 16/64k UBO limit as the per-draw data are now one vec4 smaller. For the indirection overhead I can imagine adding a new flag which makes material mapping implicit (materialId == drawId). That seems to put the benchmark numbers back to the original speed. Same could be done for other shaders. --- src/Magnum/Shaders/Flat.frag | 30 ++- src/Magnum/Shaders/Flat.h | 182 ++++++++++---- src/Magnum/Shaders/FlatGL.cpp | 32 ++- src/Magnum/Shaders/FlatGL.h | 86 +++++-- src/Magnum/Shaders/Test/FlatGLTest.cpp | 233 ++++++++++++++---- src/Magnum/Shaders/Test/FlatTest.cpp | 97 ++++++-- .../Shaders/Test/ShadersGLBenchmark.cpp | 43 ++-- 7 files changed, 525 insertions(+), 178 deletions(-) diff --git a/src/Magnum/Shaders/Flat.frag b/src/Magnum/Shaders/Flat.frag index b9fa74103..c63b22d41 100644 --- a/src/Magnum/Shaders/Flat.frag +++ b/src/Magnum/Shaders/Flat.frag @@ -27,10 +27,6 @@ #extension GL_EXT_gpu_shader4: require #endif -#if defined(UNIFORM_BUFFERS) && defined(ALPHA_MASK) && !defined(GL_ES) -#extension GL_ARB_shader_bit_encoding: require -#endif - #ifndef NEW_GLSL #define fragmentColor gl_FragColor #define texture texture2D @@ -88,10 +84,9 @@ uniform highp uint drawOffset #endif struct DrawUniform { - lowp vec4 color; - highp uvec4 objectIdReservedAlphaMaskReserved; - #define draw_objectId objectIdReservedAlphaMaskReserved.x - #define draw_alphaMask objectIdReservedAlphaMaskReserved.z + highp uvec4 materialIdReservedObjectIdReservedReserved; + #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x + #define draw_objectId materialIdReservedObjectIdReservedReserved.y }; layout(std140 @@ -101,6 +96,20 @@ layout(std140 ) uniform Draw { DrawUniform draws[DRAW_COUNT]; }; + +struct MaterialUniform { + lowp vec4 color; + highp vec4 alphaMaskReservedReservedReserved; + #define material_alphaMask alphaMaskReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; #endif /* Textures */ @@ -148,12 +157,13 @@ flat in highp uint drawId; void main() { #ifdef UNIFORM_BUFFERS - lowp const vec4 color = draws[drawId].color; #ifdef OBJECT_ID highp const uint objectId = draws[drawId].draw_objectId; #endif + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + lowp const vec4 color = materials[materialId].color; #ifdef ALPHA_MASK - lowp const float alphaMask = uintBitsToFloat(draws[drawId].draw_alphaMask); + lowp const float alphaMask = materials[materialId].material_alphaMask; #endif #endif diff --git a/src/Magnum/Shaders/Flat.h b/src/Magnum/Shaders/Flat.h index e5bef400b..4a894c173 100644 --- a/src/Magnum/Shaders/Flat.h +++ b/src/Magnum/Shaders/Flat.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Struct @ref Magnum::Shaders::FlatDrawUniform + * @brief Struct @ref Magnum::Shaders::FlatDrawUniform, @ref Magnum::Shaders::FlatMaterialUniform */ #include "Magnum/Magnum.h" @@ -47,24 +47,29 @@ namespace Magnum { namespace Shaders { Together with the generic @ref TransformationProjectionUniform2D / @ref TransformationProjectionUniform3D contains parameters that are specific to each draw call. Texture transformation, if needed, is supplied separately in a -@ref TextureTransformationUniform. +@ref TextureTransformationUniform; material-related properties are expected to +be shared among multiple draw calls and thus are provided in a separate +@ref FlatMaterialUniform structure, referenced by @ref materialId. @see @ref FlatGL::bindDrawBuffer() */ struct FlatDrawUniform { /** @brief Construct with default parameters */ - constexpr explicit FlatDrawUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, objectId{0}, - #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - /* Otherwise it refuses to constexpr, on 3.8 at least */ - _pad0{}, _pad1{}, + constexpr explicit FlatDrawUniform(DefaultInitT = DefaultInit) noexcept: + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + materialId{0}, + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) && !defined(CORRADE_TARGET_BIG_ENDIAN) + _pad0{}, #endif - alphaMask{0.5f} + objectId{0} #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - , _pad2{} + , _pad1{}, _pad2{} #endif {} /** @brief Construct without initializing the contents */ - explicit FlatDrawUniform(NoInitT) noexcept: color{NoInit} {} + explicit FlatDrawUniform(NoInitT) noexcept {} /** @{ * @name Convenience setters @@ -76,29 +81,20 @@ struct FlatDrawUniform { */ /** - * @brief Set the @ref objectId field + * @brief Set the @ref materialId field * @return Reference to self (for method chaining) */ - FlatDrawUniform& setObjectId(UnsignedInt id) { - objectId = id; + FlatDrawUniform& setMaterialId(UnsignedInt id) { + materialId = id; return *this; } /** - * @brief Set the @ref alphaMask field - * @return Reference to self (for method chaining) - */ - FlatDrawUniform& setAlphaMask(Float alphaMask) { - this->alphaMask = alphaMask; - return *this; - } - - /** - * @brief Set the @ref color field + * @brief Set the @ref objectId field * @return Reference to self (for method chaining) */ - FlatDrawUniform& setColor(const Color4& color) { - this->color = color; + FlatDrawUniform& setObjectId(UnsignedInt id) { + objectId = id; return *this; } @@ -106,17 +102,37 @@ struct FlatDrawUniform { * @} */ - /** - * @brief Color - * - * Default value is @cpp 0xffffffff_rgbaf @ce. + /** @var materialId + * @brief Material ID * - * If @ref FlatGL::Flag::VertexColor is enabled, the color is multiplied - * with a color coming from the @ref FlatGL::Color3 / @ref FlatGL::Color4 - * attribute. - * @see @ref FlatGL::setColor() + * References a particular material from a @ref FlatMaterialUniform array. + * Useful when an UBO with more than one material is supplied or in a + * multi-draw scenario. Should be less than the material count passed to + * the @ref FlatGL::FlatGL(Flags, UnsignedInt, UnsignedInt) constructor. + * Default value is @cpp 0 @ce, meaning the first material gets used. */ - Color4 color; + + /* This field is an UnsignedInt in the shader and materialId is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort materialId; + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + #endif + #else + UnsignedShort + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif /** * @brief Object ID @@ -131,35 +147,83 @@ struct FlatDrawUniform { */ UnsignedInt objectId; - /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT - /* This field is an UnsignedInt in the shader and skinOffset is extracted - as (value >> 16), so the order has to be different on BE */ - #ifndef CORRADE_TARGET_BIG_ENDIAN - UnsignedShort - #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ - #endif - :16; - UnsignedShort + Int #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ #endif - :16; /* reserved for skinOffset */ - #else - UnsignedShort + :32; + Int #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ #endif - :16; /* reserved for skinOffset */ - UnsignedShort + :32; + #endif +}; + +/** +@brief Material uniform for flat shaders +@m_since_latest + +Describes material properties referenced from +@ref FlatDrawUniform::materialId. +@see @ref FlatGL::bindMaterialBuffer() +*/ +struct FlatMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit FlatMaterialUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, alphaMask{0.5f} #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + , _pad0{}, _pad1{}, _pad2{} #endif - :16; - #endif - #endif + {} + + /** @brief Construct without initializing the contents */ + explicit FlatMaterialUniform(NoInitT) noexcept: color{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref alphaMask field + * @return Reference to self (for method chaining) + */ + FlatMaterialUniform& setAlphaMask(Float alphaMask) { + this->alphaMask = alphaMask; + return *this; + } + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + FlatMaterialUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @} + */ + + /** + * @brief Color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * + * If @ref FlatGL::Flag::VertexColor is enabled, the color is multiplied + * with a color coming from the @ref FlatGL::Color3 / @ref FlatGL::Color4 + * attribute. + * @see @ref FlatGL::setColor() + */ + Color4 color; /** * @brief Alpha mask value @@ -172,9 +236,19 @@ struct FlatDrawUniform { */ Float alphaMask; - /* warning: Member __pad2__ is not documented. FFS DOXYGEN WHY DO YOU THINK + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; + Int + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + _pad1 /* Otherwise it refuses to constexpr, on 3.8 at least */ + #endif + :32; Int #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index c91221fb6..4df4cdffb 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -57,25 +57,28 @@ namespace { bound to the same buffer for the whole time */ TransformationProjectionBufferBinding = 1, DrawBufferBinding = 2, - TextureTransformationBufferBinding = 3 + TextureTransformationBufferBinding = 3, + MaterialBufferBinding = 4 }; #endif } template FlatGL::FlatGL(const Flags flags #ifndef MAGNUM_TARGET_GLES2 - , const UnsignedInt drawCount + , const UnsignedInt materialCount, const UnsignedInt drawCount #endif ): _flags{flags} #ifndef MAGNUM_TARGET_GLES2 - , _drawCount{drawCount} + , _materialCount{materialCount}, _drawCount{drawCount} #endif { CORRADE_ASSERT(!(flags & Flag::TextureTransformation) || (flags & Flag::Textured), "Shaders::FlatGL: texture transformation enabled but the shader is not textured", ); #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::FlatGL: material count can't be zero", ); CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, "Shaders::FlatGL: draw count can't be zero", ); #endif @@ -146,8 +149,10 @@ template FlatGL::FlatGL(const Flags flags if(flags >= Flag::UniformBuffers) { frag.addSource(Utility::formatString( "#define UNIFORM_BUFFERS\n" - "#define DRAW_COUNT {}\n", - drawCount)); + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + drawCount, + materialCount)); frag.addSource(flags >= Flag::MultiDraw ? "#define MULTI_DRAW\n" : ""); } #endif @@ -219,6 +224,7 @@ template FlatGL::FlatGL(const Flags flags setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); if(flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); } #endif } @@ -242,7 +248,7 @@ template FlatGL::FlatGL(const Flags flags } #ifndef MAGNUM_TARGET_GLES2 -template FlatGL::FlatGL(const Flags flags): FlatGL{flags, 1} {} +template FlatGL::FlatGL(const Flags flags): FlatGL{flags, 1, 1} {} #endif template FlatGL& FlatGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { @@ -351,6 +357,20 @@ template FlatGL& FlatGL::bindTex buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); return *this; } + +template FlatGL& FlatGL::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} #endif template FlatGL& FlatGL::bindTexture(GL::Texture2D& texture) { diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index 421b240b6..6e86fe668 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -380,8 +380,8 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** * Use uniform buffers. Expects that uniform data are supplied via * @ref bindTransformationProjectionBuffer(), - * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() - * instead of direct uniform setters. + * @ref bindDrawBuffer(), @ref bindTextureTransformationBuffer() + * and @ref bindMaterialBuffer() instead of direct uniform setters. * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES * 2.0. @@ -437,32 +437,44 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * * While this function is meant mainly for the classic uniform * scenario (without @ref Flag::UniformBuffers set), it's equivalent to - * @ref FlatGL(Flags, UnsignedInt) with @p drawCount set to @cpp 1 @ce. + * @ref FlatGL(Flags, UnsignedInt, UnsignedInt) with @p materialCount + * and @p drawCount set to @cpp 1 @ce. */ explicit FlatGL(Flags flags = {}); #ifndef MAGNUM_TARGET_GLES2 /** * @brief Construct for a multi-draw scenario - * @param flags Flags - * @param drawCount Size of a @ref TransformationProjectionUniform2D / - * @ref TransformationProjectionUniform3D / @ref FlatDrawUniform / - * @ref TextureTransformationUniform buffer bound with + * @param flags Flags + * @param materialCount Size of a @ref FlatMaterialUniform buffer + * bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref TransformationProjectionUniform3D / @ref FlatDrawUniform + * / @ref TextureTransformationUniform buffer bound with * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() * and @ref bindTextureTransformationBuffer() * - * If @p flags contains @ref Flag::UniformBuffers, @p drawCount - * describes the uniform buffer sizes as these are required to have a - * statically defined size. The draw offset is then set via - * @ref setDrawOffset(). + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials specified via + * @ref FlatDrawUniform::materialId. * - * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is - * ignored and the constructor behaves the same as @ref FlatGL(Flags). + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref FlatGL(Flags). * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. */ - explicit FlatGL(Flags flags, UnsignedInt drawCount); + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit FlatGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); #endif /** @@ -495,6 +507,18 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: Flags flags() const { return _flags; } #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref FlatMaterialUniform uniform + * buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + /** * @brief Draw count * @m_since_latest @@ -560,7 +584,8 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * coming from the @ref Color3 / @ref Color4 attribute. * * Expects that @ref Flag::UniformBuffers is not set, in that case fill - * @ref FlatDrawUniform::color and call @ref bindDrawBuffer() instead. + * @ref FlatMaterialUniform::color and call @ref bindMaterialBuffer() + * instead. * @see @ref bindTexture() */ FlatGL& setColor(const Magnum::Color4& color); @@ -578,8 +603,8 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * in classic OpenGL. * * Expects that @ref Flag::UniformBuffers is not set, in that case fill - * @ref FlatDrawUniform::alphaMask and call @ref bindDrawBuffer() - * instead. + * @ref FlatMaterialUniform::alphaMask and call + * @ref bindMaterialBuffer() instead. * @m_keywords{glAlphaFunc()} */ FlatGL& setAlphaMask(Float mask); @@ -648,7 +673,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * expected to contain @ref drawCount() instances of * @ref TransformationProjectionUniform2D / * @ref TransformationProjectionUniform3D. At the very least you need - * to call also @ref bindDrawBuffer(). + * to call also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -668,7 +693,8 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * Expects that @ref Flag::UniformBuffers is set. The buffer is * expected to contain @ref drawCount() instances of * @ref FlatDrawUniform. At the very least you need to call also - * @ref bindTransformationProjectionBuffer(). + * @ref bindTransformationProjectionBuffer() and + * @ref bindMaterialBuffer(). * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. @@ -700,6 +726,26 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: */ FlatGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref FlatMaterialUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer() and @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& bindMaterialBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + /** * @} */ @@ -735,7 +781,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: Flags _flags; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt _drawCount{}; + UnsignedInt _materialCount{}, _drawCount{}; #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 974365108..b2581c749 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -86,7 +86,7 @@ struct FlatGLTest: GL::OpenGLTester { template void constructTextureTransformationNotTextured(); #ifndef MAGNUM_TARGET_GLES2 - template void constructUniformBuffersZeroDraws(); + template void constructUniformBuffersInvalid(); #endif #ifndef MAGNUM_TARGET_GLES2 @@ -210,17 +210,31 @@ constexpr struct { constexpr struct { const char* name; FlatGL2D::Flags flags; - UnsignedInt drawCount; + UnsignedInt materialCount, drawCount; } ConstructUniformBuffersData[]{ - {"classic fallback", {}, 1}, - {"", FlatGL2D::Flag::UniformBuffers, 1}, - /* SwiftShader has 256 uniform vectors at most, per-draw is 4+2 in 3D case - and 3+2 in 2D */ - {"multiple draws", FlatGL2D::Flag::UniformBuffers, 42}, - {"texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1}, - {"alpha mask", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, 1}, - {"object ID", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId, 1}, - {"multidraw with all the things", FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask|FlatGL2D::Flag::ObjectId|FlatGL2D::Flag::InstancedTextureOffset|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId, 42} + {"classic fallback", {}, 1, 1}, + {"", FlatGL2D::Flag::UniformBuffers, 1, 1}, + /* SwiftShader has 256 uniform vectors at most, per-draw is 4+1 in 3D case + and 3+1 in 2D, per-material 2 */ + {"multiple materials, draws", FlatGL2D::Flag::UniformBuffers, 8, 48}, + {"texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1, 1}, + {"alpha mask", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, 1, 1}, + {"object ID", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId, 1, 1}, + {"multidraw with all the things", FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask|FlatGL2D::Flag::ObjectId|FlatGL2D::Flag::InstancedTextureOffset|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId, 8, 48} +}; +#endif + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + FlatGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData[]{ + {"zero draws", FlatGL2D::Flag::UniformBuffers, 1, 0, + "draw count can't be zero"}, + {"zero materials", FlatGL2D::Flag::UniformBuffers, 0, 1, + "material count can't be zero"}, }; #endif @@ -282,30 +296,30 @@ constexpr struct { const char* expected2D; const char* expected3D; FlatGL2D::Flags flags; - UnsignedInt drawCount; + UnsignedInt materialCount, drawCount; UnsignedInt uniformIncrement; Float maxThreshold, meanThreshold; } RenderMultiData[] { {"bind with offset, colored", "multidraw2D.tga", "multidraw3D.tga", - {}, 1, 16, 0.0f, 0.0f}, + {}, 1, 1, 16, 0.0f, 0.0f}, {"bind with offset, textured", "multidraw-textured2D.tga", "multidraw-textured3D.tga", FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured, - 1, 16, + 1, 1, 16, /* Minor differences on ARM Mali */ 2.34f, 0.01f}, {"draw offset, colored", "multidraw2D.tga", "multidraw3D.tga", {}, - 3, 1, 0.0f, 0.0f}, + 2, 3, 1, 0.0f, 0.0f}, {"draw offset, textured", "multidraw-textured2D.tga", "multidraw-textured3D.tga", FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured, - 3, 1, + 2, 3, 1, /* Minor differences on ARM Mali */ 2.34f, 0.01f}, {"multidraw, colored", "multidraw2D.tga", "multidraw3D.tga", - FlatGL2D::Flag::MultiDraw, 3, 1, 0.0f, 0.0f}, + FlatGL2D::Flag::MultiDraw, 2, 3, 1, 0.0f, 0.0f}, {"multidraw, textured", "multidraw-textured2D.tga", "multidraw-textured3D.tga", FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured, - 3, 1, + 2, 3, 1, /* Minor differences on ARM Mali */ 2.34f, 0.01f} }; @@ -334,12 +348,16 @@ FlatGLTest::FlatGLTest() { #endif &FlatGLTest::constructTextureTransformationNotTextured<2>, - &FlatGLTest::constructTextureTransformationNotTextured<3>, - #ifndef MAGNUM_TARGET_GLES2 - &FlatGLTest::constructUniformBuffersZeroDraws<2>, - &FlatGLTest::constructUniformBuffersZeroDraws<3>, - #endif + &FlatGLTest::constructTextureTransformationNotTextured<3>}); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &FlatGLTest::constructUniformBuffersInvalid<2>, + &FlatGLTest::constructUniformBuffersInvalid<3>}, + Containers::arraySize(ConstructUniformBuffersInvalidData)); + #endif + + addTests({ #ifndef MAGNUM_TARGET_GLES2 &FlatGLTest::setUniformUniformBuffersEnabled<2>, &FlatGLTest::setUniformUniformBuffersEnabled<3>, @@ -557,8 +575,9 @@ template void FlatGLTest::constructUniformBuffers() { #endif } - FlatGL shader{data.flags, data.drawCount}; + FlatGL shader{data.flags, data.materialCount, data.drawCount}; CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.materialCount(), data.materialCount); CORRADE_COMPARE(shader.drawCount(), data.drawCount); CORRADE_VERIFY(shader.id()); { @@ -602,7 +621,7 @@ template void FlatGLTest::constructMoveUniformBuffers() CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - FlatGL a{FlatGL::Flag::UniformBuffers, 5}; + FlatGL a{FlatGL::Flag::UniformBuffers, 2, 5}; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -611,6 +630,7 @@ template void FlatGLTest::constructMoveUniformBuffers() FlatGL b{std::move(a)}; CORRADE_COMPARE(b.id(), id); CORRADE_COMPARE(b.flags(), FlatGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.materialCount(), 2); CORRADE_COMPARE(b.drawCount(), 5); CORRADE_VERIFY(!a.id()); @@ -618,6 +638,7 @@ template void FlatGLTest::constructMoveUniformBuffers() c = std::move(b); CORRADE_COMPARE(c.id(), id); CORRADE_COMPARE(c.flags(), FlatGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.materialCount(), 2); CORRADE_COMPARE(c.drawCount(), 5); CORRADE_VERIFY(!b.id()); } @@ -638,8 +659,10 @@ template void FlatGLTest::constructTextureTransformation } #ifndef MAGNUM_TARGET_GLES2 -template void FlatGLTest::constructUniformBuffersZeroDraws() { +template void FlatGLTest::constructUniformBuffersInvalid() { + auto&& data = ConstructUniformBuffersInvalidData[testCaseInstanceId()]; setTestCaseTemplateName(std::to_string(dimensions)); + setTestCaseDescription(data.name); #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -652,9 +675,9 @@ template void FlatGLTest::constructUniformBuffersZeroDra std::ostringstream out; Error redirectError{&out}; - FlatGL{FlatGL::Flag::UniformBuffers, 0}; - CORRADE_COMPARE(out.str(), - "Shaders::FlatGL: draw count can't be zero\n"); + FlatGL{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::FlatGL: {}\n", data.message)); } #endif @@ -706,6 +729,8 @@ template void FlatGLTest::bindBufferUniformBuffersNotEna .bindDrawBuffer(buffer, 0, 16) .bindTextureTransformationBuffer(buffer) .bindTextureTransformationBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) .setDrawOffset(0); CORRADE_COMPARE(out.str(), "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" @@ -714,6 +739,8 @@ template void FlatGLTest::bindBufferUniformBuffersNotEna "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::FlatGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } #endif @@ -829,7 +856,7 @@ template void FlatGLTest::setWrongDrawOffset() { std::ostringstream out; Error redirectError{&out}; - FlatGL{FlatGL::Flag::UniformBuffers, 5} + FlatGL{FlatGL::Flag::UniformBuffers, 2, 5} .setDrawOffset(5); CORRADE_COMPARE(out.str(), "Shaders::FlatGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); @@ -890,9 +917,13 @@ template void FlatGLTest::renderDefaults2D() { GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; shader .bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(circle); } #endif @@ -939,9 +970,13 @@ template void FlatGLTest::renderDefaults3D() { GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; shader .bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(sphere); } #endif @@ -991,11 +1026,15 @@ template void FlatGLTest::renderColored2D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} .setColor(0x9999ff_rgbf) }}; shader .bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(circle); } #endif @@ -1059,11 +1098,15 @@ template void FlatGLTest::renderColored3D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} .setColor(0x9999ff_rgbf) }}; shader .bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(sphere); } #endif @@ -1144,8 +1187,12 @@ template void FlatGLTest::renderSinglePixelTextured2D() { GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(circle); } #endif @@ -1221,8 +1268,12 @@ template void FlatGLTest::renderSinglePixelTextured3D() { GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(sphere); } #endif @@ -1302,16 +1353,20 @@ template void FlatGLTest::renderTextured2D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} - .setColor(0x9999ff_rgbf) }}; GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(data.textureTransformation) }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; if(data.flags & FlatGL2D::Flag::TextureTransformation) shader.bindTextureTransformationBuffer(textureTransformationUniform); shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(circle); } #endif @@ -1401,16 +1456,20 @@ template void FlatGLTest::renderTextured3D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} - .setColor(0x9999ff_rgbf) }}; GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(data.textureTransformation) }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; if(data.flags & FlatGL3D::Flag::TextureTransformation) shader.bindTextureTransformationBuffer(textureTransformationUniform); shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(sphere); } #endif @@ -1496,10 +1555,14 @@ template void FlatGLTest::renderVertexColor2D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} .setColor(0x9999ff_rgbf) }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(circle); } #endif @@ -1590,10 +1653,14 @@ template void FlatGLTest::renderVertexColor3D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} .setColor(0x9999ff_rgbf) }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(sphere); } #endif @@ -1685,11 +1752,15 @@ template void FlatGLTest::renderAlpha2D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} .setColor(0x9999ff_rgbf) .setAlphaMask(data.threshold) }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(circle); } #endif @@ -1782,11 +1853,15 @@ template void FlatGLTest::renderAlpha3D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} .setColor(0x9999ff_rgbf) .setAlphaMask(data.threshold) }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) - .bindDrawBuffer(drawUniform); + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); /* For proper Z order draw back faces first and then front faces */ GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); @@ -1896,11 +1971,15 @@ template void FlatGLTest::renderObjectId2D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} - .setColor(0x9999ff_rgbf) .setObjectId(data.uniformId) }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(circle); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); @@ -1990,11 +2069,15 @@ template void FlatGLTest::renderObjectId3D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} - .setColor(0x9999ff_rgbf) .setObjectId(data.uniformId) }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0x9999ff_rgbf) + }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) .draw(sphere); } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); @@ -2127,15 +2210,19 @@ template void FlatGLTest::renderInstanced2D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} - .setColor(0xffff99_rgbf) }}; GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0xffff99_rgbf) + }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) .bindTextureTransformationBuffer(textureTransformationUniform) + .bindMaterialBuffer(materialUniform) .draw(circle); } #endif @@ -2251,15 +2338,19 @@ template void FlatGLTest::renderInstanced3D() { }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { FlatDrawUniform{} - .setColor(0xffff99_rgbf) }}; GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { TextureTransformationUniform{} .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + .setColor(0xffff99_rgbf) + }}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) .bindDrawBuffer(drawUniform) .bindTextureTransformationBuffer(textureTransformationUniform) + .bindMaterialBuffer(materialUniform) .draw(sphere); } #endif @@ -2344,6 +2435,15 @@ void FlatGLTest::renderMulti2D() { The data.uniformIncrement is set high enough to ensure that, in the non-offset-bind case this value is 1. */ + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0x0000ff_rgbf); + materialData[1*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0xff0000_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} .setTransformationProjectionMatrix( @@ -2384,26 +2484,28 @@ void FlatGLTest::renderMulti2D() { GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are + done with UBO offset bindings instead. */ drawData[0*data.uniformIncrement] = FlatDrawUniform{} - .setColor(data.flags & FlatGL2D::Flag::Textured ? - 0xffffff_rgbf : 0xff0000_rgbf) + .setMaterialId(data.drawCount == 1 ? 0 : 1) .setObjectId(1211); drawData[1*data.uniformIncrement] = FlatDrawUniform{} - .setColor(data.flags & FlatGL2D::Flag::Textured ? - 0xffffff_rgbf : 0x0000ff_rgbf) + .setMaterialId(data.drawCount == 1 ? 0 : 0) .setObjectId(5627); drawData[2*data.uniformIncrement] = FlatDrawUniform{} - .setColor(data.flags & FlatGL2D::Flag::Textured ? - 0xffffff_rgbf : 0xff0000_rgbf) + .setMaterialId(data.drawCount == 1 ? 0 : 1) .setObjectId(36363); GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; - FlatGL2D shader{FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId|data.flags, data.drawCount}; + FlatGL2D shader{FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId|data.flags, data.materialCount, data.drawCount}; if(data.flags & FlatGL2D::Flag::Textured) shader.bindTexture(texture); /* Just one draw, rebinding UBOs each time */ if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); shader.bindTransformationProjectionBuffer(transformationProjectionUniform, 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), sizeof(TransformationProjectionUniform2D)); @@ -2416,6 +2518,9 @@ void FlatGLTest::renderMulti2D() { sizeof(TextureTransformationUniform)); shader.draw(circle); + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); shader.bindTransformationProjectionBuffer(transformationProjectionUniform, 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), sizeof(TransformationProjectionUniform2D)); @@ -2428,6 +2533,9 @@ void FlatGLTest::renderMulti2D() { sizeof(TextureTransformationUniform)); shader.draw(square); + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); shader.bindTransformationProjectionBuffer(transformationProjectionUniform, 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), sizeof(TransformationProjectionUniform2D)); @@ -2443,7 +2551,8 @@ void FlatGLTest::renderMulti2D() { /* Otherwise using the draw offset / multidraw */ } else { shader.bindTransformationProjectionBuffer(transformationProjectionUniform) - .bindDrawBuffer(drawUniform); + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); if(data.flags & FlatGL2D::Flag::TextureTransformation) shader.bindTextureTransformationBuffer(textureTransformationUniform); @@ -2565,6 +2674,15 @@ void FlatGLTest::renderMulti3D() { The data.uniformIncrement is set high enough to ensure that, in the non-offset-bind case this value is 1. */ + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0x0000ff_rgbf); + materialData[1*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0xff0000_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} .setTransformationProjectionMatrix( @@ -2611,26 +2729,28 @@ void FlatGLTest::renderMulti3D() { GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ drawData[0*data.uniformIncrement] = FlatDrawUniform{} - .setColor(data.flags & FlatGL3D::Flag::Textured ? - 0xffffff_rgbf : 0xff0000_rgbf) + .setMaterialId(data.drawCount == 1 ? 0 : 1) .setObjectId(1211); drawData[1*data.uniformIncrement] = FlatDrawUniform{} - .setColor(data.flags & FlatGL3D::Flag::Textured ? - 0xffffff_rgbf : 0x0000ff_rgbf) + .setMaterialId(data.drawCount == 1 ? 0 : 0) .setObjectId(5627); drawData[2*data.uniformIncrement] = FlatDrawUniform{} - .setColor(data.flags & FlatGL3D::Flag::Textured ? - 0xffffff_rgbf : 0xff0000_rgbf) + .setMaterialId(data.drawCount == 1 ? 0 : 1) .setObjectId(36363); GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; - FlatGL3D shader{FlatGL3D::Flag::UniformBuffers|FlatGL3D::Flag::ObjectId|data.flags, data.drawCount}; + FlatGL3D shader{FlatGL3D::Flag::UniformBuffers|FlatGL3D::Flag::ObjectId|data.flags, data.materialCount, data.drawCount}; if(data.flags & FlatGL3D::Flag::Textured) shader.bindTexture(texture); /* Just one draw, rebinding UBOs each time */ if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); shader.bindTransformationProjectionBuffer(transformationProjectionUniform, 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), sizeof(TransformationProjectionUniform3D)); @@ -2643,6 +2763,9 @@ void FlatGLTest::renderMulti3D() { sizeof(TextureTransformationUniform)); shader.draw(sphere); + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); shader.bindTransformationProjectionBuffer(transformationProjectionUniform, 1*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), sizeof(TransformationProjectionUniform3D)); @@ -2655,6 +2778,9 @@ void FlatGLTest::renderMulti3D() { sizeof(TextureTransformationUniform)); shader.draw(plane); + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); shader.bindTransformationProjectionBuffer(transformationProjectionUniform, 2*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), sizeof(TransformationProjectionUniform3D)); @@ -2670,7 +2796,8 @@ void FlatGLTest::renderMulti3D() { /* Otherwise using the draw offset / multidraw */ } else { shader.bindTransformationProjectionBuffer(transformationProjectionUniform) - .bindDrawBuffer(drawUniform); + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); if(data.flags & FlatGL3D::Flag::TextureTransformation) shader.bindTextureTransformationBuffer(textureTransformationUniform); diff --git a/src/Magnum/Shaders/Test/FlatTest.cpp b/src/Magnum/Shaders/Test/FlatTest.cpp index 3ab604d65..206c04f7d 100644 --- a/src/Magnum/Shaders/Test/FlatTest.cpp +++ b/src/Magnum/Shaders/Test/FlatTest.cpp @@ -38,14 +38,25 @@ struct FlatTest: TestSuite::Tester { void drawUniformConstructDefault(); void drawUniformConstructNoInit(); void drawUniformSetters(); + void drawUniformMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); }; FlatTest::FlatTest() { addTests({&FlatTest::uniformSizeAlignment, + &FlatTest::uniformSizeAlignment, &FlatTest::drawUniformConstructDefault, &FlatTest::drawUniformConstructNoInit, - &FlatTest::drawUniformSetters}); + &FlatTest::drawUniformSetters, + &FlatTest::drawUniformMaterialIdPacking, + + &FlatTest::materialUniformConstructDefault, + &FlatTest::materialUniformConstructNoInit, + &FlatTest::materialUniformSetters}); } using namespace Math::Literals; @@ -54,6 +65,9 @@ template struct UniformTraits; template<> struct UniformTraits { static const char* name() { return "FlatDrawUniform"; } }; +template<> struct UniformTraits { + static const char* name() { return "FlatMaterialUniform"; } +}; template void FlatTest::uniformSizeAlignment() { setTestCaseTemplateName(UniformTraits::name()); @@ -72,21 +86,17 @@ template void FlatTest::uniformSizeAlignment() { void FlatTest::drawUniformConstructDefault() { FlatDrawUniform a; FlatDrawUniform b{DefaultInit}; - CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); - CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); CORRADE_COMPARE(a.objectId, 0); CORRADE_COMPARE(b.objectId, 0); - CORRADE_COMPARE(a.alphaMask, 0.5f); - CORRADE_COMPARE(b.alphaMask, 0.5f); constexpr FlatDrawUniform ca; constexpr FlatDrawUniform cb{DefaultInit}; - CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); - CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); CORRADE_COMPARE(ca.objectId, 0); CORRADE_COMPARE(cb.objectId, 0); - CORRADE_COMPARE(ca.alphaMask, 0.5f); - CORRADE_COMPARE(cb.alphaMask, 0.5f); CORRADE_VERIFY(std::is_nothrow_default_constructible::value); CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -98,16 +108,16 @@ void FlatTest::drawUniformConstructDefault() { void FlatTest::drawUniformConstructNoInit() { /* Testing only some fields, should be enough */ FlatDrawUniform a; - a.color = 0x354565fc_rgbaf; - a.alphaMask = 0.7f; + a.materialId = 5; + a.objectId = 7; new(&a) FlatDrawUniform{NoInit}; { #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); #endif - CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); - CORRADE_COMPARE(a.alphaMask, 0.7f); + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.objectId, 7); } CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -118,11 +128,68 @@ void FlatTest::drawUniformConstructNoInit() { void FlatTest::drawUniformSetters() { FlatDrawUniform a; + a.setMaterialId(5) + .setObjectId(7); + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.objectId, 7); +} + +void FlatTest::drawUniformMaterialIdPacking() { + FlatDrawUniform a; + a.setMaterialId(13765); + /* materialId should be right at the beginning, in the low 16 bits on both + LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[0] & 0xffff, 13765); +} + +void FlatTest::materialUniformConstructDefault() { + FlatMaterialUniform a; + FlatMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.alphaMask, 0.5f); + CORRADE_COMPARE(b.alphaMask, 0.5f); + + constexpr FlatMaterialUniform ca; + constexpr FlatMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.alphaMask, 0.5f); + CORRADE_COMPARE(cb.alphaMask, 0.5f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void FlatTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + FlatMaterialUniform a; + a.color = 0x354565fc_rgbaf; + a.alphaMask = 0.7f; + + new(&a) FlatMaterialUniform{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.alphaMask, 0.7f); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void FlatTest::materialUniformSetters() { + FlatMaterialUniform a; a.setColor(0x354565fc_rgbaf) - .setObjectId(7) .setAlphaMask(0.7f); CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); - CORRADE_COMPARE(a.objectId, 7); CORRADE_COMPARE(a.alphaMask, 0.7f); } diff --git a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp index 814059a16..15cd1f835 100644 --- a/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp +++ b/src/Magnum/Shaders/Test/ShadersGLBenchmark.cpp @@ -130,27 +130,27 @@ constexpr std::size_t BenchmarkRepeats{4}; const struct { const char* name; FlatGL2D::Flags flags; - UnsignedInt drawCount; + UnsignedInt materialCount, drawCount; } FlatData[] { - {"", {}, 1}, - {"vertex color", FlatGL2D::Flag::VertexColor, 1}, + {"", {}, 1, 1}, + {"vertex color", FlatGL2D::Flag::VertexColor, 1, 1}, #ifndef MAGNUM_TARGET_GLES2 - {"object ID", FlatGL2D::Flag::ObjectId, 1}, + {"object ID", FlatGL2D::Flag::ObjectId, 1, 1}, #endif - {"textured", FlatGL2D::Flag::Textured, 1}, - {"textured + alpha mask", FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask, 1}, - {"texture transformation", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1}, - {"instanced transformation", FlatGL2D::Flag::InstancedTransformation, 1}, - {"instanced transformation + color", FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::VertexColor, 1}, + {"textured", FlatGL2D::Flag::Textured, 1, 1}, + {"textured + alpha mask", FlatGL2D::Flag::Textured|FlatGL2D::Flag::AlphaMask, 1, 1}, + {"texture transformation", FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1, 1}, + {"instanced transformation", FlatGL2D::Flag::InstancedTransformation, 1, 1}, + {"instanced transformation + color", FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::VertexColor, 1, 1}, #ifndef MAGNUM_TARGET_GLES2 - {"instanced transformation + object ID", FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId, 1}, + {"instanced transformation + object ID", FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId, 1, 1}, #endif - {"instanced transformation + texture offset", FlatGL2D::Flag::Textured|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedTextureOffset, 1}, + {"instanced transformation + texture offset", FlatGL2D::Flag::Textured|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedTextureOffset, 1, 1}, #ifndef MAGNUM_TARGET_GLES2 - {"UBO single", FlatGL2D::Flag::UniformBuffers, 1}, - {"UBO single, texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1}, - {"UBO multi", FlatGL2D::Flag::UniformBuffers, 128}, - {"multidraw", FlatGL2D::Flag::MultiDraw, 128}, + {"UBO single", FlatGL2D::Flag::UniformBuffers, 1, 1}, + {"UBO single, texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1, 1}, + {"UBO multi", FlatGL2D::Flag::UniformBuffers, 32, 128}, + {"multidraw", FlatGL2D::Flag::MultiDraw, 32, 128}, #endif }; @@ -541,7 +541,7 @@ template void ShadersGLBenchmark::flat() { FlatGL shader{data.flags #ifndef MAGNUM_TARGET_GLES2 - , data.drawCount + , data.materialCount, data.drawCount #endif }; @@ -549,13 +549,16 @@ template void ShadersGLBenchmark::flat() { GL::Buffer transformationProjectionUniform{NoCreate}; GL::Buffer drawUniform{NoCreate}; GL::Buffer textureTransformationUniform{NoCreate}; + GL::Buffer materialUniform{NoCreate}; if(data.flags & FlatGL2D::Flag::UniformBuffers) { transformationProjectionUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array::TransformationProjection>{data.drawCount}}; - Containers::Array drawData{data.drawCount}; - drawData[0].setAlphaMask(0.0f); - drawUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, drawData}; + drawUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; + Containers::Array materialData{data.materialCount}; + materialData[0].setAlphaMask(0.0f); + materialUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, materialData}; shader.bindTransformationProjectionBuffer(transformationProjectionUniform) - .bindDrawBuffer(drawUniform); + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); if(data.flags & FlatGL2D::Flag::TextureTransformation) { textureTransformationUniform = GL::Buffer{GL::Buffer::TargetHint::Uniform, Containers::Array{data.drawCount}}; shader.bindTextureTransformationBuffer(textureTransformationUniform);