diff --git a/src/Magnum/Shaders/Flat.frag b/src/Magnum/Shaders/Flat.frag index c07dbade0..46dc28ce5 100644 --- a/src/Magnum/Shaders/Flat.frag +++ b/src/Magnum/Shaders/Flat.frag @@ -88,9 +88,10 @@ uniform highp uint drawOffset #endif struct DrawUniform { - highp uvec4 materialIdReservedObjectIdReservedReserved; - #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x - #define draw_objectId materialIdReservedObjectIdReservedReserved.y + highp uvec4 materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved; + #define draw_materialIdReserved materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.x + #define draw_objectId materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.y + #define draw_jointOffsetPerInstanceJointCount materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.z }; layout(std140 diff --git a/src/Magnum/Shaders/Flat.h b/src/Magnum/Shaders/Flat.h index 5ae55ba1b..0a96f72c3 100644 --- a/src/Magnum/Shaders/Flat.h +++ b/src/Magnum/Shaders/Flat.h @@ -54,7 +54,13 @@ be shared among multiple draw calls and thus are provided in a separate */ struct FlatDrawUniform { /** @brief Construct with default parameters */ - constexpr explicit FlatDrawUniform(DefaultInitT = DefaultInit) noexcept: materialId{0}, objectId{0} {} + constexpr explicit FlatDrawUniform(DefaultInitT = DefaultInit) noexcept: materialId{0}, objectId{0}, + #ifndef CORRADE_TARGET_BIG_ENDIAN + jointOffset{0}, perInstanceJointCount{0} + #else + perInstanceJointCount{0}, jointOffset{0} + #endif + {} /** @brief Construct without initializing the contents */ explicit FlatDrawUniform(NoInitT) noexcept {} @@ -86,6 +92,24 @@ struct FlatDrawUniform { return *this; } + /** + * @brief Set the @ref jointOffset field + * @return Reference to self (for method chaining) + */ + FlatDrawUniform& setJointOffset(UnsignedInt offset) { + jointOffset = offset; + return *this; + } + + /** + * @brief Set the @ref perInstanceJointCount field + * @return Reference to self (for method chaining) + */ + FlatDrawUniform& setPerInstanceJointCount(UnsignedInt count) { + perInstanceJointCount = count; + return *this; + } + /** * @} */ @@ -109,10 +133,10 @@ struct FlatDrawUniform { /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT - UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; /* reserved */ #endif #else - UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; /* reserved */ UnsignedShort materialId; #endif @@ -131,11 +155,43 @@ struct FlatDrawUniform { */ UnsignedInt objectId; + /** @var jointOffset + * @brief Joint offset + * + * Offset added to joint IDs in the @ref FlatGL::JointIds and + * @ref FlatGL::SecondaryJointIds attributes. Useful when a UBO with joint + * matrices for more than one skin is supplied or in a multi-draw scenario. + * Should be less than the joint count passed to + * @ref FlatGL::Configuration::setJointCount(). Default value is + * @cpp 0 @ce, meaning no offset is added to joint IDs. + */ + + /** @var perInstanceJointCount + * @brief Per-instance joint count + * + * Offset added to joint IDs in the @ref FlatGL::JointIds and + * @ref FlatGL::SecondaryJointIds atttributes in instanced draws. Should + * be less than the joint count passed to + * @ref FlatGL::Configuration::setJointCount(). Default value is + * @cpp 0 @ce, meaning every instance will use the same joint matrices, + * setting it to a non-zero value causes the joint IDs to be interpreted as + * @glsl gl_InstanceID*count + jointId @ce. + */ + + /* This field is an UnsignedInt in the shader and jointOffset is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort jointOffset; + UnsignedShort perInstanceJointCount; + #else + UnsignedShort perInstanceJointCount; + UnsignedShort jointOffset; + #endif + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT Int:32; - Int:32; #endif }; diff --git a/src/Magnum/Shaders/Flat.vert b/src/Magnum/Shaders/Flat.vert index 0e695c56c..8a3b5982a 100644 --- a/src/Magnum/Shaders/Flat.vert +++ b/src/Magnum/Shaders/Flat.vert @@ -48,6 +48,19 @@ #define const #endif +/* Both classic uniforms and uniform buffers */ + +#ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = PER_VERTEX_JOINT_COUNT_LOCATION) +#endif +uniform mediump uvec2 perVertexJointCount + #ifndef GL_ES + = uvec2(PER_VERTEX_JOINT_COUNT, SECONDARY_PER_VERTEX_JOINT_COUNT) + #endif + ; +#endif + /* Uniforms */ #ifndef UNIFORM_BUFFERS @@ -89,6 +102,32 @@ layout(location = 2) uniform highp uint textureLayer; /* defaults to zero */ #endif +#ifdef JOINT_COUNT +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 6) +#endif +#ifdef TWO_DIMENSIONS +uniform mat3 jointMatrices[JOINT_COUNT] + #ifndef GL_ES + = mat3[](JOINT_MATRIX_INITIALIZER) + #endif + ; +#elif defined(THREE_DIMENSIONS) +uniform mat4 jointMatrices[JOINT_COUNT] + #ifndef GL_ES + = mat4[](JOINT_MATRIX_INITIALIZER) + #endif + ; +#else +#error +#endif + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = PER_INSTANCE_JOINT_COUNT_LOCATION) +#endif +uniform uint perInstanceJointCount; /* defaults to zero */ +#endif + /* Uniform buffers */ #else @@ -105,6 +144,21 @@ uniform highp uint drawOffset #define drawOffset 0u #endif +struct DrawUniform { + highp uvec4 materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved; + #define draw_materialIdReserved materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.x + #define draw_objectId materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.y + #define draw_jointOffsetPerInstanceJointCount materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + layout(std140 #ifdef EXPLICIT_BINDING , binding = 1 @@ -123,6 +177,26 @@ layout(std140 transformationProjectionMatrices[DRAW_COUNT]; }; +#ifdef JOINT_COUNT +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 6 + #endif +) uniform Joint { + highp + #ifdef TWO_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see DrawUniform in Phong.vert for + details */ + mat3x4 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + jointMatrices[JOINT_COUNT]; +}; +#endif + #ifdef TEXTURE_TRANSFORMATION struct TextureTransformationUniform { highp vec4 rotationScaling; @@ -168,6 +242,32 @@ layout(location = COLOR_ATTRIBUTE_LOCATION) in lowp vec4 vertexColor; #endif +#ifdef JOINT_COUNT +#if PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = WEIGHTS_ATTRIBUTE_LOCATION) +#endif +in mediump vec4 weights; + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = JOINTIDS_ATTRIBUTE_LOCATION) +#endif +in mediump uvec4 jointIds; +#endif + +#if SECONDARY_PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = SECONDARY_WEIGHTS_ATTRIBUTE_LOCATION) +#endif +in mediump vec4 secondaryWeights; + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = SECONDARY_JOINTIDS_ATTRIBUTE_LOCATION) +#endif +in mediump uvec4 secondaryJointIds; +#endif +#endif + #ifdef INSTANCED_OBJECT_ID #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = OBJECT_ID_ATTRIBUTE_LOCATION) @@ -252,6 +352,49 @@ void main() { highp const uint textureLayer = floatBitsToUint(textureTransformations[drawId].textureTransformation_layer); #endif #endif + #ifdef JOINT_COUNT + mediump const uint jointOffset = (draws[drawId].draw_jointOffsetPerInstanceJointCount & 0xffffu) + uint(gl_InstanceID)*(draws[drawId].draw_jointOffsetPerInstanceJointCount >> 16 & 0xffffu); + #endif + #else + #ifdef JOINT_COUNT + mediump const uint jointOffset = uint(gl_InstanceID)*perInstanceJointCount; + #endif + #endif + + #ifdef JOINT_COUNT + #ifdef TWO_DIMENSIONS + mat3 skinMatrix = mat3(0.0); + #elif defined(THREE_DIMENSIONS) + mat4 skinMatrix = mat4(0.0); + #else + #error + #endif + #if PER_VERTEX_JOINT_COUNT + for(uint i = 0u; i != PER_VERTEX_JOINT_COUNT + #ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT + && i != perVertexJointCount.x + #endif + ; ++i) + skinMatrix += weights[i]* + #ifdef TWO_DIMENSIONS + mat3 /* need to slice because it's a mat3x4 due to ANGLE, see + DrawUniform in Phong.vert for details */ + #endif + (jointMatrices[jointOffset + jointIds[i]]); + #endif + #if SECONDARY_PER_VERTEX_JOINT_COUNT + for(uint i = 0u; i != SECONDARY_PER_VERTEX_JOINT_COUNT + #ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT + && i != perVertexJointCount.y + #endif + ; ++i) + skinMatrix += secondaryWeights[i]* + #ifdef TWO_DIMENSIONS + mat3 /* need to slice because it's a mat3x4 due to ANGLE, see + DrawUniform in Phong.vert for details */ + #endif + (jointMatrices[jointOffset + secondaryJointIds[i]]); + #endif #endif #ifdef TWO_DIMENSIONS @@ -259,12 +402,18 @@ void main() { #ifdef INSTANCED_TRANSFORMATION instancedTransformationMatrix* #endif + #ifdef JOINT_COUNT + skinMatrix* + #endif vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) gl_Position = transformationProjectionMatrix* #ifdef INSTANCED_TRANSFORMATION instancedTransformationMatrix* #endif + #ifdef JOINT_COUNT + skinMatrix* + #endif position; #else #error diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index 70498f87b..93f6937b7 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -49,6 +49,8 @@ namespace Magnum { namespace Shaders { +using namespace Containers::Literals; + namespace { enum: Int { TextureUnit = 0, @@ -58,13 +60,17 @@ namespace { #ifndef MAGNUM_TARGET_GLES2 enum: Int { - /* Not using the zero binding to avoid conflicts with - ProjectionBufferBinding from other shaders which can likely stay - bound to the same buffer for the whole time */ + /* Texture transformation and joints is slots 3 and 6 in all shaders so + haders can be switched without rebinding everything. Not using the + zero binding to avoid conflicts with ProjectionBufferBinding from + other shaders which can likely stay bound to the same buffer for the + whole time. */ TransformationProjectionBufferBinding = 1, DrawBufferBinding = 2, TextureTransformationBufferBinding = 3, - MaterialBufferBinding = 4 + MaterialBufferBinding = 4, + /* 5 unused */ + JointBufferBinding = 6, }; #endif } @@ -96,6 +102,13 @@ template typename FlatGL::CompileState FlatG "Shaders::FlatGL: texture arrays require texture transformation enabled as well if uniform buffers are used", CompileState{NoCreate}); #endif + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(configuration.flags() & Flag::DynamicPerVertexJointCount) || configuration.jointCount(), + "Shaders::FlatGL: dynamic per-vertex joint count enabled for zero joints", CompileState{NoCreate}); + CORRADE_ASSERT(!(configuration.flags() & Flag::InstancedTransformation) || !configuration.secondaryPerVertexJointCount(), + "Shaders::FlatGL: TransformationMatrix attribute binding conflicts with the SecondaryJointIds / SecondaryWeights attributes, use a non-instanced rendering with secondary weights instead", CompileState{NoCreate}); + #endif + #ifndef MAGNUM_TARGET_GLES if(configuration.flags() >= Flag::UniformBuffers) MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); @@ -131,6 +144,19 @@ template typename FlatGL::CompileState FlatG const GL::Version version = context.supportedVersion({GL::Version::GLES300, GL::Version::GLES200}); #endif + FlatGL out{NoInit}; + out._flags = configuration.flags(); + #ifndef MAGNUM_TARGET_GLES2 + out._jointCount = configuration.jointCount(); + out._perVertexJointCount = configuration.perVertexJointCount(); + out._secondaryPerVertexJointCount = configuration.secondaryPerVertexJointCount(); + out._materialCount = configuration.materialCount(); + out._drawCount = configuration.drawCount(); + out._perInstanceJointCountUniform = out._jointMatricesUniform + configuration.jointCount(); + out._perVertexJointCountUniform = configuration.flags() >= Flag::UniformBuffers ? + 1 : out._perInstanceJointCountUniform + 1; + #endif + GL::Shader vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); @@ -151,6 +177,31 @@ template typename FlatGL::CompileState FlatG .addSource(configuration.flags() & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") .addSource(configuration.flags() >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : ""); #ifndef MAGNUM_TARGET_GLES2 + if(configuration.jointCount()) { + vert.addSource(Utility::formatString( + "#define JOINT_COUNT {}\n" + "#define PER_VERTEX_JOINT_COUNT {}u\n" + "#define SECONDARY_PER_VERTEX_JOINT_COUNT {}u\n" + #ifndef MAGNUM_TARGET_GLES + "#define JOINT_MATRIX_INITIALIZER {}\n" + #endif + "#define PER_INSTANCE_JOINT_COUNT_LOCATION {}\n", + configuration.jointCount(), + configuration.perVertexJointCount(), + configuration.secondaryPerVertexJointCount(), + #ifndef MAGNUM_TARGET_GLES + ((dimensions == 2 ? "mat3(1.0), "_s : "mat4(1.0), "_s)*configuration.jointCount()).exceptSuffix(2), + #endif + out._perInstanceJointCountUniform)); + } + if(configuration.flags() >= Flag::DynamicPerVertexJointCount) { + vert.addSource(Utility::formatString( + "#define DYNAMIC_PER_VERTEX_JOINT_COUNT\n" + "#define PER_VERTEX_JOINT_COUNT_LOCATION {}\n", + out._perVertexJointCountUniform)); + } + #endif + #ifndef MAGNUM_TARGET_GLES2 if(configuration.flags() >= Flag::UniformBuffers) { vert.addSource(Utility::formatString( "#define UNIFORM_BUFFERS\n" @@ -190,13 +241,6 @@ template typename FlatGL::CompileState FlatG vert.submitCompile(); frag.submitCompile(); - FlatGL out{NoInit}; - out._flags = configuration.flags(); - #ifndef MAGNUM_TARGET_GLES2 - out._materialCount = configuration.materialCount(); - out._drawCount = configuration.drawCount(); - #endif - out.attachShaders({vert, frag}); /* ES3 has this done in the shader directly and doesn't even provide @@ -227,6 +271,19 @@ template typename FlatGL::CompileState FlatG out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); if(configuration.flags() >= Flag::InstancedTextureOffset) out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + #ifndef MAGNUM_TARGET_GLES2 + /* Configuration::setJointCount() checks that jointCount and + perVertexJointCount / secondaryPerVertexJointCount are either all + zero or non-zero so we don't need to check for jointCount() here */ + if(configuration.perVertexJointCount()) { + out.bindAttributeLocation(Weights::Location, "weights"); + out.bindAttributeLocation(JointIds::Location, "jointIds"); + } + if(configuration.secondaryPerVertexJointCount()) { + out.bindAttributeLocation(SecondaryWeights::Location, "secondaryWeights"); + out.bindAttributeLocation(SecondaryJointIds::Location, "secondaryJointIds"); + } + #endif } #endif @@ -270,6 +327,8 @@ template FlatGL::FlatGL(CompileState&& state #endif { #ifndef MAGNUM_TARGET_GLES2 + if(_flags >= Flag::DynamicPerVertexJointCount) + _perVertexJointCountUniform = uniformLocation("perVertexJointCount"); if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else @@ -287,6 +346,12 @@ template FlatGL::FlatGL(CompileState&& state #ifndef MAGNUM_TARGET_GLES2 if(_flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); #endif + #ifndef MAGNUM_TARGET_GLES2 + if(_jointCount) { + _jointMatricesUniform = uniformLocation("jointMatrices"); + _perInstanceJointCountUniform = uniformLocation("perInstanceJointCount"); + } + #endif } } @@ -303,6 +368,8 @@ template FlatGL::FlatGL(CompileState&& state if(_flags & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + if(_jointCount) + setUniformBlockBinding(uniformBlockIndex("Joint"), JointBufferBinding); } #endif } @@ -310,6 +377,10 @@ template FlatGL::FlatGL(CompileState&& state /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 + if(_flags >= Flag::DynamicPerVertexJointCount) + setPerVertexJointCount(_perVertexJointCount, _secondaryPerVertexJointCount); + #endif + #ifndef MAGNUM_TARGET_GLES2 if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else @@ -322,6 +393,12 @@ template FlatGL::FlatGL(CompileState&& state setColor(Magnum::Color4{1.0f}); if(_flags & Flag::AlphaMask) setAlphaMask(0.5f); /* Object ID is zero by default */ + #ifndef MAGNUM_TARGET_GLES2 + if(_jointCount) { + setJointMatrices(Containers::Array>{DirectInit, _jointCount, Math::IdentityInit}); + /* Per-instance joint count is zero by default */ + } + #endif } #endif } @@ -344,6 +421,19 @@ template FlatGL::FlatGL(const Flags flags, c template FlatGL::FlatGL(NoInitT) {} +#ifndef MAGNUM_TARGET_GLES2 +template FlatGL& FlatGL::setPerVertexJointCount(const UnsignedInt count, const UnsignedInt secondaryCount) { + CORRADE_ASSERT(_flags >= Flag::DynamicPerVertexJointCount, + "Shaders::FlatGL::setPerVertexJointCount(): the shader was not created with dynamic per-vertex joint count enabled", *this); + CORRADE_ASSERT(count <= _perVertexJointCount, + "Shaders::FlatGL::setPerVertexJointCount(): expected at most" << _perVertexJointCount << "per-vertex joints, got" << count, *this); + CORRADE_ASSERT(secondaryCount <= _secondaryPerVertexJointCount, + "Shaders::FlatGL::setPerVertexJointCount(): expected at most" << _secondaryPerVertexJointCount << "secondary per-vertex joints, got" << secondaryCount, *this); + setUniform(_perVertexJointCountUniform, Vector2ui{count, secondaryCount}); + return *this; +} +#endif + template FlatGL& FlatGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), @@ -404,6 +494,35 @@ template FlatGL& FlatGL::setObje setUniform(_objectIdUniform, id); return *this; } + +template FlatGL& FlatGL::setJointMatrices(const Containers::ArrayView> matrices) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setJointMatrices(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_jointCount == matrices.size(), + "Shaders::FlatGL::setJointMatrices(): expected" << _jointCount << "items but got" << matrices.size(), *this); + if(_jointCount) setUniform(_jointMatricesUniform, matrices); + return *this; +} + +template FlatGL& FlatGL::setJointMatrices(const std::initializer_list> matrices) { + return setJointMatrices(Containers::arrayView(matrices)); +} + +template FlatGL& FlatGL::setJointMatrix(const UnsignedInt id, const MatrixTypeFor& matrix) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setJointMatrix(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(id < _jointCount, + "Shaders::FlatGL::setJointMatrix(): joint ID" << id << "is out of bounds for" << _jointCount << "joints", *this); + setUniform(_jointMatricesUniform + id, matrix); + return *this; +} + +template FlatGL& FlatGL::setPerInstanceJointCount(const UnsignedInt count) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setPerInstanceJointCount(): the shader was created with uniform buffers enabled", *this); + setUniform(_perInstanceJointCountUniform, count); + return *this; +} #endif #ifndef MAGNUM_TARGET_GLES2 @@ -475,6 +594,20 @@ template FlatGL& FlatGL::bindMat buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); return *this; } + +template FlatGL& FlatGL::bindJointBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindJointBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, JointBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindJointBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindJointBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, JointBufferBinding, offset, size); + return *this; +} #endif template FlatGL& FlatGL::bindTexture(GL::Texture2D& texture) { @@ -521,6 +654,21 @@ template FlatGL& FlatGL::bindObj } #endif +#ifndef MAGNUM_TARGET_GLES2 +template typename FlatGL::Configuration& FlatGL::Configuration::setJointCount(UnsignedInt count, UnsignedInt perVertexCount, UnsignedInt secondaryPerVertexCount) { + CORRADE_ASSERT(perVertexCount <= 4, + "Shaders::FlatGL::Configuration::setJointCount(): expected at most 4 per-vertex joints, got" << perVertexCount, *this); + CORRADE_ASSERT(secondaryPerVertexCount <= 4, + "Shaders::FlatGL::Configuration::setJointCount(): expected at most 4 secondary per-vertex joints, got" << secondaryPerVertexCount, *this); + CORRADE_ASSERT(!count == (!perVertexCount && !secondaryPerVertexCount), + "Shaders::FlatGL::Configuration::setJointCount(): either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero", *this); + _jointCount = count; + _perVertexJointCount = perVertexCount; + _secondaryPerVertexJointCount = secondaryPerVertexCount; + return *this; +} +#endif + template class MAGNUM_SHADERS_EXPORT FlatGL<2>; template class MAGNUM_SHADERS_EXPORT FlatGL<3>; @@ -555,6 +703,7 @@ Debug& operator<<(Debug& debug, const FlatGLFlag value) { _c(UniformBuffers) _c(MultiDraw) _c(TextureArrays) + _c(DynamicPerVertexJointCount) #endif #undef _c /* LCOV_EXCL_STOP */ @@ -583,7 +732,8 @@ Debug& operator<<(Debug& debug, const FlatGLFlags value) { #ifndef MAGNUM_TARGET_GLES2 FlatGLFlag::MultiDraw, /* Superset of UniformBuffers */ FlatGLFlag::UniformBuffers, - FlatGLFlag::TextureArrays + FlatGLFlag::TextureArrays, + FlatGLFlag::DynamicPerVertexJointCount, #endif }); } diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index b13442300..7ead11daa 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -55,7 +55,8 @@ namespace Implementation { #ifndef MAGNUM_TARGET_GLES2 UniformBuffers = 1 << 8, MultiDraw = UniformBuffers|(1 << 9), - TextureArrays = 1 << 10 + TextureArrays = 1 << 10, + DynamicPerVertexJointCount = 1 << 12 #endif }; typedef Containers::EnumSet FlatGLFlags; @@ -142,6 +143,26 @@ will contain a sum of the per-vertex ID, texture ID and ID coming from @requires_webgl20 Object ID output requires integer support in shaders, which is not available in WebGL 1.0. +@section Shaders-FlatGL-skinning Skinning + +To render skinned meshes, bind up to two sets of up to four-component joint ID +and weight attributes to @ref JointIds / @ref SecondaryJointIds and +@ref Weights / @ref SecondaryWeights, set an appropriate joint count and +per-vertex primary and secondary joint count in +@ref Configuration::setJointCount() and upload appropriate joint matrices with +@ref setJointMatrices(). + +To avoid having to compile multiple shader variants for different per-vertex +joint counts, enable @ref Flag::DynamicPerVertexJointCount, set the maximum +per-vertex joint count in @ref Configuration::setJointCount() and then adjust +the actual per-draw joint count with @ref setPerVertexJointCount(). + +@requires_gl30 Extension @gl_extension{EXT,texture_integer} +@requires_gles30 Skinning requires integer support in shaders, which is not + available in OpenGL ES 2.0. +@requires_webgl20 Skinning requires integer support in shaders, which is not + available in WebGL 1.0. + @section Shaders-FlatGL-instancing Instanced rendering Enabling @ref Flag::InstancedTransformation will turn the shader into an @@ -157,6 +178,12 @@ color to a mesh: @snippet MagnumShaders-gl.cpp FlatGL-usage-instancing +For instanced skinning the joint buffer is assumed to contain joint +transformations for all instances. By default all instances use the same joint +transformations, seting @ref setPerInstanceJointCount() will cause the shader +to offset the per-vertex joint IDs with +@glsl gl_InstanceID*perInstanceJointCount @ce. + @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, @gl_extension{EXT,instanced_arrays} or @gl_extension{NV,instanced_arrays} @@ -189,6 +216,17 @@ For a multidraw workflow enable @ref Flag::MultiDraw (and possibly texture offsets/layers for every draw. The usage is similar for all shaders, see @ref shaders-usage-multidraw for an example. +For skinning, joint matrices are supplied via a @ref TransformationUniform2D / +@ref TransformationUniform3D buffer bound with @ref bindJointBuffer(). In an +instanced scenario the per-instance joint count is supplied via +@ref FlatDrawUniform::perInstanceJointCount, a per-draw joint offset for the +multidraw scenario is supplied via @ref FlatDrawUniform::jointOffset. +Altogether for a particular draw, each per-vertex joint ID is offset with +@glsl gl_InstanceID*perInstanceJointCount + jointOffset @ce. The +@ref setPerVertexJointCount() stays as an immediate uniform in the UBO and +multidraw scenario as well, as it is tied to a particular mesh layout and thus +doesn't need to vary per draw. + @requires_gl30 Extension @gl_extension{EXT,texture_array} for texture arrays. @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform buffers. @@ -207,7 +245,8 @@ see @ref shaders-usage-multidraw for an example. */ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL::AbstractShaderProgram { public: - class Configuration; + /* MSVC needs dllexport here as well */ + class MAGNUM_SHADERS_EXPORT Configuration; class CompileState; /** @@ -248,6 +287,64 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: */ typedef typename GenericGL::Color4 Color4; + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint ids + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4ui. + * Used only if @ref perVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::JointIds JointIds; + + /** + * @brief Weights + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4. + * Used only if @ref perVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::Weights Weights; + + /** + * @brief Secondary joint ids + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4ui. + * Used only if @ref secondaryPerVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::SecondaryJointIds SecondaryJointIds; + + /** + * @brief Secondary weights + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4. + * Used only if @ref secondaryPerVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::SecondaryWeights SecondaryWeights; + #endif + #ifndef MAGNUM_TARGET_GLES2 /** * @brief (Instanced) object ID @@ -532,7 +629,30 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * @requires_webgl20 Texture arrays are not available in WebGL 1.0. * @m_since_latest */ - TextureArrays = 1 << 10 + TextureArrays = 1 << 10, + + /** + * Dynamic per-vertex joint count for skinning. Uses only the first + * M / N primary / secondary components defined by + * @ref setPerVertexJointCount() instead of + * all primary / secondary components defined by + * @ref Configuration::setJointCount() at shader compilation time. + * Useful in order to avoid having a shader permutation defined for + * every possible joint count. Unfortunately it's not possible to + * make use of default values for unspecified input components as + * the last component is always @cpp 1.0 @ce instead of + * @cpp 0.0 @ce, on the other hand dynamically limiting the joint + * count can reduce the time spent executing the vertex shader + * compared to going through the full set of per-vertex joints + * always. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, + * which is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + * @m_since_latest + */ + DynamicPerVertexJointCount = 1 << 12, #endif }; @@ -663,6 +783,48 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: */ Flags flags() const { return _flags; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint count + * @m_since_latest + * + * If @ref Flag::UniformBuffers is not set, this is the number of joint + * matrices accepted by @ref setJointMatrices() / @ref setJointMatrix(). + * If @ref Flag::UniformBuffers is set, this is the statically defined + * size of the @ref TransformationUniform2D / + * @ref TransformationUniform3D uniform buffer bound with + * @ref bindJointBuffer(). + * @see @ref Configuration::setJointCount() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt jointCount() const { return _jointCount; } + + /** + * @brief Per-vertex joint count + * @m_since_latest + * + * Returns the value set with @ref Configuration::setJointCount(). If + * @ref Flag::DynamicPerVertexJointCount is set, the count can be + * additionally modified per-draw using @ref setPerVertexJointCount(). + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt perVertexJointCount() const { return _perVertexJointCount; } + + /** + * @brief Secondary per-vertex joint count + * @m_since_latest + * + * Returns the value set with @ref Configuration::setJointCount(). If + * @ref Flag::DynamicPerVertexJointCount is set, the count can be + * additionally modified per-draw using @ref setPerVertexJointCount(). + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt secondaryPerVertexJointCount() const { return _secondaryPerVertexJointCount; } + #endif + #ifndef MAGNUM_TARGET_GLES2 /** * @brief Material count @@ -695,6 +857,34 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: UnsignedInt drawCount() const { return _drawCount; } #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set dynamic per-vertex skinning joint count + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Allows reducing the count of iterated joints for a particular draw + * call, making it possible to use a single shader with meshes that + * contain different count of per-vertex joints. See + * @ref Flag::DynamicPerVertexJointCount for more information. As the + * joint count is tied to the mesh layout, this is a per-draw-call + * setting even in case of @ref Flag::UniformBuffers instead of being + * a value in @ref FlatDrawUniform. Initial value is the same as + * @ref perVertexJointCount() and @ref secondaryPerVertexJointCount(). + * + * Expects that @ref Flag::DynamicPerVertexJointCount is set, + * @p count is not larger than @ref perVertexJointCount() and + * @p secondaryCount not larger than @ref secondaryPerVertexJointCount(). + * @see @ref Configuration::setJointCount() + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, which + * is not available in WebGL 1.0. + */ + FlatGL& setPerVertexJointCount(UnsignedInt count, UnsignedInt secondaryCount = 0); + #endif + /** @{ * @name Uniform setters * @@ -818,6 +1008,78 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: FlatGL& setObjectId(UnsignedInt id); #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set joint matrices + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Initial values are identity transformations. Expects that the size + * of the @p matrices array is the same as @ref jointCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform2D::transformationMatrix / + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindJointBuffer() instead. + * @see @ref setJointMatrix(UnsignedInt, const MatrixTypeFor&) + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + FlatGL& setJointMatrices(const Containers::ArrayView> matrices); + + /** + * @overload + * @m_since_latest + */ + FlatGL& setJointMatrices(std::initializer_list> matrices); + + /** + * @brief Set joint matrix for given joint + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Unlike @ref setJointMatrices() updates just a single joint matrix. + * Expects that @p id is less than @ref jointCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform2D::transformationMatrix / + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindJointBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + FlatGL& setJointMatrix(UnsignedInt id, const MatrixTypeFor& matrix); + + /** + * @brief Set per-instance joint count + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Offset added to joint IDs in the @ref JointIds and + * @ref SecondaryJointIds in instanced draws. Should be less than + * @ref jointCount(). Initial value is @cpp 0 @ce, meaning every + * instance will use the same joint matrices, setting it to a non-zero + * value causes the joint IDs to be interpreted as + * @glsl gl_InstanceID*count + jointId @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref FlatDrawUniform::perInstanceJointCount and call + * @ref bindDrawBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + FlatGL& setPerInstanceJointCount(UnsignedInt count); + #endif + /** * @} */ @@ -936,6 +1198,25 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: */ FlatGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + /** + * @brief Bind a joint matrix 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 jointCount() instances of + * @ref TransformationUniform2D / @ref TransformationUniform3D. + * @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& bindJointBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindJointBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + /** * @} */ @@ -1036,7 +1317,11 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: Flags _flags; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt _materialCount{}, _drawCount{}; + UnsignedInt _jointCount{}, + _perVertexJointCount{}, + _secondaryPerVertexJointCount{}, + _materialCount{}, + _drawCount{}; #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, @@ -1046,10 +1331,13 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: _colorUniform{3}, _alphaMaskUniform{4}; #ifndef MAGNUM_TARGET_GLES2 - Int _objectIdUniform{5}; - /* Used instead of all other uniforms when Flag::UniformBuffers is set, - so it can alias them */ - Int _drawOffsetUniform{0}; + Int _objectIdUniform{5}, + _jointMatricesUniform{6}, + _perInstanceJointCountUniform, /* 6 + jointCount */ + /* Used instead of all other uniforms when Flag::UniformBuffers is + set, so it can alias them */ + _drawOffsetUniform{0}, + _perVertexJointCountUniform; /* 7 + jointCount, or 1 with UBOs */ #endif }; @@ -1059,7 +1347,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: @see @ref FlatGL(const Configuration&), @ref compile(const Configuration&) */ -template class FlatGL::Configuration { +template class MAGNUM_SHADERS_EXPORT FlatGL::Configuration { public: explicit Configuration() = default; @@ -1078,6 +1366,66 @@ template class FlatGL::Configuration { } #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt jointCount() const { return _jointCount; } + + /** + * @brief Per-vertex joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt perVertexJointCount() const { return _perVertexJointCount; } + + /** + *@brief Secondary per-vertex joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt secondaryPerVertexJointCount() const { return _secondaryPerVertexJointCount; } + + /** + * @brief Set joint count + * + * If @ref Flag::UniformBuffers isn't set, @p count describes how many + * joint matrices get supplied to each draw by @ref setJointMatrices() + * / @ref setJointMatrix(). If @ref Flag::UniformBuffers is set, + * @p count describes size of a @ref TransformationUniform2D / + * @ref TransformationUniform3D buffer bound with + * @ref bindJointBuffer(); as uniform buffers are required to have a + * statically defined size. The per-vertex joints then index into the + * array offset by @ref FlatDrawUniform::jointOffset. If @p count is + * @cpp 0 @ce, skinning is not performed. + * + * The @p perVertexCount and @p secondaryPerVertexCount then describe + * how many components are taken from @ref JointIds / @ref Weights and + * @ref SecondaryJointIds / @ref SecondaryWeights attributes. Both + * values are expected to not be larger than @cpp 4 @ce, setting either + * of these to @cpp 0 @ce means given attribute is not used at all. If + * @p count is @cpp 0 @ce, both @p perVertexCount and + * @p secondaryPerVertexCount is expected to be @cpp 0 @ce as well; if + * @p count is non-zero at least one of @p perVertexCount and + * @p secondaryPerVertexCount is expected to be non-zero as well. + * + * Default value for all three is @cpp 0 @ce. + * @see @ref FlatGL::jointCount(), @ref FlatGL::perVertexJointCount(), + * @ref FlatGL::secondaryPerVertexJointCount(), + * @ref Flag::DynamicPerVertexJointCount, + * @ref FlatGL::setPerVertexJointCount() + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + Configuration& setJointCount(UnsignedInt count, UnsignedInt perVertexCount, UnsignedInt secondaryPerVertexCount = 0); + /** * @brief Material count * @@ -1144,7 +1492,10 @@ template class FlatGL::Configuration { private: Flags _flags; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt _materialCount = 1, + UnsignedInt _jointCount = 0, + _perVertexJointCount = 0, + _secondaryPerVertexJointCount = 0, + _materialCount = 1, _drawCount = 1; #endif }; diff --git a/src/Magnum/Shaders/MeshVisualizer.frag b/src/Magnum/Shaders/MeshVisualizer.frag index 1dfa4d006..5dea76e96 100644 --- a/src/Magnum/Shaders/MeshVisualizer.frag +++ b/src/Magnum/Shaders/MeshVisualizer.frag @@ -144,9 +144,10 @@ struct DrawUniform { #elif !defined(TWO_DIMENSIONS) #error #endif - highp uvec4 materialIdReservedObjectIdReservedReserved; - #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x - #define draw_objectId materialIdReservedObjectIdReservedReserved.y + highp uvec4 materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved; + #define draw_materialIdReserved materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.x + #define draw_objectId materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.y + #define draw_jointOffsetPerInstanceJointCount materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.z }; layout(std140 diff --git a/src/Magnum/Shaders/MeshVisualizer.geom b/src/Magnum/Shaders/MeshVisualizer.geom index ff55c5830..c8cdfb1f8 100644 --- a/src/Magnum/Shaders/MeshVisualizer.geom +++ b/src/Magnum/Shaders/MeshVisualizer.geom @@ -108,9 +108,10 @@ struct DrawUniform { #elif !defined(TWO_DIMENSIONS) #error #endif - highp uvec4 materialIdReservedObjectIdReservedReserved; - #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x - #define draw_objectId materialIdReservedObjectIdReservedReserved.y + highp uvec4 materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved; + #define draw_materialIdReserved materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.x + #define draw_objectId materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.y + #define draw_jointOffsetPerInstanceJointCount materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.z }; layout(std140 diff --git a/src/Magnum/Shaders/MeshVisualizer.h b/src/Magnum/Shaders/MeshVisualizer.h index 0f5352b9a..d6ae515f9 100644 --- a/src/Magnum/Shaders/MeshVisualizer.h +++ b/src/Magnum/Shaders/MeshVisualizer.h @@ -54,7 +54,13 @@ separate @ref MeshVisualizerMaterialUniform structure, referenced by */ struct MeshVisualizerDrawUniform2D { /** @brief Construct with default parameters */ - constexpr explicit MeshVisualizerDrawUniform2D(DefaultInitT = DefaultInit) noexcept: materialId{0}, objectId{0} {} + constexpr explicit MeshVisualizerDrawUniform2D(DefaultInitT = DefaultInit) noexcept: materialId{0}, objectId{0}, + #ifndef CORRADE_TARGET_BIG_ENDIAN + jointOffset{0}, perInstanceJointCount{0} + #else + perInstanceJointCount{0}, jointOffset{0} + #endif + {} /** @brief Construct without initializing the contents */ explicit MeshVisualizerDrawUniform2D(NoInitT) noexcept {} @@ -86,6 +92,24 @@ struct MeshVisualizerDrawUniform2D { return *this; } + /** + * @brief Set the @ref jointOffset field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform2D& setJointOffset(UnsignedInt offset) { + jointOffset = offset; + return *this; + } + + /** + * @brief Set the @ref perInstanceJointCount field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform2D& setPerInstanceJointCount(UnsignedInt count) { + perInstanceJointCount = count; + return *this; + } + /** * @} */ @@ -111,10 +135,10 @@ struct MeshVisualizerDrawUniform2D { /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT - UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; /* reserved */ #endif #else - UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; /* reserved */ UnsignedShort materialId; #endif @@ -133,11 +157,43 @@ struct MeshVisualizerDrawUniform2D { */ UnsignedInt objectId; + /** @var jointOffset + * @brief Joint offset + * + * Offset added to joint IDs in the @ref MeshVisualizerGL2D::JointIds and + * @ref MeshVisualizerGL2D::SecondaryJointIds attributes. Useful when a UBO + * with joint matrices for more than one skin is supplied or in a + * multi-draw scenario. Should be less than the joint count passed to + * @ref MeshVisualizerGL2D::Configuration::setJointCount(). Default value + * is @cpp 0 @ce, meaning no offset is added to joint IDs. + */ + + /** @var perInstanceJointCount + * @brief Per-instance joint count + * + * Offset added to joint IDs in the @ref MeshVisualizerGL2D::JointIds and + * @ref MeshVisualizerGL2D::SecondaryJointIds atttributes in instanced + * draws. Should be less than the joint count passed to + * @ref MeshVisualizerGL2D::Configuration::setJointCount(). Default value + * is @cpp 0 @ce, meaning every instance will use the same joint matrices, + * setting it to a non-zero value causes the joint IDs to be interpreted as + * @glsl gl_InstanceID*count + jointId @ce. + */ + + /* This field is an UnsignedInt in the shader and jointOffset is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort jointOffset; + UnsignedShort perInstanceJointCount; + #else + UnsignedShort perInstanceJointCount; + UnsignedShort jointOffset; + #endif + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT Int:32; - Int:32; #endif }; @@ -153,7 +209,13 @@ shared among multiple draw calls and thus are provided in a separate */ struct MeshVisualizerDrawUniform3D { /** @brief Construct with default parameters */ - constexpr explicit MeshVisualizerDrawUniform3D(DefaultInitT = DefaultInit) noexcept: normalMatrix{Math::IdentityInit}, materialId{0}, objectId{0} {} + constexpr explicit MeshVisualizerDrawUniform3D(DefaultInitT = DefaultInit) noexcept: normalMatrix{Math::IdentityInit}, materialId{0}, objectId{0}, + #ifndef CORRADE_TARGET_BIG_ENDIAN + jointOffset{0}, perInstanceJointCount{0} + #else + perInstanceJointCount{0}, jointOffset{0} + #endif + {} /** @brief Construct without initializing the contents */ explicit MeshVisualizerDrawUniform3D(NoInitT) noexcept: normalMatrix{NoInit} {} @@ -197,6 +259,24 @@ struct MeshVisualizerDrawUniform3D { return *this; } + /** + * @brief Set the @ref jointOffset field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform3D& setJointOffset(UnsignedInt offset) { + jointOffset = offset; + return *this; + } + + /** + * @brief Set the @ref perInstanceJointCount field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform3D& setPerInstanceJointCount(UnsignedInt count) { + perInstanceJointCount = count; + return *this; + } + /** * @} */ @@ -228,10 +308,10 @@ struct MeshVisualizerDrawUniform3D { /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT - UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; /* reserved */ #endif #else - UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; /* reserved */ UnsignedShort materialId; #endif @@ -250,11 +330,43 @@ struct MeshVisualizerDrawUniform3D { */ UnsignedInt objectId; + /** @var jointOffset + * @brief Joint offset + * + * Offset added to joint IDs in the @ref MeshVisualizerGL2D::JointIds and + * @ref MeshVisualizerGL2D::SecondaryJointIds attributes. Useful when a UBO + * with joint matrices for more than one skin is supplied or in a + * multi-draw scenario. Should be less than the joint count passed to + * @ref MeshVisualizerGL2D::Configuration::setJointCount(). Default value + * is @cpp 0 @ce, meaning no offset is added to joint IDs. + */ + + /** @var perInstanceJointCount + * @brief Per-instance joint count + * + * Offset added to joint IDs in the @ref MeshVisualizerGL2D::JointIds and + * @ref MeshVisualizerGL2D::SecondaryJointIds atttributes in instanced + * draws. Should be less than the joint count passed to + * @ref MeshVisualizerGL2D::Configuration::setJointCount(). Default value + * is @cpp 0 @ce, meaning every instance will use the same joint matrices, + * setting it to a non-zero value causes the joint IDs to be interpreted as + * @glsl gl_InstanceID*count + jointId @ce. + */ + + /* This field is an UnsignedInt in the shader and jointOffset is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort jointOffset; + UnsignedShort perInstanceJointCount; + #else + UnsignedShort perInstanceJointCount; + UnsignedShort jointOffset; + #endif + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT Int:32; - Int:32; #endif }; diff --git a/src/Magnum/Shaders/MeshVisualizer.vert b/src/Magnum/Shaders/MeshVisualizer.vert index 6e86c2826..f57b45bf1 100644 --- a/src/Magnum/Shaders/MeshVisualizer.vert +++ b/src/Magnum/Shaders/MeshVisualizer.vert @@ -48,6 +48,19 @@ #define const #endif +/* Both classic uniforms and uniform buffers */ + +#ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = PER_VERTEX_JOINT_COUNT_LOCATION) +#endif +uniform mediump uvec2 perVertexJointCount + #ifndef GL_ES + = uvec2(PER_VERTEX_JOINT_COUNT, SECONDARY_PER_VERTEX_JOINT_COUNT) + #endif + ; +#endif + /* Uniforms */ #ifndef UNIFORM_BUFFERS @@ -133,6 +146,32 @@ layout(location = 8) uniform highp uint textureLayer; /* defaults to zero */ #endif +#ifdef JOINT_COUNT +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 14) +#endif +#ifdef TWO_DIMENSIONS +uniform mat3 jointMatrices[JOINT_COUNT] + #ifndef GL_ES + = mat3[](JOINT_MATRIX_INITIALIZER) + #endif + ; +#elif defined(THREE_DIMENSIONS) +uniform mat4 jointMatrices[JOINT_COUNT] + #ifndef GL_ES + = mat4[](JOINT_MATRIX_INITIALIZER) + #endif + ; +#else +#error +#endif + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = PER_INSTANCE_JOINT_COUNT_LOCATION) +#endif +uniform uint perInstanceJointCount; /* defaults to zero */ +#endif + /* Uniform buffers */ #else @@ -206,9 +245,10 @@ struct DrawUniform { #elif !defined(TWO_DIMENSIONS) #error #endif - highp uvec4 materialIdReservedObjectIdReservedReserved; - #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x - #define draw_objectId materialIdReservedObjectIdReservedReserved.y + highp uvec4 materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved; + #define draw_materialIdReserved materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.x + #define draw_objectId materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.y + #define draw_jointOffsetPerInstanceJointCount materialIdReservedObjectIdJointOffsetPerInstanceJointCountReserved.z }; layout(std140 @@ -242,6 +282,26 @@ layout(std140 ) uniform Material { MaterialUniform materials[MATERIAL_COUNT]; }; + +#ifdef JOINT_COUNT +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 6 + #endif +) uniform Joint { + highp + #ifdef TWO_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see DrawUniform in Phong.vert for + details */ + mat3x4 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + jointMatrices[JOINT_COUNT]; +}; +#endif #endif /* Inputs */ @@ -285,6 +345,32 @@ layout(location = TEXTURECOORDINATES_ATTRIBUTE_LOCATION) in mediump vec2 textureCoordinates; #endif +#ifdef JOINT_COUNT +#if PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = WEIGHTS_ATTRIBUTE_LOCATION) +#endif +in mediump vec4 weights; + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = JOINTIDS_ATTRIBUTE_LOCATION) +#endif +in mediump uvec4 jointIds; +#endif + +#if SECONDARY_PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = SECONDARY_WEIGHTS_ATTRIBUTE_LOCATION) +#endif +in mediump vec4 secondaryWeights; + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = SECONDARY_JOINTIDS_ATTRIBUTE_LOCATION) +#endif +in mediump uvec4 secondaryJointIds; +#endif +#endif + #ifdef INSTANCED_TEXTURE_OFFSET #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = TEXTURE_OFFSET_ATTRIBUTE_LOCATION) @@ -442,6 +528,49 @@ void main() { highp const uint textureLayer = floatBitsToUint(textureTransformations[drawId].textureTransformation_layer); #endif #endif + #ifdef JOINT_COUNT + mediump const uint jointOffset = (draws[drawId].draw_jointOffsetPerInstanceJointCount & 0xffffu) + uint(gl_InstanceID)*(draws[drawId].draw_jointOffsetPerInstanceJointCount >> 16 & 0xffffu); + #endif + #else + #ifdef JOINT_COUNT + mediump const uint jointOffset = uint(gl_InstanceID)*perInstanceJointCount; + #endif + #endif + + #ifdef JOINT_COUNT + #ifdef TWO_DIMENSIONS + mat3 skinMatrix = mat3(0.0); + #elif defined(THREE_DIMENSIONS) + mat4 skinMatrix = mat4(0.0); + #else + #error + #endif + #if PER_VERTEX_JOINT_COUNT + for(uint i = 0u; i != PER_VERTEX_JOINT_COUNT + #ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT + && i != perVertexJointCount.x + #endif + ; ++i) + skinMatrix += weights[i]* + #ifdef TWO_DIMENSIONS + mat3 /* need to slice because it's a mat3x4 due to ANGLE, see + DrawUniform in Phong.vert for details */ + #endif + (jointMatrices[jointOffset + jointIds[i]]); + #endif + #if SECONDARY_PER_VERTEX_JOINT_COUNT + for(uint i = 0u; i != SECONDARY_PER_VERTEX_JOINT_COUNT + #ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT + && i != perVertexJointCount.y + #endif + ; ++i) + skinMatrix += secondaryWeights[i]* + #ifdef TWO_DIMENSIONS + mat3 /* need to slice because it's a mat3x4 due to ANGLE, see + DrawUniform in Phong.vert for details */ + #endif + (jointMatrices[jointOffset + secondaryJointIds[i]]); + #endif #endif #ifdef TWO_DIMENSIONS @@ -449,12 +578,18 @@ void main() { #ifdef INSTANCED_TRANSFORMATION instancedTransformationMatrix* #endif + #ifdef JOINT_COUNT + skinMatrix* + #endif vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) highp const vec4 transformedPosition4 = transformationMatrix* #ifdef INSTANCED_TRANSFORMATION instancedTransformationMatrix* #endif + #ifdef JOINT_COUNT + skinMatrix* + #endif position; gl_Position = projectionMatrix*transformedPosition4; #else diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 484705ab9..85d516206 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -48,6 +48,8 @@ namespace Magnum { namespace Shaders { +using namespace Containers::Literals; + namespace { enum: Int { /* First four taken by Phong (A/D/S/N) */ @@ -57,6 +59,9 @@ namespace { #ifndef MAGNUM_TARGET_GLES2 enum: Int { + /* Projection, transformation, texture transformation and joints is + slots 0, 1, 3, 6 in all shaders so shaders can be switched without + rebinding everything */ ProjectionBufferBinding = 0, /* Not using the zero binding to avoid conflicts with ProjectionBufferBinding from the 3D variant which can likely stay @@ -66,6 +71,8 @@ namespace { DrawBufferBinding = 2, TextureTransformationBufferBinding = 3, MaterialBufferBinding = 4, + /* 5 unused */ + JointBufferBinding = 6, }; #endif } @@ -142,7 +149,11 @@ void MeshVisualizerGLBase::assertExtensions(const FlagsBase flags) { GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, const FlagsBase flags #ifndef MAGNUM_TARGET_GLES2 - , const UnsignedInt materialCount, UnsignedInt const drawCount + , const UnsignedInt + #ifndef MAGNUM_TARGET_GLES + dimensions /* used for a uniform initializer, which isn't on GLSL ES */ + #endif + , const UnsignedInt jointCount, const UnsignedInt perVertexJointCount, const UnsignedInt secondaryPerVertexJointCount, const UnsignedInt materialCount, const UnsignedInt drawCount, const UnsignedInt perInstanceJointCountUniform, const UnsignedInt perVertexJointCountUniform #endif ) { GL::Context& context = GL::Context::current(); @@ -184,6 +195,31 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra #endif ; #ifndef MAGNUM_TARGET_GLES2 + if(jointCount) { + vert.addSource(Utility::formatString( + "#define JOINT_COUNT {}\n" + "#define PER_VERTEX_JOINT_COUNT {}u\n" + "#define SECONDARY_PER_VERTEX_JOINT_COUNT {}u\n" + #ifndef MAGNUM_TARGET_GLES + "#define JOINT_MATRIX_INITIALIZER {}\n" + #endif + "#define PER_INSTANCE_JOINT_COUNT_LOCATION {}\n", + jointCount, + perVertexJointCount, + secondaryPerVertexJointCount, + #ifndef MAGNUM_TARGET_GLES + ((dimensions == 2 ? "mat3(1.0), "_s : "mat4(1.0), "_s)*jointCount).exceptSuffix(2), + #endif + perInstanceJointCountUniform)); + } + if(flags >= FlagBase::DynamicPerVertexJointCount) { + vert.addSource(Utility::formatString( + "#define DYNAMIC_PER_VERTEX_JOINT_COUNT\n" + "#define PER_VERTEX_JOINT_COUNT_LOCATION {}\n", + perVertexJointCountUniform)); + } + #endif + #ifndef MAGNUM_TARGET_GLES2 if(flags >= FlagBase::UniformBuffers) { vert.addSource(Utility::formatString( "#define UNIFORM_BUFFERS\n" @@ -222,6 +258,19 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra return version; } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGLBase& MeshVisualizerGLBase::setPerVertexJointCount(const UnsignedInt count, const UnsignedInt secondaryCount) { + CORRADE_ASSERT(_flags >= FlagBase::DynamicPerVertexJointCount, + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): the shader was not created with dynamic per-vertex joint count enabled", *this); + CORRADE_ASSERT(count <= _perVertexJointCount, + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): expected at most" << _perVertexJointCount << "per-vertex joints, got" << count, *this); + CORRADE_ASSERT(secondaryCount <= _secondaryPerVertexJointCount, + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): expected at most" << _secondaryPerVertexJointCount << "secondary per-vertex joints, got" << secondaryCount, *this); + setUniform(_perVertexJointCountUniform, Vector2ui{count, secondaryCount}); + return *this; +} +#endif + #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGLBase& MeshVisualizerGLBase::setTextureMatrix(const Matrix3& matrix) { #ifndef MAGNUM_TARGET_GLES2 @@ -300,6 +349,13 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::setColorMapTransformation(const Floa setUniform(_colorMapOffsetScaleUniform, Vector2{offset, scale}); return *this; } + +MeshVisualizerGLBase& MeshVisualizerGLBase::setPerInstanceJointCount(const UnsignedInt count) { + CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), + "Shaders::MeshVisualizerGL::setPerInstanceJointCount(): the shader was created with uniform buffers enabled", *this); + setUniform(_perInstanceJointCountUniform, count); + return *this; +} #endif #ifndef MAGNUM_TARGET_GLES2 @@ -343,6 +399,20 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::bindMaterialBuffer(GL::Buffer& buffe buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); return *this; } + +MeshVisualizerGLBase& MeshVisualizerGLBase::bindJointBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= FlagBase::UniformBuffers, + "Shaders::MeshVisualizerGL::bindJointBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, JointBufferBinding); + return *this; +} + +MeshVisualizerGLBase& MeshVisualizerGLBase::bindJointBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= FlagBase::UniformBuffers, + "Shaders::MeshVisualizerGL::bindJointBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, JointBufferBinding, offset, size); + return *this; +} #endif #ifndef MAGNUM_TARGET_GLES2 @@ -398,12 +468,35 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Configuration "Shaders::MeshVisualizerGL2D: draw count can't be zero", CompileState{NoCreate}); #endif + /* Has to be here and not in the base class in order to have it exit the + constructor when testing for asserts -- GLSL compilation would fail + otherwise */ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(configuration.flags() & Flag::DynamicPerVertexJointCount) || configuration.jointCount(), + "Shaders::MeshVisualizerGL2D: dynamic per-vertex joint count enabled for zero joints", CompileState{NoCreate}); + CORRADE_ASSERT(!(configuration.flags() & Flag::InstancedTransformation) || !configuration.secondaryPerVertexJointCount(), + "Shaders::MeshVisualizerGL2D: TransformationMatrix attribute binding conflicts with the SecondaryJointIds / SecondaryWeights attributes, use a non-instanced rendering with secondary weights instead", CompileState{NoCreate}); + #endif + + MeshVisualizerGL2D out{NoInit}; + out._flags = baseFlags; + #ifndef MAGNUM_TARGET_GLES2 + out._jointCount = configuration.jointCount(); + out._perVertexJointCount = configuration.perVertexJointCount(); + out._secondaryPerVertexJointCount = configuration.secondaryPerVertexJointCount(); + out._materialCount = configuration.materialCount(); + out._drawCount = configuration.drawCount(); + out._perInstanceJointCountUniform = out._jointMatricesUniform + configuration.jointCount(); + out._perVertexJointCountUniform = configuration.flags() >= Flag::UniformBuffers ? + 2 : out._perInstanceJointCountUniform + 1; + #endif + Utility::Resource rs{"MagnumShadersGL"}; GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; const GL::Version version = setupShaders(vert, frag, rs, baseFlags #ifndef MAGNUM_TARGET_GLES2 - , configuration.materialCount(), configuration.drawCount() + , 2, configuration.jointCount(), configuration.perVertexJointCount(), configuration.secondaryPerVertexJointCount(), configuration.materialCount(), configuration.drawCount(), out._perInstanceJointCountUniform, out._perVertexJointCountUniform #endif ); Containers::Optional geom; @@ -466,13 +559,6 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Configuration frag.submitCompile(); if(geom) geom->submitCompile(); - MeshVisualizerGL2D out{NoInit}; - out._flags = baseFlags; - #ifndef MAGNUM_TARGET_GLES2 - out._materialCount = configuration.materialCount(); - out._drawCount = configuration.drawCount(); - #endif - out.attachShaders({vert, frag}); if(geom) out.attachShader(*geom); @@ -504,6 +590,19 @@ MeshVisualizerGL2D::CompileState MeshVisualizerGL2D::compile(const Configuration out.bindAttributeLocation(VertexIndex::Location, "vertexIndex"); } #endif + #ifndef MAGNUM_TARGET_GLES2 + /* Configuration::setJointCount() checks that jointCount and + perVertexJointCount / secondaryPerVertexJointCount are either all + zero or non-zero so we don't need to check for jointCount() here */ + if(configuration.perVertexJointCount()) { + out.bindAttributeLocation(Weights::Location, "weights"); + out.bindAttributeLocation(JointIds::Location, "jointIds"); + } + if(configuration.secondaryPerVertexJointCount()) { + out.bindAttributeLocation(SecondaryWeights::Location, "secondaryWeights"); + out.bindAttributeLocation(SecondaryJointIds::Location, "secondaryJointIds"); + } + #endif } #endif @@ -551,6 +650,8 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D _viewportSizeUniform = uniformLocation("viewportSize"); #ifndef MAGNUM_TARGET_GLES2 + if(flags() >= Flag::DynamicPerVertexJointCount) + _perVertexJointCountUniform = uniformLocation("perVertexJointCount"); if(flags() >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else @@ -581,6 +682,12 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D if(flags() & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); #endif + #ifndef MAGNUM_TARGET_GLES2 + if(_jointCount) { + _jointMatricesUniform = uniformLocation("jointMatrices"); + _perInstanceJointCountUniform = uniformLocation("perInstanceJointCount"); + } + #endif } } @@ -601,6 +708,8 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); if(flags() & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + if(_jointCount) + setUniformBlockBinding(uniformBlockIndex("Joint"), JointBufferBinding); } #endif } @@ -609,6 +718,10 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 + if(flags() >= Flag::DynamicPerVertexJointCount) + setPerVertexJointCount(_perVertexJointCount, _secondaryPerVertexJointCount); + #endif + #ifndef MAGNUM_TARGET_GLES2 if(flags() >= Flag::UniformBuffers) { /* Viewport size is zero by default */ /* Draw offset is zero by default */ @@ -632,6 +745,12 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(CompileState&& state): MeshVisualizerGL2D if(flags() & (Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #endif + #ifndef MAGNUM_TARGET_GLES2 + if(_jointCount) { + setJointMatrices(Containers::Array{DirectInit, _jointCount, Math::IdentityInit}); + /* Per-instance joint count is zero by default */ + } + #endif } #endif } @@ -680,6 +799,30 @@ MeshVisualizerGL2D& MeshVisualizerGL2D::setSmoothness(const Float smoothness) { return *this; } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL2D& MeshVisualizerGL2D::setJointMatrices(const Containers::ArrayView matrices) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL2D::setJointMatrices(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_jointCount == matrices.size(), + "Shaders::MeshVisualizerGL2D::setJointMatrices(): expected" << _jointCount << "items but got" << matrices.size(), *this); + if(_jointCount) setUniform(_jointMatricesUniform, matrices); + return *this; +} + +MeshVisualizerGL2D& MeshVisualizerGL2D::setJointMatrices(const std::initializer_list matrices) { + return setJointMatrices(Containers::arrayView(matrices)); +} + +MeshVisualizerGL2D& MeshVisualizerGL2D::setJointMatrix(const UnsignedInt id, const Matrix3& matrix) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL2D::setJointMatrix(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(id < _jointCount, + "Shaders::MeshVisualizerGL2D::setJointMatrix(): joint ID" << id << "is out of bounds for" << _jointCount << "joints", *this); + setUniform(_jointMatricesUniform + id, matrix); + return *this; +} +#endif + #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGL2D& MeshVisualizerGL2D::bindTransformationProjectionBuffer(GL::Buffer& buffer) { CORRADE_ASSERT(flags() >= Flag::UniformBuffers, @@ -710,6 +853,21 @@ MeshVisualizerGL2D& MeshVisualizerGL2D::bindDrawBuffer(GL::Buffer& buffer, const } #endif +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL2D::Configuration& MeshVisualizerGL2D::Configuration::setJointCount(UnsignedInt count, UnsignedInt perVertexCount, UnsignedInt secondaryPerVertexCount) { + CORRADE_ASSERT(perVertexCount <= 4, + "Shaders::MeshVisualizerGL2D::Configuration::setJointCount(): expected at most 4 per-vertex joints, got" << perVertexCount, *this); + CORRADE_ASSERT(secondaryPerVertexCount <= 4, + "Shaders::MeshVisualizerGL2D::Configuration::setJointCount(): expected at most 4 secondary per-vertex joints, got" << secondaryPerVertexCount, *this); + CORRADE_ASSERT(!count == (!perVertexCount && !secondaryPerVertexCount), + "Shaders::MeshVisualizerGL2D::Configuration::setJointCount(): either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero", *this); + _jointCount = count; + _perVertexJointCount = perVertexCount; + _secondaryPerVertexJointCount = secondaryPerVertexCount; + return *this; +} +#endif + MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(const Configuration& configuration) { FlagsBase baseFlags = Implementation::MeshVisualizerGLBase::FlagBase(UnsignedInt(configuration.flags())); assertExtensions(baseFlags); @@ -744,12 +902,35 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(const Configuration "Shaders::MeshVisualizerGL3D: draw count can't be zero", CompileState{NoCreate}); #endif + /* Has to be here and not in the base class in order to have it exit the + constructor when testing for asserts -- GLSL compilation would fail + otherwise */ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(configuration.flags() & Flag::DynamicPerVertexJointCount) || configuration.jointCount(), + "Shaders::MeshVisualizerGL3D: dynamic per-vertex joint count enabled for zero joints", CompileState{NoCreate}); + CORRADE_ASSERT(!(configuration.flags() & Flag::InstancedTransformation) || !configuration.secondaryPerVertexJointCount(), + "Shaders::MeshVisualizerGL3D: TransformationMatrix attribute binding conflicts with the SecondaryJointIds / SecondaryWeights attributes, use a non-instanced rendering with secondary weights instead", CompileState{NoCreate}); + #endif + + MeshVisualizerGL3D out{NoInit}; + out._flags = baseFlags; + #ifndef MAGNUM_TARGET_GLES2 + out._jointCount = configuration.jointCount(); + out._perVertexJointCount = configuration.perVertexJointCount(); + out._secondaryPerVertexJointCount = configuration.secondaryPerVertexJointCount(); + out._materialCount = configuration.materialCount(); + out._drawCount = configuration.drawCount(); + out._perInstanceJointCountUniform = out._jointMatricesUniform + configuration.jointCount(); + out._perVertexJointCountUniform = configuration.flags() >= Flag::UniformBuffers ? + 2 : out._perInstanceJointCountUniform + 1; + #endif + Utility::Resource rs{"MagnumShadersGL"}; GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; const GL::Version version = setupShaders(vert, frag, rs, baseFlags #ifndef MAGNUM_TARGET_GLES2 - , configuration.materialCount(), configuration.drawCount() + , 3, configuration.jointCount(), configuration.perVertexJointCount(), configuration.secondaryPerVertexJointCount(), configuration.materialCount(), configuration.drawCount(), out._perInstanceJointCountUniform, out._perVertexJointCountUniform #endif ); Containers::Optional geom; @@ -846,13 +1027,6 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(const Configuration frag.submitCompile(); if(geom) geom->submitCompile(); - MeshVisualizerGL3D out{NoInit}; - out._flags = baseFlags; - #ifndef MAGNUM_TARGET_GLES2 - out._materialCount = configuration.materialCount(); - out._drawCount = configuration.drawCount(); - #endif - out.attachShaders({vert, frag}); if(geom) out.attachShader(*geom); @@ -899,6 +1073,19 @@ MeshVisualizerGL3D::CompileState MeshVisualizerGL3D::compile(const Configuration out.bindAttributeLocation(VertexIndex::Location, "vertexIndex"); } #endif + #ifndef MAGNUM_TARGET_GLES2 + /* Configuration::setJointCount() checks that jointCount and + perVertexJointCount / secondaryPerVertexJointCount are either all + zero or non-zero so we don't need to check for jointCount() here */ + if(configuration.perVertexJointCount()) { + out.bindAttributeLocation(Weights::Location, "weights"); + out.bindAttributeLocation(JointIds::Location, "jointIds"); + } + if(configuration.secondaryPerVertexJointCount()) { + out.bindAttributeLocation(SecondaryWeights::Location, "secondaryWeights"); + out.bindAttributeLocation(SecondaryJointIds::Location, "secondaryJointIds"); + } + #endif } #endif @@ -950,6 +1137,8 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D _viewportSizeUniform = uniformLocation("viewportSize"); #ifndef MAGNUM_TARGET_GLES2 + if(flags() >= Flag::DynamicPerVertexJointCount) + _perVertexJointCountUniform = uniformLocation("perVertexJointCount"); if(flags() >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else @@ -994,6 +1183,12 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D _lineLengthUniform = uniformLocation("lineLength"); } #endif + #ifndef MAGNUM_TARGET_GLES2 + if(_jointCount) { + _jointMatricesUniform = uniformLocation("jointMatrices"); + _perInstanceJointCountUniform = uniformLocation("perInstanceJointCount"); + } + #endif } } @@ -1015,6 +1210,8 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); if(flags() & Flag::TextureTransformation) setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + if(_jointCount) + setUniformBlockBinding(uniformBlockIndex("Joint"), JointBufferBinding); } #endif } @@ -1023,6 +1220,10 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 + if(flags() >= Flag::DynamicPerVertexJointCount) + setPerVertexJointCount(_perVertexJointCount, _secondaryPerVertexJointCount); + #endif + #ifndef MAGNUM_TARGET_GLES2 if(flags() >= Flag::UniformBuffers) { /* Viewport size is zero by default */ /* Draw offset is zero by default */ @@ -1060,6 +1261,12 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(CompileState&& state): MeshVisualizerGL3D setLineLength(1.0f); } #endif + #ifndef MAGNUM_TARGET_GLES2 + if(_jointCount) { + setJointMatrices(Containers::Array{DirectInit, _jointCount, Math::IdentityInit}); + /* Per-instance joint count is zero by default */ + } + #endif } #endif } @@ -1161,6 +1368,30 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setSmoothness(const Float smoothness) { return *this; } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL3D& MeshVisualizerGL3D::setJointMatrices(const Containers::ArrayView matrices) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setJointMatrices(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_jointCount == matrices.size(), + "Shaders::MeshVisualizerGL3D::setJointMatrices(): expected" << _jointCount << "items but got" << matrices.size(), *this); + if(_jointCount) setUniform(_jointMatricesUniform, matrices); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::setJointMatrices(const std::initializer_list matrices) { + return setJointMatrices(Containers::arrayView(matrices)); +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::setJointMatrix(const UnsignedInt id, const Matrix4& matrix) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setJointMatrix(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(id < _jointCount, + "Shaders::MeshVisualizerGL3D::setJointMatrix(): joint ID" << id << "is out of bounds for" << _jointCount << "joints", *this); + setUniform(_jointMatricesUniform + id, matrix); + return *this; +} +#endif + #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGL3D& MeshVisualizerGL3D::bindProjectionBuffer(GL::Buffer& buffer) { CORRADE_ASSERT(flags() >= Flag::UniformBuffers, @@ -1205,6 +1436,21 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::bindDrawBuffer(GL::Buffer& buffer, const } #endif +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL3D::Configuration& MeshVisualizerGL3D::Configuration::setJointCount(UnsignedInt count, UnsignedInt perVertexCount, UnsignedInt secondaryPerVertexCount) { + CORRADE_ASSERT(perVertexCount <= 4, + "Shaders::MeshVisualizerGL3D::Configuration::setJointCount(): expected at most 4 per-vertex joints, got" << perVertexCount, *this); + CORRADE_ASSERT(secondaryPerVertexCount <= 4, + "Shaders::MeshVisualizerGL3D::Configuration::setJointCount(): expected at most 4 secondary per-vertex joints, got" << secondaryPerVertexCount, *this); + CORRADE_ASSERT(!count == (!perVertexCount && !secondaryPerVertexCount), + "Shaders::MeshVisualizerGL3D::Configuration::setJointCount(): either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero", *this); + _jointCount = count; + _perVertexJointCount = perVertexCount; + _secondaryPerVertexJointCount = secondaryPerVertexCount; + return *this; +} +#endif + Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flag value) { #ifndef MAGNUM_TARGET_GLES2 /* Special case coming from the Flags printer. As both flags are a superset @@ -1240,6 +1486,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flag value) { _c(UniformBuffers) _c(MultiDraw) _c(TextureArrays) + _c(DynamicPerVertexJointCount) #endif #undef _c /* LCOV_EXCL_STOP */ @@ -1289,6 +1536,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flag value) { _c(UniformBuffers) _c(MultiDraw) _c(TextureArrays) + _c(DynamicPerVertexJointCount) #endif #undef _c /* LCOV_EXCL_STOP */ @@ -1325,6 +1573,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flags value) { MeshVisualizerGL2D::Flag::MultiDraw, /* Superset of UniformBuffers */ MeshVisualizerGL2D::Flag::UniformBuffers, MeshVisualizerGL2D::Flag::TextureArrays, + MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, #endif #endif }); @@ -1364,6 +1613,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flags value) { MeshVisualizerGL3D::Flag::MultiDraw, /* Superset of UniformBuffers */ MeshVisualizerGL3D::Flag::UniformBuffers, MeshVisualizerGL3D::Flag::TextureArrays, + MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, #endif #endif }); diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index 0c3a5f01e..665b10356 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -65,6 +65,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr UniformBuffers = 1 << 10, MultiDraw = UniformBuffers|(1 << 11), TextureArrays = 1 << 17, + DynamicPerVertexJointCount = 1 << 18 #endif }; typedef Containers::EnumSet FlagsBase; @@ -78,11 +79,12 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr static MAGNUM_SHADERS_LOCAL void assertExtensions(const FlagsBase flags); static MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs, const FlagsBase flags #ifndef MAGNUM_TARGET_GLES2 - , UnsignedInt materialCount, UnsignedInt drawCount + , UnsignedInt dimensions, UnsignedInt jointCount, UnsignedInt perVertexJointCount, UnsignedInt secondaryPerVertexJointCount, UnsignedInt materialCount, UnsignedInt drawCount, UnsignedInt perInstanceJointCountUniform, UnsignedInt perVertexJointCountUniform #endif ); #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGLBase& setPerVertexJointCount(UnsignedInt count, UnsignedInt secondaryCount); MeshVisualizerGLBase& setTextureMatrix(const Matrix3& matrix); MeshVisualizerGLBase& setTextureLayer(UnsignedInt layer); MeshVisualizerGLBase& setObjectId(UnsignedInt id); @@ -92,22 +94,29 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr MeshVisualizerGLBase& setWireframeWidth(Float width); #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGLBase& setColorMapTransformation(Float offset, Float scale); + MeshVisualizerGLBase& setPerInstanceJointCount(UnsignedInt count); + MeshVisualizerGLBase& setDrawOffset(UnsignedInt offset); MeshVisualizerGLBase& bindColorMapTexture(GL::Texture2D& texture); MeshVisualizerGLBase& bindObjectIdTexture(GL::Texture2D& texture); MeshVisualizerGLBase& bindObjectIdTexture(GL::Texture2DArray& texture); #endif #ifndef MAGNUM_TARGET_GLES2 - MeshVisualizerGLBase& setDrawOffset(UnsignedInt offset); MeshVisualizerGLBase& bindTextureTransformationBuffer(GL::Buffer& buffer); MeshVisualizerGLBase& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); MeshVisualizerGLBase& bindMaterialBuffer(GL::Buffer& buffer); MeshVisualizerGLBase& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + MeshVisualizerGLBase& bindJointBuffer(GL::Buffer& buffer); + MeshVisualizerGLBase& bindJointBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); #endif FlagsBase _flags; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt _materialCount{}, _drawCount{}; + UnsignedInt _jointCount{}, + _perVertexJointCount{}, + _secondaryPerVertexJointCount{}, + _materialCount{}, + _drawCount{}; #endif Int _viewportSizeUniform{0}, _colorUniform{1}, @@ -118,10 +127,15 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr Int _colorMapOffsetScaleUniform{5}, _objectIdUniform{6}, _textureMatrixUniform{7}, - _textureLayerUniform{8}; - /* Used instead of all other uniforms except viewportSize when - Flag::UniformBuffers is set, so it can alias them */ - Int _drawOffsetUniform{1}; + _textureLayerUniform{8}, + /* 9 to 13 different between MeshVisualizerGL2D and + MeshVisualizerGL3D */ + _jointMatricesUniform{14}, + _perInstanceJointCountUniform, /* 14 + jointCount */ + /* Used instead of all other uniforms except viewportSize when + Flag::UniformBuffers is set, so it can alias them */ + _drawOffsetUniform{1}, + _perVertexJointCountUniform; /* 15 + jointCount, or 2 with UBOs */ #endif }; @@ -189,7 +203,8 @@ a trimmed-down @ref MeshVisualizerDrawUniform2D is used instead of */ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisualizerGLBase { public: - class Configuration; + /* MSVC needs dllexport here as well */ + class MAGNUM_SHADERS_EXPORT Configuration; class CompileState; /** @@ -205,6 +220,64 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua typedef GenericGL2D::TextureCoordinates TextureCoordinates; #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint ids + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4ui. + * Used only if @ref perVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::JointIds JointIds; + + /** + * @brief Weights + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4. + * Used only if @ref perVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::Weights Weights; + + /** + * @brief Secondary joint ids + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4ui. + * Used only if @ref secondaryPerVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::SecondaryJointIds SecondaryJointIds; + + /** + * @brief Secondary weights + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4. + * Used only if @ref secondaryPerVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::SecondaryWeights SecondaryWeights; + #endif + /** * @brief Vertex index * @@ -443,6 +516,29 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua /** @copydoc MeshVisualizerGL3D::Flag::TextureArrays */ TextureArrays = 1 << 17, + + /** + * Dynamic per-vertex joint count for skinning. Uses only the first + * M / N primary / secondary components defined by + * @ref setPerVertexJointCount() instead of + * all primary / secondary components defined by + * @ref Configuration::setJointCount() at shader compilation time. + * Useful in order to avoid having a shader permutation defined for + * every possible joint count. Unfortunately it's not possible to + * make use of default values for unspecified input components as + * the last component is always @cpp 1.0 @ce instead of + * @cpp 0.0 @ce, on the other hand dynamically limiting the joint + * count can reduce the time spent executing the vertex shader + * compared to going through the full set of per-vertex joints + * always. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, + * which is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + * @m_since_latest + */ + DynamicPerVertexJointCount = 1 << 18, #endif }; @@ -555,6 +651,47 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua return Flag(UnsignedInt(Implementation::MeshVisualizerGLBase::_flags)); } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint count + * @m_since_latest + * + * If @ref Flag::UniformBuffers is not set, this is the number of joint + * matrices accepted by @ref setJointMatrices() / @ref setJointMatrix(). + * If @ref Flag::UniformBuffers is set, this is the statically defined + * size of the @ref TransformationUniform2D uniform buffer bound with + * @ref bindJointBuffer(). + * @see @ref Configuration::setJointCount() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt jointCount() const { return _jointCount; } + + /** + * @brief Per-vertex joint count + * @m_since_latest + * + * Returns the value set with @ref Configuration::setJointCount(). If + * @ref Flag::DynamicPerVertexJointCount is set, the count can be + * additionally modified per-draw using @ref setPerVertexJointCount(). + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt perVertexJointCount() const { return _perVertexJointCount; } + + /** + * @brief Secondary per-vertex joint count + * @m_since_latest + * + * Returns the value set with @ref Configuration::setJointCount(). If + * @ref Flag::DynamicPerVertexJointCount is set, the count can be + * additionally modified per-draw using @ref setPerVertexJointCount(). + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt secondaryPerVertexJointCount() const { return _secondaryPerVertexJointCount; } + #endif + #ifndef MAGNUM_TARGET_GLES2 /** * @brief Material count @@ -585,6 +722,37 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua UnsignedInt drawCount() const { return _drawCount; } #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set dynamic per-vertex skinning joint count + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Allows reducing the count of iterated joints for a particular draw + * call, making it possible to use a single shader with meshes that + * contain different count of per-vertex joints. See + * @ref Flag::DynamicPerVertexJointCount for more information. As the + * joint count is tied to the mesh layout, this is a per-draw-call + * setting even in case of @ref Flag::UniformBuffers instead of being + * a value in @ref MeshVisualizerDrawUniform2D. Initial value is same + * as @ref perVertexJointCount() and + * @ref secondaryPerVertexJointCount(). + * + * Expects that @ref Flag::DynamicPerVertexJointCount is set, + * @p count is not larger than @ref perVertexJointCount() and + * @p secondaryCount not larger than @ref secondaryPerVertexJointCount(). + * @see @ref Configuration::setJointCount() + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, which + * is not available in WebGL 1.0. + */ + MeshVisualizerGL2D& setPerVertexJointCount(UnsignedInt count, UnsignedInt secondaryCount = 0) { + return static_cast(Implementation::MeshVisualizerGLBase::setPerVertexJointCount(count, secondaryCount)); + } + #endif + /** @{ * @name Uniform setters * @@ -722,6 +890,78 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua */ MeshVisualizerGL2D& setSmoothness(Float smoothness); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set joint matrices + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Initial values are identity transformations. Expects that the size + * of the @p matrices array is the same as @ref jointCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform2D::transformationMatrix and call + * @ref bindJointBuffer() instead. + * @see @ref setJointMatrix(UnsignedInt, const Matrix3&) + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + MeshVisualizerGL2D& setJointMatrices(const Containers::ArrayView matrices); + + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL2D& setJointMatrices(std::initializer_list matrices); + + /** + * @brief Set joint matrix for given joint + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Unlike @ref setJointMatrices() updates just a single joint matrix. + * Expects that @p id is less than @ref jointCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform2D::transformationMatrix and call + * @ref bindJointBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + MeshVisualizerGL2D& setJointMatrix(UnsignedInt id, const Matrix3& matrix); + + /** + * @brief Set per-instance joint count + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Offset added to joint IDs in the @ref JointIds and + * @ref SecondaryJointIds in instanced draws. Should be less than + * @ref jointCount(). Initial value is @cpp 0 @ce, meaning every + * instance will use the same joint matrices, setting it to a non-zero + * value causes the joint IDs to be interpreted as + * @glsl gl_InstanceID*count + jointId @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerDrawUniform2D::perInstanceJointCount and call + * @ref bindDrawBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + MeshVisualizerGL2D& setPerInstanceJointCount(UnsignedInt count) { + return static_cast(Implementation::MeshVisualizerGLBase::setPerInstanceJointCount(count)); + } + #endif + /** * @} */ @@ -834,6 +1074,29 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer, offset, size)); } + /** + * @brief Bind a joint matrix 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 jointCount() instances of + * @ref TransformationUniform2D. + * @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. + */ + MeshVisualizerGL2D& bindJointBuffer(GL::Buffer& buffer) { + return static_cast(Implementation::MeshVisualizerGLBase::bindJointBuffer(buffer)); + } + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL2D& bindJointBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size) { + return static_cast(Implementation::MeshVisualizerGLBase::bindJointBuffer(buffer, offset, size)); + } + /** * @} */ @@ -881,7 +1144,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua @see @ref MeshVisualizerGL2D(const Configuration&), @ref compile(const Configuration&) */ -class MeshVisualizerGL2D::Configuration { +class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D::Configuration { public: explicit Configuration() = default; @@ -903,6 +1166,67 @@ class MeshVisualizerGL2D::Configuration { } #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt jointCount() const { return _jointCount; } + + /** + * @brief Per-vertex joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt perVertexJointCount() const { return _perVertexJointCount; } + + /** + *@brief Secondary per-vertex joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt secondaryPerVertexJointCount() const { return _secondaryPerVertexJointCount; } + + /** + * @brief Set joint count + * + * If @ref Flag::UniformBuffers isn't set, @p count describes how many + * joint matrices get supplied to each draw by @ref setJointMatrices() + * / @ref setJointMatrix(). If @ref Flag::UniformBuffers is set, + * @p count describes size of a @ref TransformationUniform2D buffer + * bound with @ref bindJointBuffer(); as uniform buffers are required + * to have a statically defined size. The per-vertex joints then index + * into the array offset by + * @ref MeshVisualizerDrawUniform2D::jointOffset. If @p count is + * @cpp 0 @ce, skinning is not performed. + * + * The @p perVertexCount and @p secondaryPerVertexCount then describe + * how many components are taken from @ref JointIds / @ref Weights and + * @ref SecondaryJointIds / @ref SecondaryWeights attributes. Both + * values are expected to not be larger than @cpp 4 @ce, setting either + * of these to @cpp 0 @ce means given attribute is not used at all. If + * @p count is @cpp 0 @ce, both @p perVertexCount and + * @p secondaryPerVertexCount is expected to be @cpp 0 @ce as well; if + * @p count is non-zero at least one of @p perVertexCount and + * @p secondaryPerVertexCount is expected to be non-zero as well. + * + * Default value for all three is @cpp 0 @ce. + * @see @ref MeshVisualizerGL2D::jointCount(), + * @ref MeshVisualizerGL2D::perVertexJointCount(), + * @ref MeshVisualizerGL2D::secondaryPerVertexJointCount(), + * @ref Flag::DynamicPerVertexJointCount, + * @ref MeshVisualizerGL2D::setPerVertexJointCount() + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + Configuration& setJointCount(UnsignedInt count, UnsignedInt perVertexCount, UnsignedInt secondaryPerVertexCount = 0); + /** * @brief Material count * @@ -969,7 +1293,10 @@ class MeshVisualizerGL2D::Configuration { private: Flags _flags; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt _materialCount = 1, + UnsignedInt _jointCount = 0, + _perVertexJointCount = 0, + _secondaryPerVertexJointCount = 0, + _materialCount = 1, _drawCount = 1; #endif }; @@ -1171,6 +1498,28 @@ non-indexed @ref MeshPrimitive::Triangles. arrays are not available in WebGL 1.0. @requires_webgl20 `gl_VertexID` is not available in WebGL 1.0. +@section Shaders-MeshVisualizerGL3D-skinning Skinning + +To render skinned meshes, bind up to two sets of up to four-component joint ID +and weight attributes to @ref JointIds / @ref SecondaryJointIds and +@ref Weights / @ref SecondaryWeights, set an appropriate joint count and +per-vertex primary and secondary joint count in +@ref Configuration::setJointCount() and upload appropriate joint matrices with +@ref setJointMatrices(). Currently, the mesh visualizer supports only +transforming the mesh vertices for feature parity with other shaders, +no skinning-specific visualization feature is implemented. + +To avoid having to compile multiple shader variants for different per-vertex +joint counts, enable @ref Flag::DynamicPerVertexJointCount, set the maximum +per-vertex joint count in @ref Configuration::setJointCount() and then adjust +the actual per-draw joint count with @ref setPerVertexJointCount(). + +@requires_gl30 Extension @gl_extension{EXT,texture_integer} +@requires_gles30 Skinning requires integer support in shaders, which is not + available in OpenGL ES 2.0. +@requires_webgl20 Skinning requires integer support in shaders, which is not + available in WebGL 1.0. + @section Shaders-MeshVisualizerGL3D-instancing Instanced rendering Enabling @ref Flag::InstancedTransformation will turn the shader into an @@ -1190,6 +1539,12 @@ enabled, the @ref TextureOffset attribute (or @ref TextureOffsetLayer in case @ref Flag::TextureArrays is enabled as well) then can supply per-instance texture offset (or offset and layer). +For instanced skinning the joint buffer is assumed to contain joint +transformations for all instances. By default all instances use the same joint +transformations, seting @ref setPerInstanceJointCount() will cause the shader +to offset the per-vertex joint IDs with +@glsl gl_InstanceID*perInstanceJointCount @ce. + @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, @gl_extension{EXT,instanced_arrays} or @gl_extension{NV,instanced_arrays} @@ -1223,6 +1578,18 @@ and draw count via @ref Configuration::setMaterialCount() and every draw. The usage is similar for all shaders, see @ref shaders-usage-multidraw for an example. +For skinning, joint matrices are supplied via a @ref TransformationUniform3D +buffer bound with @ref bindJointBuffer(). In an instanced scenario the +per-instance joint count is supplied via +@ref MeshVisualizerDrawUniform3D::perInstanceJointCount, a per-draw joint +offset for the multidraw scenario is supplied via +@ref MeshVisualizerDrawUniform3D::jointOffset. Altogether for a particular +draw, each per-vertex joint ID is offset with +@glsl gl_InstanceID*perInstanceJointCount + jointOffset @ce. The +@ref setPerVertexJointCount() stays as an immediate uniform in the UBO and +multidraw scenario as well, as it is tied to a particular mesh layout and thus +doesn't need to vary per draw. + @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform buffers. @requires_gl46 Extension @gl_extension{ARB,shader_draw_parameters} for @@ -1239,7 +1606,8 @@ every draw. The usage is similar for all shaders, see */ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisualizerGLBase { public: - class Configuration; + /* MSVC needs dllexport here as well */ + class MAGNUM_SHADERS_EXPORT Configuration; class CompileState; /** @@ -1308,6 +1676,64 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua typedef GenericGL3D::TextureCoordinates TextureCoordinates; #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint ids + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4ui. + * Used only if @ref perVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::JointIds JointIds; + + /** + * @brief Weights + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4. + * Used only if @ref perVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::Weights Weights; + + /** + * @brief Secondary joint ids + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4ui. + * Used only if @ref secondaryPerVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::SecondaryJointIds SecondaryJointIds; + + /** + * @brief Secondary weights + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4. + * Used only if @ref secondaryPerVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::SecondaryWeights SecondaryWeights; + #endif + /** * @brief Vertex index * @@ -1749,7 +2175,30 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @m_since_latest * @todoc rewrite the ext requirements once we have more textures */ - TextureArrays = 1 << 17 + TextureArrays = 1 << 17, + + /** + * Dynamic per-vertex joint count for skinning. Uses only the first + * M / N primary / secondary components defined by + * @ref setPerVertexJointCount() instead of + * all primary / secondary components defined by + * @ref Configuration::setJointCount() at shader compilation time. + * Useful in order to avoid having a shader permutation defined for + * every possible joint count. Unfortunately it's not possible to + * make use of default values for unspecified input components as + * the last component is always @cpp 1.0 @ce instead of + * @cpp 0.0 @ce, on the other hand dynamically limiting the joint + * count can reduce the time spent executing the vertex shader + * compared to going through the full set of per-vertex joints + * always. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, + * which is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + * @m_since_latest + */ + DynamicPerVertexJointCount = 1 << 18, #endif }; @@ -1870,6 +2319,47 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua return Flag(UnsignedInt(Implementation::MeshVisualizerGLBase::_flags)); } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint count + * @m_since_latest + * + * If @ref Flag::UniformBuffers is not set, this is the number of joint + * matrices accepted by @ref setJointMatrices() / @ref setJointMatrix(). + * If @ref Flag::UniformBuffers is set, this is the statically defined + * size of the @ref TransformationUniform3D uniform buffer bound with + * @ref bindJointBuffer(). + * @see @ref Configuration::setJointCount() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt jointCount() const { return _jointCount; } + + /** + * @brief Per-vertex joint count + * @m_since_latest + * + * Returns the value set with @ref Configuration::setJointCount(). If + * @ref Flag::DynamicPerVertexJointCount is set, the count can be + * additionally modified per-draw using @ref setPerVertexJointCount(). + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt perVertexJointCount() const { return _perVertexJointCount; } + + /** + * @brief Secondary per-vertex joint count + * @m_since_latest + * + * Returns the value set with @ref Configuration::setJointCount(). If + * @ref Flag::DynamicPerVertexJointCount is set, the count can be + * additionally modified per-draw using @ref setPerVertexJointCount(). + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt secondaryPerVertexJointCount() const { return _secondaryPerVertexJointCount; } + #endif + #ifndef MAGNUM_TARGET_GLES2 /** * @brief Material count @@ -1900,6 +2390,37 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua UnsignedInt drawCount() const { return _drawCount; } #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set dynamic per-vertex skinning joint count + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Allows reducing the count of iterated joints for a particular draw + * call, making it possible to use a single shader with meshes that + * contain different count of per-vertex joints. See + * @ref Flag::DynamicPerVertexJointCount for more information. As the + * joint count is tied to the mesh layout, this is a per-draw-call + * setting even in case of @ref Flag::UniformBuffers instead of being + * a value in @ref MeshVisualizerDrawUniform3D. Initial value is same + * as @ref perVertexJointCount() and + * @ref secondaryPerVertexJointCount(). + * + * Expects that @ref Flag::DynamicPerVertexJointCount is set, + * @p count is not larger than @ref perVertexJointCount() and + * @p secondaryCount not larger than @ref secondaryPerVertexJointCount(). + * @see @ref Configuration::setJointCount() + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, which + * is not available in WebGL 1.0. + */ + MeshVisualizerGL3D& setPerVertexJointCount(UnsignedInt count, UnsignedInt secondaryCount = 0) { + return static_cast(Implementation::MeshVisualizerGLBase::setPerVertexJointCount(count, secondaryCount)); + } + #endif + /** @{ * @name Uniform setters * @@ -2216,6 +2737,78 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua */ MeshVisualizerGL3D& setSmoothness(Float smoothness); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set joint matrices + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Initial values are identity transformations. Expects that the size + * of the @p matrices array is the same as @ref jointCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindJointBuffer() instead. + * @see @ref setJointMatrix(UnsignedInt, const Matrix4&) + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + MeshVisualizerGL3D& setJointMatrices(const Containers::ArrayView matrices); + + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& setJointMatrices(std::initializer_list matrices); + + /** + * @brief Set joint matrix for given joint + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Unlike @ref setJointMatrices() updates just a single joint matrix. + * Expects that @p id is less than @ref jointCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindJointBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + MeshVisualizerGL3D& setJointMatrix(UnsignedInt id, const Matrix4& matrix); + + /** + * @brief Set per-instance joint count + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Offset added to joint IDs in the @ref JointIds and + * @ref SecondaryJointIds in instanced draws. Should be less than + * @ref jointCount(). Initial value is @cpp 0 @ce, meaning every + * instance will use the same joint matrices, setting it to a non-zero + * value causes the joint IDs to be interpreted as + * @glsl gl_InstanceID*count + jointId @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerDrawUniform3D::perInstanceJointCount and call + * @ref bindDrawBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + MeshVisualizerGL3D& setPerInstanceJointCount(UnsignedInt count) { + return static_cast(Implementation::MeshVisualizerGLBase::setPerInstanceJointCount(count)); + } + #endif + /** * @} */ @@ -2367,6 +2960,29 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer, offset, size)); } + /** + * @brief Bind a joint matrix 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 jointCount() instances of + * @ref TransformationUniform3D. + * @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. + */ + MeshVisualizerGL3D& bindJointBuffer(GL::Buffer& buffer) { + return static_cast(Implementation::MeshVisualizerGLBase::bindJointBuffer(buffer)); + } + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindJointBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size) { + return static_cast(Implementation::MeshVisualizerGLBase::bindJointBuffer(buffer, offset, size)); + } + /** * @} */ @@ -2497,6 +3113,66 @@ class MeshVisualizerGL3D::Configuration { } #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt jointCount() const { return _jointCount; } + + /** + * @brief Per-vertex joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt perVertexJointCount() const { return _perVertexJointCount; } + + /** + *@brief Secondary per-vertex joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt secondaryPerVertexJointCount() const { return _secondaryPerVertexJointCount; } + + /** + * @brief Set joint count + * + * If @ref Flag::UniformBuffers isn't set, @p count describes how many + * joint matrices get supplied to each draw by @ref setJointMatrices() + * / @ref setJointMatrix(). If @ref Flag::UniformBuffers is set, + * @p count describes size of a @ref TransformationUniform3D buffer + * bound with @ref bindJointBuffer(); as uniform buffers are required + * to have a statically defined size. The per-vertex joints then index + * into the array offset by @ref MeshVisualizerDrawUniform3D::jointOffset. + * If @p count is @cpp 0 @ce, skinning is not performed. + * + * The @p perVertexCount and @p secondaryPerVertexCount then describe + * how many components are taken from @ref JointIds / @ref Weights and + * @ref SecondaryJointIds / @ref SecondaryWeights attributes. Both + * values are expected to not be larger than @cpp 4 @ce, setting either + * of these to @cpp 0 @ce means given attribute is not used at all. If + * @p count is @cpp 0 @ce, both @p perVertexCount and + * @p secondaryPerVertexCount is expected to be @cpp 0 @ce as well; if + * @p count is non-zero at least one of @p perVertexCount and + * @p secondaryPerVertexCount is expected to be non-zero as well. + * + * Default value for all three is @cpp 0 @ce. + * @see @ref MeshVisualizerGL2D::jointCount(), + * @ref MeshVisualizerGL2D::perVertexJointCount(), + * @ref MeshVisualizerGL2D::secondaryPerVertexJointCount(), + * @ref Flag::DynamicPerVertexJointCount, + * @ref MeshVisualizerGL2D::setPerVertexJointCount() + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + Configuration& setJointCount(UnsignedInt count, UnsignedInt perVertexCount, UnsignedInt secondaryPerVertexCount = 0); + /** * @brief Material count * @@ -2562,7 +3238,10 @@ class MeshVisualizerGL3D::Configuration { private: Flags _flags; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt _materialCount = 1, + UnsignedInt _jointCount = 0, + _perVertexJointCount = 0, + _secondaryPerVertexJointCount = 0, + _materialCount = 1, _drawCount = 1; #endif }; diff --git a/src/Magnum/Shaders/Phong.frag b/src/Magnum/Shaders/Phong.frag index 58ab39df2..37af369ef 100644 --- a/src/Magnum/Shaders/Phong.frag +++ b/src/Magnum/Shaders/Phong.frag @@ -183,10 +183,11 @@ uniform highp uint drawOffset struct DrawUniform { /* Can't be a mat3 because of ANGLE, see Phong.vert for details */ mediump mat3x4 normalMatrix; - highp uvec4 materialIdReservedObjectIdLightOffsetLightCountReserved; - #define draw_materialIdReserved materialIdReservedObjectIdLightOffsetLightCountReserved.x - #define draw_objectId materialIdReservedObjectIdLightOffsetLightCountReserved.y - #define draw_lightOffsetLightCount materialIdReservedObjectIdLightOffsetLightCountReserved.z + highp uvec4 materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount; + #define draw_materialIdReserved materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount.x + #define draw_objectId materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount.y + #define draw_lightOffsetLightCount materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount.z + #define draw_jointOffsetPerInstanceJointCount materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount.w }; layout(std140 diff --git a/src/Magnum/Shaders/Phong.h b/src/Magnum/Shaders/Phong.h index 01afdad48..286c1c574 100644 --- a/src/Magnum/Shaders/Phong.h +++ b/src/Magnum/Shaders/Phong.h @@ -57,9 +57,9 @@ struct PhongDrawUniform { /** @brief Construct with default parameters */ constexpr explicit PhongDrawUniform(DefaultInitT = DefaultInit) noexcept: normalMatrix{Math::IdentityInit}, materialId{0}, objectId{0}, #ifndef CORRADE_TARGET_BIG_ENDIAN - lightOffset{0}, lightCount{0xffffu} + lightOffset{0}, lightCount{0xffffu}, jointOffset{0}, perInstanceJointCount{0} #else - lightCount{0xffffu}, lightOffset{0} + lightCount{0xffffu}, lightOffset{0}, perInstanceJointCount{0}, jointOffset{0} #endif {} @@ -115,6 +115,24 @@ struct PhongDrawUniform { return *this; } + /** + * @brief Set the @ref jointOffset field + * @return Reference to self (for method chaining) + */ + PhongDrawUniform& setJointOffset(UnsignedInt offset) { + jointOffset = offset; + return *this; + } + + /** + * @brief Set the @ref perInstanceJointCount field + * @return Reference to self (for method chaining) + */ + PhongDrawUniform& setPerInstanceJointCount(UnsignedInt count) { + perInstanceJointCount = count; + return *this; + } + /** * @} */ @@ -151,10 +169,10 @@ struct PhongDrawUniform { /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT - UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; /* reserved */ #endif #else - UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; /* reserved */ UnsignedShort materialId; #endif @@ -207,10 +225,37 @@ struct PhongDrawUniform { UnsignedShort lightOffset; #endif - /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK - I MADE THOSE UNNAMED, YOU DUMB FOOL */ - #ifndef DOXYGEN_GENERATING_OUTPUT - Int:32; + /** @var jointOffset + * @brief Joint offset + * + * Offset added to joint IDs in the @ref PhongGL::JointIds and + * @ref PhongGL::SecondaryJointIds attributes. Useful when a UBO with joint + * matrices for more than one skin is supplied or in a multi-draw scenario. + * Should be less than the joint count passed to + * @ref PhongGL::Configuration::setJointCount(). Default value is + * @cpp 0 @ce, meaning no offset is added to joint IDs. + */ + + /** @var perInstanceJointCount + * @brief Per-instance joint count + * + * Offset added to joint IDs in the @ref PhongGL::JointIds and + * @ref PhongGL::SecondaryJointIds atttributes in instanced draws. Should + * be less than the joint count passed to + * @ref PhongGL::Configuration::setJointCount(). Default value is + * @cpp 0 @ce, meaning every instance will use the same joint matrices, + * setting it to a non-zero value causes the joint IDs to be interpreted as + * @glsl gl_InstanceID*count + jointId @ce. + */ + + /* This field is an UnsignedInt in the shader and jointOffset is extracted + as (value & 0xffff), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort jointOffset; + UnsignedShort perInstanceJointCount; + #else + UnsignedShort perInstanceJointCount; + UnsignedShort jointOffset; #endif }; diff --git a/src/Magnum/Shaders/Phong.vert b/src/Magnum/Shaders/Phong.vert index e8d23e2a4..6755236ee 100644 --- a/src/Magnum/Shaders/Phong.vert +++ b/src/Magnum/Shaders/Phong.vert @@ -48,6 +48,19 @@ #define const #endif +/* Both classic uniforms and uniform buffers */ + +#ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = PER_VERTEX_JOINT_COUNT_LOCATION) +#endif +uniform mediump uvec2 perVertexJointCount + #ifndef GL_ES + = uvec2(PER_VERTEX_JOINT_COUNT, SECONDARY_PER_VERTEX_JOINT_COUNT) + #endif + ; +#endif + /* Uniforms */ #ifndef UNIFORM_BUFFERS @@ -99,6 +112,22 @@ layout(location = 4) uniform highp uint textureLayer; /* defaults to zero */ #endif +#ifdef JOINT_COUNT +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = JOINT_MATRICES_LOCATION) +#endif +uniform mat4 jointMatrices[JOINT_COUNT] + #ifndef GL_ES + = mat4[](JOINT_MATRIX_INITIALIZER) + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = PER_INSTANCE_JOINT_COUNT_LOCATION) +#endif +uniform uint perInstanceJointCount; /* defaults to zero */ +#endif + /* Uniform buffers */ #else @@ -132,10 +161,11 @@ struct DrawUniform { 2. Forget to actually implement and test the damn thing. */ mediump mat3x4 normalMatrix; - highp uvec4 materialIdReservedObjectIdLightOffsetLightCountReserved; - #define draw_materialIdReserved materialIdReservedObjectIdLightOffsetLightCountReserved.x - #define draw_objectId materialIdReservedObjectIdLightOffsetLightCountReserved.y - #define draw_lightOffsetLightCount materialIdReservedObjectIdLightOffsetLightCountReserved.z + highp uvec4 materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount; + #define draw_materialIdReserved materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount.x + #define draw_objectId materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount.y + #define draw_lightOffsetLightCount materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount.z + #define draw_jointOffsetPerInstanceJointCount materialIdReservedObjectIdLightOffsetLightCountJointOffsetPerInstanceJointCount.w }; layout(std140 @@ -162,6 +192,16 @@ layout(std140 highp mat4 transformationMatrices[DRAW_COUNT]; }; +#ifdef JOINT_COUNT +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 6 + #endif +) uniform Joint { + highp mat4 jointMatrices[JOINT_COUNT]; +}; +#endif + #ifdef TEXTURE_TRANSFORMATION struct TextureTransformationUniform { highp vec4 rotationScaling; @@ -228,6 +268,32 @@ layout(location = COLOR_ATTRIBUTE_LOCATION) in lowp vec4 vertexColor; #endif +#ifdef JOINT_COUNT +#if PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = WEIGHTS_ATTRIBUTE_LOCATION) +#endif +in mediump vec4 weights; + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = JOINTIDS_ATTRIBUTE_LOCATION) +#endif +in mediump uvec4 jointIds; +#endif + +#if SECONDARY_PER_VERTEX_JOINT_COUNT +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = SECONDARY_WEIGHTS_ATTRIBUTE_LOCATION) +#endif +in mediump vec4 secondaryWeights; + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = SECONDARY_JOINTIDS_ATTRIBUTE_LOCATION) +#endif +in mediump uvec4 secondaryJointIds; +#endif +#endif + #ifdef INSTANCED_OBJECT_ID #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = OBJECT_ID_ATTRIBUTE_LOCATION) @@ -323,6 +389,33 @@ void main() { highp const uint textureLayer = floatBitsToUint(textureTransformations[drawId].textureTransformation_layer); #endif #endif + #ifdef JOINT_COUNT + mediump const uint jointOffset = (draws[drawId].draw_jointOffsetPerInstanceJointCount & 0xffffu) + uint(gl_InstanceID)*(draws[drawId].draw_jointOffsetPerInstanceJointCount >> 16 & 0xffffu); + #endif + #else + #ifdef JOINT_COUNT + mediump const uint jointOffset = uint(gl_InstanceID)*perInstanceJointCount; + #endif + #endif + + #ifdef JOINT_COUNT + mat4 skinMatrix = mat4(0.0); + #if PER_VERTEX_JOINT_COUNT + for(uint i = 0u; i != PER_VERTEX_JOINT_COUNT + #ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT + && i != perVertexJointCount.x + #endif + ; ++i) + skinMatrix += weights[i]*jointMatrices[jointOffset + jointIds[i]]; + #endif + #if SECONDARY_PER_VERTEX_JOINT_COUNT + for(uint i = 0u; i != SECONDARY_PER_VERTEX_JOINT_COUNT + #ifdef DYNAMIC_PER_VERTEX_JOINT_COUNT + && i != perVertexJointCount.y + #endif + ; ++i) + skinMatrix += secondaryWeights[i]*jointMatrices[jointOffset + secondaryJointIds[i]]; + #endif #endif /* Transformed vertex position */ @@ -330,6 +423,9 @@ void main() { #ifdef INSTANCED_TRANSFORMATION instancedTransformationMatrix* #endif + #ifdef JOINT_COUNT + skinMatrix* + #endif position; #ifndef HAS_LIGHTS highp vec3 diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index 103eaa7e9..882dc792a 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -53,6 +53,8 @@ namespace Magnum { namespace Shaders { +using namespace Containers::Literals; + namespace { enum: Int { AmbientTextureUnit = 0, @@ -65,12 +67,16 @@ namespace { #ifndef MAGNUM_TARGET_GLES2 enum: Int { + /* Projection, transformation, texture transformation and joints is + slots 0, 1, 3, 6 in all shaders so shaders can be switched without + rebinding everything */ ProjectionBufferBinding = 0, TransformationBufferBinding = 1, DrawBufferBinding = 2, TextureTransformationBufferBinding = 3, MaterialBufferBinding = 4, - LightBufferBinding = 5 + LightBufferBinding = 5, + JointBufferBinding = 6, }; #endif } @@ -112,6 +118,13 @@ PhongGL::CompileState PhongGL::compile(const Configuration& configuration) { CORRADE_ASSERT(!(configuration.flags() & Flag::SpecularTexture) || !(configuration.flags() & (Flag::NoSpecular)), "Shaders::PhongGL: specular texture requires the shader to not have specular disabled", CompileState{NoCreate}); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(configuration.flags() & Flag::DynamicPerVertexJointCount) || configuration.jointCount(), + "Shaders::PhongGL: dynamic per-vertex joint count enabled for zero joints", CompileState{NoCreate}); + CORRADE_ASSERT(!(configuration.flags() & Flag::InstancedTransformation) || !configuration.secondaryPerVertexJointCount(), + "Shaders::PhongGL: TransformationMatrix attribute binding conflicts with the SecondaryJointIds / SecondaryWeights attributes, use a non-instanced rendering with secondary weights instead", CompileState{NoCreate}); + #endif + #ifndef MAGNUM_TARGET_GLES if(configuration.flags() >= Flag::UniformBuffers) MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); @@ -155,13 +168,22 @@ PhongGL::CompileState PhongGL::compile(const Configuration& configuration) { PhongGL out{NoInit}; out._flags = configuration.flags(); out._lightCount = configuration.lightCount(); - out._lightColorsUniform = out._lightPositionsUniform + Int(configuration.lightCount()); - out._lightSpecularColorsUniform = out._lightPositionsUniform + 2*Int(configuration.lightCount()); - out._lightRangesUniform = out._lightPositionsUniform + 3*Int(configuration.lightCount()); #ifndef MAGNUM_TARGET_GLES2 + out._jointCount = configuration.jointCount(); + out._perVertexJointCount = configuration.perVertexJointCount(); + out._secondaryPerVertexJointCount = configuration.secondaryPerVertexJointCount(); out._materialCount = configuration.materialCount(); out._drawCount = configuration.drawCount(); #endif + out._lightColorsUniform = out._lightPositionsUniform + Int(configuration.lightCount()); + out._lightSpecularColorsUniform = out._lightColorsUniform + Int(configuration.lightCount()); + out._lightRangesUniform = out._lightSpecularColorsUniform + Int(configuration.lightCount()); + #ifndef MAGNUM_TARGET_GLES2 + out._jointMatricesUniform = out._lightRangesUniform + Int(configuration.lightCount()); + out._perInstanceJointCountUniform = out._jointMatricesUniform + configuration.jointCount(); + out._perVertexJointCountUniform = configuration.flags() >= Flag::UniformBuffers ? + 1 : out._perInstanceJointCountUniform + 1; + #endif GL::Shader vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); @@ -234,6 +256,33 @@ PhongGL::CompileState PhongGL::compile(const Configuration& configuration) { .addSource(configuration.flags() & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") .addSource(configuration.flags() >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : ""); #ifndef MAGNUM_TARGET_GLES2 + if(configuration.jointCount()) { + vert.addSource(Utility::formatString( + "#define JOINT_COUNT {}\n" + "#define PER_VERTEX_JOINT_COUNT {}u\n" + "#define SECONDARY_PER_VERTEX_JOINT_COUNT {}u\n" + "#define JOINT_MATRICES_LOCATION {}\n" + #ifndef MAGNUM_TARGET_GLES + "#define JOINT_MATRIX_INITIALIZER {}\n" + #endif + "#define PER_INSTANCE_JOINT_COUNT_LOCATION {}\n", + configuration.jointCount(), + configuration.perVertexJointCount(), + configuration.secondaryPerVertexJointCount(), + out._jointMatricesUniform, + #ifndef MAGNUM_TARGET_GLES + ("mat4(1.0), "_s*configuration.jointCount()).exceptSuffix(2), + #endif + out._perInstanceJointCountUniform)); + } + if(configuration.flags() >= Flag::DynamicPerVertexJointCount) { + vert.addSource(Utility::formatString( + "#define DYNAMIC_PER_VERTEX_JOINT_COUNT\n" + "#define PER_VERTEX_JOINT_COUNT_LOCATION {}\n", + out._perVertexJointCountUniform)); + } + #endif + #ifndef MAGNUM_TARGET_GLES2 if(configuration.flags() >= Flag::UniformBuffers) { vert.addSource(Utility::formatString( "#define UNIFORM_BUFFERS\n" @@ -337,6 +386,19 @@ PhongGL::CompileState PhongGL::compile(const Configuration& configuration) { } if(configuration.flags() >= Flag::InstancedTextureOffset) out.bindAttributeLocation(TextureOffset::Location, "instancedTextureOffset"); + #ifndef MAGNUM_TARGET_GLES2 + /* Configuration::setJointCount() checks that jointCount and + perVertexJointCount / secondaryPerVertexJointCount are either all + zero or non-zero so we don't need to check for jointCount() here */ + if(configuration.perVertexJointCount()) { + out.bindAttributeLocation(Weights::Location, "weights"); + out.bindAttributeLocation(JointIds::Location, "jointIds"); + } + if(configuration.secondaryPerVertexJointCount()) { + out.bindAttributeLocation(SecondaryWeights::Location, "secondaryWeights"); + out.bindAttributeLocation(SecondaryJointIds::Location, "secondaryJointIds"); + } + #endif } #endif @@ -382,6 +444,8 @@ PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move #endif { #ifndef MAGNUM_TARGET_GLES2 + if(_flags >= Flag::DynamicPerVertexJointCount) + _perVertexJointCountUniform = uniformLocation("perVertexJointCount"); if(_flags >= Flag::UniformBuffers) { if(_drawCount > 1) _drawOffsetUniform = uniformLocation("drawOffset"); } else @@ -415,6 +479,12 @@ PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move #ifndef MAGNUM_TARGET_GLES2 if(_flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); #endif + #ifndef MAGNUM_TARGET_GLES2 + if(_jointCount) { + _jointMatricesUniform = uniformLocation("jointMatrices"); + _perInstanceJointCountUniform = uniformLocation("perInstanceJointCount"); + } + #endif } } @@ -439,6 +509,8 @@ PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); if(_lightCount) setUniformBlockBinding(uniformBlockIndex("Light"), LightBufferBinding); + if(_jointCount) + setUniformBlockBinding(uniformBlockIndex("Joint"), JointBufferBinding); } #endif } @@ -446,6 +518,10 @@ PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES2 + if(_flags >= Flag::DynamicPerVertexJointCount) + setPerVertexJointCount(_perVertexJointCount, _secondaryPerVertexJointCount); + #endif + #ifndef MAGNUM_TARGET_GLES2 if(_flags >= Flag::UniformBuffers) { /* Draw offset is zero by default */ } else @@ -478,6 +554,12 @@ PhongGL::PhongGL(CompileState&& state): PhongGL{static_cast(std::move /* Texture layer is zero by default */ if(_flags & Flag::AlphaMask) setAlphaMask(0.5f); /* Object ID is zero by default */ + #ifndef MAGNUM_TARGET_GLES2 + if(_jointCount) { + setJointMatrices(Containers::Array{DirectInit, _jointCount, Math::IdentityInit}); + /* Per-instance joint count is zero by default */ + } + #endif } #endif } @@ -500,6 +582,19 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount, const Unsigned #endif #endif +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::setPerVertexJointCount(const UnsignedInt count, const UnsignedInt secondaryCount) { + CORRADE_ASSERT(_flags >= Flag::DynamicPerVertexJointCount, + "Shaders::PhongGL::setPerVertexJointCount(): the shader was not created with dynamic per-vertex joint count enabled", *this); + CORRADE_ASSERT(count <= _perVertexJointCount, + "Shaders::PhongGL::setPerVertexJointCount(): expected at most" << _perVertexJointCount << "per-vertex joints, got" << count, *this); + CORRADE_ASSERT(secondaryCount <= _secondaryPerVertexJointCount, + "Shaders::PhongGL::setPerVertexJointCount(): expected at most" << _secondaryPerVertexJointCount << "secondary per-vertex joints, got" << secondaryCount, *this); + setUniform(_perVertexJointCountUniform, Vector2ui{count, secondaryCount}); + return *this; +} +#endif + PhongGL& PhongGL::setAmbientColor(const Magnum::Color4& color) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), @@ -786,6 +881,37 @@ PhongGL& PhongGL::setLightRange(const UnsignedInt id, const Float range) { return *this; } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::setJointMatrices(const Containers::ArrayView matrices) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setJointMatrices(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_jointCount == matrices.size(), + "Shaders::PhongGL::setJointMatrices(): expected" << _jointCount << "items but got" << matrices.size(), *this); + if(_jointCount) setUniform(_jointMatricesUniform, matrices); + return *this; +} + +PhongGL& PhongGL::setJointMatrices(const std::initializer_list matrices) { + return setJointMatrices(Containers::arrayView(matrices)); +} + +PhongGL& PhongGL::setJointMatrix(const UnsignedInt id, const Matrix4& matrix) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setJointMatrix(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(id < _jointCount, + "Shaders::PhongGL::setJointMatrix(): joint ID" << id << "is out of bounds for" << _jointCount << "joints", *this); + setUniform(_jointMatricesUniform + id, matrix); + return *this; +} + +PhongGL& PhongGL::setPerInstanceJointCount(const UnsignedInt count) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setPerInstanceJointCount(): the shader was created with uniform buffers enabled", *this); + setUniform(_perInstanceJointCountUniform, count); + return *this; +} +#endif + #ifndef MAGNUM_TARGET_GLES2 PhongGL& PhongGL::setDrawOffset(const UnsignedInt offset) { CORRADE_ASSERT(_flags >= Flag::UniformBuffers, @@ -883,6 +1009,20 @@ PhongGL& PhongGL::bindLightBuffer(GL::Buffer& buffer, const GLintptr offset, con buffer.bind(GL::Buffer::Target::Uniform, LightBufferBinding, offset, size); return *this; } + +PhongGL& PhongGL::bindJointBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindJointBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, JointBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindJointBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindJointBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, JointBufferBinding, offset, size); + return *this; +} #endif PhongGL& PhongGL::bindAmbientTexture(GL::Texture2D& texture) { @@ -1006,6 +1146,21 @@ PhongGL& PhongGL::bindTextures(GL::Texture2D* ambient, GL::Texture2D* diffuse, G return *this; } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL::Configuration& PhongGL::Configuration::setJointCount(UnsignedInt count, UnsignedInt perVertexCount, UnsignedInt secondaryPerVertexCount) { + CORRADE_ASSERT(perVertexCount <= 4, + "Shaders::PhongGL::Configuration::setJointCount(): expected at most 4 per-vertex joints, got" << perVertexCount, *this); + CORRADE_ASSERT(secondaryPerVertexCount <= 4, + "Shaders::PhongGL::Configuration::setJointCount(): expected at most 4 secondary per-vertex joints, got" << secondaryPerVertexCount, *this); + CORRADE_ASSERT(!count == (!perVertexCount && !secondaryPerVertexCount), + "Shaders::PhongGL::Configuration::setJointCount(): either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero", *this); + _jointCount = count; + _perVertexJointCount = perVertexCount; + _secondaryPerVertexJointCount = secondaryPerVertexCount; + return *this; +} +#endif + Debug& operator<<(Debug& debug, const PhongGL::Flag value) { #ifndef MAGNUM_TARGET_GLES2 /* Special case coming from the Flags printer. As both flags are a superset @@ -1042,6 +1197,9 @@ Debug& operator<<(Debug& debug, const PhongGL::Flag value) { _c(LightCulling) #endif _c(NoSpecular) + #ifndef MAGNUM_TARGET_GLES2 + _c(DynamicPerVertexJointCount) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -1076,7 +1234,10 @@ Debug& operator<<(Debug& debug, const PhongGL::Flags value) { PhongGL::Flag::TextureArrays, PhongGL::Flag::LightCulling, #endif - PhongGL::Flag::NoSpecular + PhongGL::Flag::NoSpecular, + #ifndef MAGNUM_TARGET_GLES2 + PhongGL::Flag::DynamicPerVertexJointCount, + #endif }); } diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 4435e4b81..d96ea5110 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -225,6 +225,26 @@ The functionality is practically the same as in the @ref FlatGL shader, see @ref Shaders-FlatGL-object-id "its documentation" for more information and usage example. +@requires_gl30 Extension @gl_extension{EXT,texture_integer} +@requires_gles30 Object ID output requires integer support in shaders, which + is not available in OpenGL ES 2.0. +@requires_webgl20 Object ID output requires integer support in shaders, which + is not available in WebGL 1.0. + +@section Shaders-PhongGL-skinning Skinning + +To render skinned meshes, bind up to two sets of up to four-component joint ID +and weight attributes to @ref JointIds / @ref SecondaryJointIds and +@ref Weights / @ref SecondaryWeights, set an appropriate joint count and +per-vertex primary and secondary joint count in +@ref Configuration::setJointCount() and upload appropriate joint matrices with +@ref setJointMatrices(). + +To avoid having to compile multiple shader variants for different per-vertex +joint counts, enable @ref Flag::DynamicPerVertexJointCount, set the maximum +per-vertex joint count in @ref Configuration::setJointCount() and then adjust +the actual per-draw joint count with @ref setPerVertexJointCount(). + @requires_gl30 Extension @gl_extension{EXT,texture_integer} @requires_gles30 Object ID output requires integer support in shaders, which is not available in OpenGL ES 2.0. @@ -248,6 +268,12 @@ well to ensure lighting works: @snippet MagnumShaders-gl.cpp PhongGL-usage-instancing +For instanced skinning the joint buffer is assumed to contain joint +transformations for all instances. By default all instances use the same joint +transformations, seting @ref setPerInstanceJointCount() will cause the shader +to offset the per-vertex joint IDs with +@glsl gl_InstanceID*perInstanceJointCount @ce. + @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, @gl_extension{EXT,instanced_arrays} or @gl_extension{NV,instanced_arrays} @@ -286,6 +312,17 @@ into the @ref PhongLightUniform array using @ref PhongDrawUniform::lightOffset and @relativeref{PhongDrawUniform,lightCount}. Besides that, the usage is similar for all shaders, see @ref shaders-usage-multidraw for an example. +For skinning, joint matrices are supplied via a @ref TransformationUniform3D +buffer bound with @ref bindJointBuffer(). In an instanced scenario the +per-instance joint count is supplied via +@ref PhongDrawUniform::perInstanceJointCount, a per-draw joint offset for the +multidraw scenario is supplied via @ref PhongDrawUniform::jointOffset. +Altogether for a particular draw, each per-vertex joint ID is offset with +@glsl gl_InstanceID*perInstanceJointCount + jointOffset @ce. The +@ref setPerVertexJointCount() stays as an immediate uniform in the UBO and +multidraw scenario as well, as it is tied to a particular mesh layout and thus +doesn't need to vary per draw. + @requires_gl30 Extension @gl_extension{EXT,texture_array} for texture arrays. @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform buffers. @@ -304,7 +341,8 @@ similar for all shaders, see @ref shaders-usage-multidraw for an example. */ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { public: - class Configuration; + /* MSVC needs dllexport here as well */ + class MAGNUM_SHADERS_EXPORT Configuration; class CompileState; /** @@ -395,6 +433,64 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ typedef GenericGL3D::Color4 Color4; + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint ids + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4ui. + * Used only if @ref perVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::JointIds JointIds; + + /** + * @brief Weights + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4. + * Used only if @ref perVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::Weights Weights; + + /** + * @brief Secondary joint ids + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4ui. + * Used only if @ref secondaryPerVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::SecondaryJointIds SecondaryJointIds; + + /** + * @brief Secondary weights + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Vector4. + * Used only if @ref secondaryPerVertexJointCount() isn't @cpp 0 @ce. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + typedef GenericGL3D::SecondaryWeights SecondaryWeights; + #endif + #ifndef MAGNUM_TARGET_GLES2 /** * @brief (Instanced) object ID @@ -757,7 +853,32 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * specular highlights are not desired. * @m_since_latest */ - NoSpecular = 1 << 16 + NoSpecular = 1 << 16, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Dynamic per-vertex joint count for skinning. Uses only the first + * M / N primary / secondary components defined by + * @ref setPerVertexJointCount() instead of + * all primary / secondary components defined by + * @ref Configuration::setJointCount() at shader compilation time. + * Useful in order to avoid having a shader permutation defined for + * every possible joint count. Unfortunately it's not possible to + * make use of default values for unspecified input components as + * the last component is always @cpp 1.0 @ce instead of + * @cpp 0.0 @ce, on the other hand dynamically limiting the joint + * count can reduce the time spent executing the vertex shader + * compared to going through the full set of per-vertex joints + * always. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, + * which is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + * @m_since_latest + */ + DynamicPerVertexJointCount = 1 << 18, + #endif }; /** @@ -898,6 +1019,47 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ UnsignedInt lightCount() const { return _lightCount; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint count + * @m_since_latest + * + * If @ref Flag::UniformBuffers is not set, this is the number of joint + * matrices accepted by @ref setJointMatrices() / @ref setJointMatrix(). + * If @ref Flag::UniformBuffers is set, this is the statically defined + * size of the @ref TransformationUniform3D uniform buffer bound with + * @ref bindJointBuffer(). + * @see @ref Configuration::setJointCount() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt jointCount() const { return _jointCount; } + + /** + * @brief Per-vertex joint count + * @m_since_latest + * + * Returns the value set with @ref Configuration::setJointCount(). If + * @ref Flag::DynamicPerVertexJointCount is set, the count can be + * additionally modified per-draw using @ref setPerVertexJointCount(). + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt perVertexJointCount() const { return _perVertexJointCount; } + + /** + * @brief Secondary per-vertex joint count + * @m_since_latest + * + * Returns the value set with @ref Configuration::setJointCount(). If + * @ref Flag::DynamicPerVertexJointCount is set, the count can be + * additionally modified per-draw using @ref setPerVertexJointCount(). + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt secondaryPerVertexJointCount() const { return _secondaryPerVertexJointCount; } + #endif + #ifndef MAGNUM_TARGET_GLES2 /** * @brief Material count @@ -928,6 +1090,34 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { UnsignedInt drawCount() const { return _drawCount; } #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set dynamic per-vertex skinning joint count + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Allows reducing the count of iterated joints for a particular draw + * call, making it possible to use a single shader with meshes that + * contain different count of per-vertex joints. See + * @ref Flag::DynamicPerVertexJointCount for more information. As the + * joint count is tied to the mesh layout, this is a per-draw-call + * setting even in case of @ref Flag::UniformBuffers instead of being + * a value in @ref PhongDrawUniform. Initial value is the same as + * @ref perVertexJointCount() and @ref secondaryPerVertexJointCount(). + * + * Expects that @ref Flag::DynamicPerVertexJointCount is set, + * @p count is not larger than @ref perVertexJointCount() and + * @p secondaryCount not larger than @ref secondaryPerVertexJointCount(). + * @see @ref Configuration::setJointCount() + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, which + * is not available in WebGL 1.0. + */ + PhongGL& setPerVertexJointCount(UnsignedInt count, UnsignedInt secondaryCount = 0); + #endif + /** @{ * @name Uniform setters * @@ -1393,6 +1583,76 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ PhongGL& setLightRange(UnsignedInt id, Float range); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set joint matrices + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Initial values are identity transformations. Expects that the size + * of the @p matrices array is the same as @ref jointCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindJointBuffer() instead. + * @see @ref setJointMatrix(UnsignedInt, const Matrix4&) + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + PhongGL& setJointMatrices(const Containers::ArrayView matrices); + + /** + * @overload + * @m_since_latest + */ + PhongGL& setJointMatrices(std::initializer_list matrices); + + /** + * @brief Set joint matrix for given joint + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Unlike @ref setJointMatrices() updates just a single joint matrix. + * Expects that @p id is less than @ref jointCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindJointBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + PhongGL& setJointMatrix(UnsignedInt id, const Matrix4& matrix); + + /** + * @brief Set per-instance joint count + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Offset added to joint IDs in the @ref JointIds and + * @ref SecondaryJointIds in instanced draws. Should be less than + * @ref jointCount(). Initial value is @cpp 0 @ce, meaning every + * instance will use the same joint matrices, setting it to a non-zero + * value causes the joint IDs to be interpreted as + * @glsl gl_InstanceID*count + jointId @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongDrawUniform::perInstanceJointCount and call + * @ref bindDrawBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + PhongGL& setPerInstanceJointCount(UnsignedInt count); + #endif + /** * @} */ @@ -1551,6 +1811,25 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ PhongGL& bindLightBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + /** + * @brief Bind a joint matrix 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 jointCount() instances of + * @ref TransformationUniform3D. + * @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. + */ + PhongGL& bindJointBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindJointBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + /** * @} */ @@ -1776,7 +2055,11 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { Flags _flags; UnsignedInt _lightCount{}; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt _materialCount{}, _drawCount{}; + UnsignedInt _jointCount{}, + _perVertexJointCount{}, + _secondaryPerVertexJointCount{}, + _materialCount{}, + _drawCount{}; #endif Int _transformationMatrixUniform{0}, _projectionMatrixUniform{1}, @@ -1799,9 +2082,13 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { _lightSpecularColorsUniform, /* 12 + 2*lightCount */ _lightRangesUniform; /* 12 + 3*lightCount */ #ifndef MAGNUM_TARGET_GLES2 - /* Used instead of all other uniforms when Flag::UniformBuffers is set, - so it can alias them */ - Int _drawOffsetUniform{0}; + Int _jointMatricesUniform, /* 12 + 4*lightCount */ + _perInstanceJointCountUniform, /* 12 + 4*lightCount + jointCount */ + /* Used instead of all other uniforms when Flag::UniformBuffers is + set, so it can alias them */ + _drawOffsetUniform{0}, + /* 13 + 4*lightCount + jointCount, or 1 with UBOs */ + _perVertexJointCountUniform; #endif }; @@ -1811,7 +2098,7 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { @see @ref PhongGL(const Configuration&), @ref compile(const Configuration&) */ -class PhongGL::Configuration { +class MAGNUM_SHADERS_EXPORT PhongGL::Configuration { public: explicit Configuration() = default; @@ -1860,6 +2147,65 @@ class PhongGL::Configuration { } #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt jointCount() const { return _jointCount; } + + /** + * @brief Per-vertex joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt perVertexJointCount() const { return _perVertexJointCount; } + + /** + *@brief Secondary per-vertex joint count + * + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt secondaryPerVertexJointCount() const { return _secondaryPerVertexJointCount; } + + /** + * @brief Set joint count + * + * If @ref Flag::UniformBuffers isn't set, @p count describes how many + * joint matrices get supplied to each draw by @ref setJointMatrices() + * / @ref setJointMatrix(). If @ref Flag::UniformBuffers is set, + * @p count describes size of a @ref TransformationUniform3D buffer + * bound with @ref bindJointBuffer(); as uniform buffers are required + * to have a statically defined size. The per-vertex joints then index + * into the array offset by @ref PhongDrawUniform::jointOffset. If + * @p count is @cpp 0 @ce, skinning is not performed. + * + * The @p perVertexCount and @p secondaryPerVertexCount then describe + * how many components are taken from @ref JointIds / @ref Weights and + * @ref SecondaryJointIds / @ref SecondaryWeights attributes. Both + * values are expected to not be larger than @cpp 4 @ce, setting either + * of these to @cpp 0 @ce means given attribute is not used at all. If + * @p count is @cpp 0 @ce, both @p perVertexCount and + * @p secondaryPerVertexCount is expected to be @cpp 0 @ce as well; if + * @p count is non-zero at least one of @p perVertexCount and + * @p secondaryPerVertexCount is expected to be non-zero as well. + * + * Default value for all three is @cpp 0 @ce. + * @see @ref PhongGL::jointCount(), @ref PhongGL::perVertexJointCount(), + * @ref PhongGL::secondaryPerVertexJointCount(), + * @ref Flag::DynamicPerVertexJointCount, + * @ref PhongGL::setPerVertexJointCount() + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Skinning requires integer support in shaders, which + * is not available in OpenGL ES 2.0. + * @requires_webgl20 Skinning requires integer support in shaders, + * which is not available in WebGL 1.0. + */ + Configuration& setJointCount(UnsignedInt count, UnsignedInt perVertexCount, UnsignedInt secondaryPerVertexCount = 0); + /** * @brief Material count * @@ -1925,7 +2271,10 @@ class PhongGL::Configuration { Flags _flags; UnsignedInt _lightCount = 1; #ifndef MAGNUM_TARGET_GLES2 - UnsignedInt _materialCount = 1, + UnsignedInt _jointCount = 0, + _perVertexJointCount = 0, + _secondaryPerVertexJointCount = 0, + _materialCount = 1, _drawCount = 1; #endif }; diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index 91ab6651a..f617ccd7a 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -38,10 +38,10 @@ corrade_add_test(ShadersVectorTest VectorTest.cpp LIBRARIES MagnumShaders) # There's an underscore between GL and Test to disambiguate from GLTest, which # is a common suffix used to mark tests that need a GL context. Ugly, I know. corrade_add_test(ShadersDistanceFieldVectorGL_Test DistanceFieldVectorGL_Test.cpp LIBRARIES MagnumShaders) -corrade_add_test(ShadersFlatGL_Test FlatGL_Test.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersFlatGL_Test FlatGL_Test.cpp LIBRARIES MagnumShadersTestLib) corrade_add_test(ShadersGenericGL_Test GenericGL_Test.cpp LIBRARIES MagnumShaders) -corrade_add_test(ShadersMeshVisualizerGL_Test MeshVisualizerGL_Test.cpp LIBRARIES MagnumShaders) -corrade_add_test(ShadersPhongGL_Test PhongGL_Test.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersMeshVisualizerGL_Test MeshVisualizerGL_Test.cpp LIBRARIES MagnumShadersTestLib) +corrade_add_test(ShadersPhongGL_Test PhongGL_Test.cpp LIBRARIES MagnumShadersTestLib) corrade_add_test(ShadersVectorGL_Test VectorGL_Test.cpp LIBRARIES MagnumShaders) corrade_add_test(ShadersVertexColorGL_Test VertexColorGL_Test.cpp LIBRARIES MagnumShaders) @@ -127,6 +127,10 @@ if(MAGNUM_BUILD_GL_TESTS) TestFiles/diffuse-alpha-texture.tga TestFiles/diffuse-texture.tga TestFiles/alpha-mask1.0.tga + TestFiles/skinning.tga + TestFiles/skinning-default.tga + TestFiles/skinning-instanced.tga + TestFiles/skinning-multi.tga FlatTestFiles/colored2D.tga FlatTestFiles/colored3D.tga @@ -246,7 +250,11 @@ if(MAGNUM_BUILD_GL_TESTS) MeshVisualizerTestFiles/multidraw-instancedobjectid2D.tga MeshVisualizerTestFiles/multidraw-instancedobjectid3D.tga MeshVisualizerTestFiles/multidraw-objectidtexture2D.tga - MeshVisualizerTestFiles/multidraw-objectidtexture3D.tga) + MeshVisualizerTestFiles/multidraw-objectidtexture3D.tga + MeshVisualizerTestFiles/skinning.tga + MeshVisualizerTestFiles/skinning-default.tga + MeshVisualizerTestFiles/skinning-instanced.tga + MeshVisualizerTestFiles/skinning-multi.tga) target_include_directories(ShadersMeshVisualizerGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(MAGNUM_BUILD_PLUGINS_STATIC) if(MAGNUM_WITH_ANYIMAGEIMPORTER) @@ -284,6 +292,10 @@ if(MAGNUM_BUILD_GL_TESTS) TestFiles/normal-texture.tga TestFiles/specular-texture.tga TestFiles/alpha-mask1.0.tga + TestFiles/skinning.tga + TestFiles/skinning-default.tga + TestFiles/skinning-instanced.tga + TestFiles/skinning-multi.tga PhongTestFiles/colored.tga PhongTestFiles/defaults.tga diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index a7de96ddd..7e00a6839 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -26,11 +26,13 @@ #include #include +#include #include #include #include #include /** @todo remove once AbstractImporter is -free */ #include +#include #include #include #include @@ -86,6 +88,9 @@ struct FlatGLTest: GL::OpenGLTester { explicit FlatGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructSkinning(); + #endif template void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 template void constructUniformBuffers(); @@ -102,6 +107,9 @@ struct FlatGLTest: GL::OpenGLTester { template void constructUniformBuffersInvalid(); #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setPerVertexJointCountInvalid(); + #endif #ifndef MAGNUM_TARGET_GLES2 template void setUniformUniformBuffersEnabled(); template void bindBufferUniformBuffersNotEnabled(); @@ -118,6 +126,7 @@ struct FlatGLTest: GL::OpenGLTester { #endif #ifndef MAGNUM_TARGET_GLES2 template void setObjectIdNotEnabled(); + template void setWrongJointCountOrId(); #endif #ifndef MAGNUM_TARGET_GLES2 template void setWrongDrawOffset(); @@ -152,12 +161,23 @@ struct FlatGLTest: GL::OpenGLTester { template void renderObjectId3D(); #endif + #ifndef MAGNUM_TARGET_GLES2 + template void renderSkinning2D(); + template void renderSkinning3D(); + #endif + template void renderInstanced2D(); template void renderInstanced3D(); + #ifndef MAGNUM_TARGET_GLES2 + template void renderInstancedSkinning2D(); + template void renderInstancedSkinning3D(); + #endif #ifndef MAGNUM_TARGET_GLES2 void renderMulti2D(); void renderMulti3D(); + void renderMultiSkinning2D(); + void renderMultiSkinning3D(); #endif private: @@ -237,52 +257,79 @@ constexpr struct { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +const struct { + const char* name; + FlatGL2D::Flags flags; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; +} ConstructSkinningData[]{ + {"no skinning", {}, + 0, 0, 0}, + {"one set", {}, + 16, 4, 0}, + {"two partial sets", {}, + 32, 2, 3}, + {"secondary set only", {}, + 12, 0, 4}, + {"dynamic per-vertex sets", FlatGL2D::Flag::DynamicPerVertexJointCount, + 16, 4, 3}, +}; +#endif + #ifndef MAGNUM_TARGET_GLES2 constexpr struct { const char* name; FlatGL2D::Flags flags; UnsignedInt materialCount, drawCount; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; } ConstructUniformBuffersData[]{ {"classic fallback", {}, - 1, 1}, + 1, 1, 0, 0, 0}, {"", FlatGL2D::Flag::UniformBuffers, - 1, 1}, + 1, 1, 0, 0, 0}, /* 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}, + 8, 48, 0, 0, 0}, {"textured", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured, - 1, 1}, + 1, 1, 0, 0, 0}, {"textured + texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, - 1, 1}, + 1, 1, 0, 0, 0}, {"texture arrays + texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, - 1, 1}, + 1, 1, 0, 0, 0}, {"alpha mask", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectIdTexture, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture array", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectIdTexture|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::TextureTransformation, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture + instanced texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectIdTexture|FlatGL2D::Flag::InstancedTextureOffset, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture array + instanced texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectIdTexture|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::InstancedTextureOffset, - 1, 1}, + 1, 1, 0, 0, 0}, {"instanced object ID texture array + texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectIdTexture|FlatGL2D::Flag::InstancedObjectId|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::TextureTransformation, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture + textured", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectIdTexture|FlatGL2D::Flag::Textured, - 1, 1}, + 1, 1, 0, 0, 0}, {"instanced texture array offset + layer", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::InstancedTextureOffset, - 1, 1}, - {"multidraw with all the things", FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::AlphaMask|FlatGL2D::Flag::ObjectId|FlatGL2D::Flag::InstancedTextureOffset|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId, - 8, 48} + 1, 1, 0, 0, 0}, + {"skinning", FlatGL2D::Flag::UniformBuffers, + 1, 1, 32, 3, 2}, + {"skinning, dynamic per-vertex sets", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::DynamicPerVertexJointCount, + 1, 1, 32, 3, 4}, + {"multidraw with all the things except secondary per-vertex sets", FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::AlphaMask|FlatGL2D::Flag::ObjectId|FlatGL2D::Flag::InstancedTextureOffset|FlatGL2D::Flag::InstancedTransformation|FlatGL2D::Flag::InstancedObjectId|FlatGL2D::Flag::DynamicPerVertexJointCount, + 8, 48, 16, 4, 0}, + {"multidraw with all the things except instancing", FlatGL2D::Flag::MultiDraw|FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::AlphaMask|FlatGL2D::Flag::ObjectId|FlatGL2D::Flag::DynamicPerVertexJointCount, + 8, 48, 16, 3, 4} }; #endif constexpr struct { const char* name; FlatGL2D::Flags flags; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; const char* message; } ConstructInvalidData[]{ {"texture transformation but not textured", @@ -293,13 +340,23 @@ constexpr struct { |FlatGL2D::Flag::ObjectId #endif , + 0, 0, 0, "texture transformation enabled but the shader is not textured"}, #ifndef MAGNUM_TARGET_GLES2 {"texture arrays but not textured", /* ObjectId shares bits with ObjectIdTexture but should still trigger the assert */ FlatGL2D::Flag::TextureArrays|FlatGL2D::Flag::ObjectId, - "texture arrays enabled but the shader is not textured"} + 0, 0, 0, + "texture arrays enabled but the shader is not textured"}, + {"dynamic per-vertex joint count but no static per-vertex joint count", + FlatGL2D::Flag::DynamicPerVertexJointCount, + 0, 0, 0, + "dynamic per-vertex joint count enabled for zero joints"}, + {"instancing together with secondary per-vertex sets", + FlatGL2D::Flag::InstancedTransformation, + 10, 4, 1, + "TransformationMatrix attribute binding conflicts with the SecondaryJointIds / SecondaryWeights attributes, use a non-instanced rendering with secondary weights instead"} #endif }; @@ -460,6 +517,81 @@ const struct { }; #endif +#ifndef MAGNUM_TARGET_GLES2 +/* Same as in PhongGL and MeshVisualizerGL tests */ +const struct { + const char* name; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; + UnsignedInt dynamicPerVertexJointCount, dynamicSecondaryPerVertexJointCount; + FlatGL2D::Flags flags; + Containers::Array> attributes; + bool setDynamicPerVertexJointCount, setJointMatrices, setJointMatricesOneByOne; + const char* expected; +} RenderSkinningData[]{ + {"no skinning", 0, 0, 0, 0, 0, {}, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::Three}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::Three}}, + }}, false, false, false, + "skinning-default.tga"}, + {"default joint matrices", 5, 3, 0, 0, 0, {}, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::Three}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::Three}}, + }}, false, false, false, + "skinning-default.tga"}, + {"single set", 5, 3, 0, 0, 0, {}, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::Three}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"single set, joint matrices one by one", 5, 3, 0, 0, 0, {}, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::Three}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::Three}}, + }}, false, true, true, + "skinning.tga"}, + {"single set, dynamic, left at defaults", 5, 3, 0, 0, 0, FlatGL2D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::Three}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"single set, dynamic", 5, 4, 0, 3, 0, FlatGL2D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::Three}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::Three}}, + }}, true, true, false, + "skinning.tga"}, + {"two sets", 5, 1, 2, 0, 0, {}, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::One}}, + {4, FlatGL2D::SecondaryJointIds{FlatGL2D::SecondaryJointIds::Components::Two}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::One}}, + {4*4, FlatGL2D::SecondaryWeights{FlatGL2D::SecondaryWeights::Components::Two}}, + }}, false, true, false, + "skinning.tga"}, + {"two sets, dynamic, left at defaults", 5, 1, 2, 0, 0, FlatGL2D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::One}}, + {4, FlatGL2D::SecondaryJointIds{FlatGL2D::SecondaryJointIds::Components::Two}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::One}}, + {4*4, FlatGL2D::SecondaryWeights{FlatGL2D::SecondaryWeights::Components::Two}}, + }}, false, true, false, + "skinning.tga"}, + {"two sets, dynamic", 5, 4, 4, 1, 2, FlatGL2D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, FlatGL2D::JointIds{FlatGL2D::JointIds::Components::One}}, + {4, FlatGL2D::SecondaryJointIds{FlatGL2D::SecondaryJointIds::Components::Two}}, + {3*4, FlatGL2D::Weights{FlatGL2D::Weights::Components::One}}, + {4*4, FlatGL2D::SecondaryWeights{FlatGL2D::SecondaryWeights::Components::Two}}, + }}, true, true, false, + "skinning.tga"}, + {"only secondary set", 5, 0, 3, 0, 0, {}, {InPlaceInit, { + {0, FlatGL2D::SecondaryJointIds{FlatGL2D::SecondaryJointIds::Components::Three}}, + {3*4, FlatGL2D::SecondaryWeights{FlatGL2D::SecondaryWeights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"only secondary set, dynamic", 5, 4, 4, 0, 3, FlatGL2D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, FlatGL2D::SecondaryJointIds{FlatGL2D::SecondaryJointIds::Components::Three}}, + {3*4, FlatGL2D::SecondaryWeights{FlatGL2D::SecondaryWeights::Components::Three}}, + }}, true, true, false, + "skinning.tga"}, +}; +#endif + constexpr struct { const char* name; const char* expected2D; @@ -613,6 +745,21 @@ constexpr struct { while the 2D array has a black area around) */ 65.0f, 0.15f} }; + +/* Same as in PhongGL and MeshVisualizerGL tests */ +const struct { + const char* name; + FlatGL2D::Flags flags; + UnsignedInt materialCount, drawCount, jointCount; + UnsignedInt uniformIncrement; +} RenderMultiSkinningData[]{ + {"bind with offset", + {}, 1, 1, 4, 16}, + {"draw offset", + {}, 2, 3, 9, 1}, + {"multidraw", + FlatGL2D::Flag::MultiDraw, 2, 3, 9, 1} +}; #endif FlatGLTest::FlatGLTest() { @@ -621,6 +768,13 @@ FlatGLTest::FlatGLTest() { &FlatGLTest::construct<3>}, Containers::arraySize(ConstructData)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &FlatGLTest::constructSkinning<2>, + &FlatGLTest::constructSkinning<3>}, + Containers::arraySize(ConstructSkinningData)); + #endif + addTests({ &FlatGLTest::constructAsync<2>, &FlatGLTest::constructAsync<3>}); @@ -660,6 +814,8 @@ FlatGLTest::FlatGLTest() { addTests({ #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::setPerVertexJointCountInvalid<2>, + &FlatGLTest::setPerVertexJointCountInvalid<3>, &FlatGLTest::setUniformUniformBuffersEnabled<2>, &FlatGLTest::setUniformUniformBuffersEnabled<3>, &FlatGLTest::bindBufferUniformBuffersNotEnabled<2>, @@ -695,6 +851,8 @@ FlatGLTest::FlatGLTest() { #ifndef MAGNUM_TARGET_GLES2 &FlatGLTest::setObjectIdNotEnabled<2>, &FlatGLTest::setObjectIdNotEnabled<3>, + &FlatGLTest::setWrongJointCountOrId<2>, + &FlatGLTest::setWrongJointCountOrId<3>, #endif #ifndef MAGNUM_TARGET_GLES2 &FlatGLTest::setWrongDrawOffset<2>, @@ -803,6 +961,18 @@ FlatGLTest::FlatGLTest() { &FlatGLTest::renderObjectIdTeardown); #endif + #ifndef MAGNUM_TARGET_GLES2 + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &FlatGLTest::renderSkinning2D, + &FlatGLTest::renderSkinning2D, + &FlatGLTest::renderSkinning3D, + &FlatGLTest::renderSkinning3D}, + Containers::arraySize(RenderSkinningData), + &FlatGLTest::renderSetup, + &FlatGLTest::renderTeardown); + #endif + /* MSVC needs explicit type due to default template args */ addInstancedTests({ &FlatGLTest::renderInstanced2D, @@ -824,12 +994,29 @@ FlatGLTest::FlatGLTest() { #endif ); + #ifndef MAGNUM_TARGET_GLES2 + /* MSVC needs explicit type due to default template args */ + addTests({ + &FlatGLTest::renderInstancedSkinning2D, + &FlatGLTest::renderInstancedSkinning2D, + &FlatGLTest::renderInstancedSkinning3D, + &FlatGLTest::renderInstancedSkinning3D}, + &FlatGLTest::renderSetup, + &FlatGLTest::renderTeardown); + #endif + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&FlatGLTest::renderMulti2D, &FlatGLTest::renderMulti3D}, Containers::arraySize(RenderMultiData), &FlatGLTest::renderObjectIdSetup, &FlatGLTest::renderObjectIdTeardown); + + addInstancedTests({&FlatGLTest::renderMultiSkinning2D, + &FlatGLTest::renderMultiSkinning3D}, + Containers::arraySize(RenderMultiSkinningData), + &FlatGLTest::renderSetup, + &FlatGLTest::renderTeardown); #endif /* Load the plugins directly from the build tree. Otherwise they're either @@ -883,11 +1070,43 @@ template void FlatGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::constructSkinning() { + auto&& data = ConstructSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + FlatGL shader{typename FlatGL::Configuration{} + .setFlags(data.flags) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.jointCount(), data.jointCount); + CORRADE_COMPARE(shader.perVertexJointCount(), data.perVertexJointCount); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), data.secondaryPerVertexJointCount); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void FlatGLTest::constructAsync() { setTestCaseTemplateName(Utility::format("{}", dimensions)); typename FlatGL::CompileState state = FlatGL::compile(typename FlatGL::Configuration{} - .setFlags(FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation)); + .setFlags(FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation) + /* Skinning properties tested in constructUniformBuffersAsync(), as + there we don't need to bother with ES2 */ + ); CORRADE_COMPARE(state.flags(), FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation); while(!state.isLinkFinished()) @@ -917,7 +1136,7 @@ template void FlatGLTest::constructUniformBuffers() { #ifndef MAGNUM_TARGET_GLES if((data.flags & FlatGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); - if((data.flags & FlatGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + if((data.flags & FlatGL2D::Flag::ObjectId || data.jointCount) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); @@ -939,10 +1158,14 @@ template void FlatGLTest::constructUniformBuffers() { FlatGL shader{typename FlatGL::Configuration{} .setFlags(data.flags) .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; + .setDrawCount(data.drawCount) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; CORRADE_COMPARE(shader.flags(), data.flags); CORRADE_COMPARE(shader.materialCount(), data.materialCount); CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_COMPARE(shader.jointCount(), data.jointCount); + CORRADE_COMPARE(shader.perVertexJointCount(), data.perVertexJointCount); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), data.secondaryPerVertexJointCount); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) @@ -960,23 +1183,32 @@ template void FlatGLTest::constructUniformBuffersAsync() #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif typename FlatGL::CompileState state = FlatGL::compile(typename FlatGL::Configuration{} .setFlags(FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask) - .setMaterialCount(8) - .setDrawCount(48)); + .setMaterialCount(5) + .setDrawCount(36) + .setJointCount(7, 3, 4)); CORRADE_COMPARE(state.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); - CORRADE_COMPARE(state.materialCount(), 8); - CORRADE_COMPARE(state.drawCount(), 48); + CORRADE_COMPARE(state.materialCount(), 5); + CORRADE_COMPARE(state.drawCount(), 36); + CORRADE_COMPARE(state.jointCount(), 7); + CORRADE_COMPARE(state.perVertexJointCount(), 3); + CORRADE_COMPARE(state.secondaryPerVertexJointCount(), 4); while(!state.isLinkFinished()) Utility::System::sleep(100); FlatGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask); - CORRADE_COMPARE(shader.materialCount(), 8); - CORRADE_COMPARE(shader.drawCount(), 48); + CORRADE_COMPARE(shader.materialCount(), 5); + CORRADE_COMPARE(shader.drawCount(), 36); + CORRADE_COMPARE(shader.jointCount(), 7); + CORRADE_COMPARE(shader.perVertexJointCount(), 3); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), 4); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) @@ -994,7 +1226,10 @@ template void FlatGLTest::constructMove() { setTestCaseTemplateName(Utility::format("{}", dimensions)); FlatGL a{typename FlatGL::Configuration{} - .setFlags(FlatGL::Flag::Textured)}; + .setFlags(FlatGL::Flag::Textured) + /* Skinning properties tested in constructMoveUniformBuffers(), as + there we don't need to bother with ES2 */ + }; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -1019,12 +1254,15 @@ template void FlatGLTest::constructMoveUniformBuffers() #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif FlatGL a{typename FlatGL::Configuration{} .setFlags(FlatGL::Flag::UniformBuffers) .setMaterialCount(2) - .setDrawCount(5)}; + .setDrawCount(5) + .setJointCount(16, 4, 3)}; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -1035,6 +1273,9 @@ template void FlatGLTest::constructMoveUniformBuffers() CORRADE_COMPARE(b.flags(), FlatGL::Flag::UniformBuffers); CORRADE_COMPARE(b.materialCount(), 2); CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_COMPARE(b.jointCount(), 16); + CORRADE_COMPARE(b.perVertexJointCount(), 4); + CORRADE_COMPARE(b.secondaryPerVertexJointCount(), 3); CORRADE_VERIFY(!a.id()); FlatGL c{NoCreate}; @@ -1043,6 +1284,9 @@ template void FlatGLTest::constructMoveUniformBuffers() CORRADE_COMPARE(c.flags(), FlatGL::Flag::UniformBuffers); CORRADE_COMPARE(c.materialCount(), 2); CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_COMPARE(c.jointCount(), 16); + CORRADE_COMPARE(c.perVertexJointCount(), 4); + CORRADE_COMPARE(c.secondaryPerVertexJointCount(), 3); CORRADE_VERIFY(!b.id()); } #endif @@ -1057,7 +1301,11 @@ template void FlatGLTest::constructInvalid() { std::ostringstream out; Error redirectError{&out}; FlatGL{typename FlatGL::Configuration{} - .setFlags(data.flags)}; + .setFlags(data.flags) + #ifndef MAGNUM_TARGET_GLES2 + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount) + #endif + }; CORRADE_COMPARE(out.str(), Utility::formatString( "Shaders::FlatGL: {}\n", data.message)); } @@ -1086,6 +1334,34 @@ template void FlatGLTest::constructUniformBuffersInvalid } #endif +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::setPerVertexJointCountInvalid() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + FlatGL a{typename FlatGL::Configuration{}}; + FlatGL b{typename FlatGL::Configuration{} + .setFlags(FlatGL::Flag::DynamicPerVertexJointCount) + .setJointCount(16, 3, 2)}; + + std::ostringstream out; + Error redirectError{&out}; + a.setPerVertexJointCount(3, 2); + b.setPerVertexJointCount(4); + b.setPerVertexJointCount(3, 3); + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setPerVertexJointCount(): the shader was not created with dynamic per-vertex joint count enabled\n" + "Shaders::FlatGL::setPerVertexJointCount(): expected at most 3 per-vertex joints, got 4\n" + "Shaders::FlatGL::setPerVertexJointCount(): expected at most 2 secondary per-vertex joints, got 3\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 template void FlatGLTest::setUniformUniformBuffersEnabled() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -1102,19 +1378,27 @@ template void FlatGLTest::setUniformUniformBuffersEnable std::ostringstream out; Error redirectError{&out}; - shader.setTransformationProjectionMatrix({}) + shader + /* setPerVertexJointCount() works on both UBOs and classic */ + .setTransformationProjectionMatrix({}) .setTextureMatrix({}) .setTextureLayer({}) .setColor({}) .setAlphaMask({}) - .setObjectId({}); + .setObjectId({}) + .setJointMatrices({}) + .setJointMatrix(0, {}) + .setPerInstanceJointCount(0); CORRADE_COMPARE(out.str(), "Shaders::FlatGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" "Shaders::FlatGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" "Shaders::FlatGL::setTextureLayer(): the shader was created with uniform buffers enabled\n" "Shaders::FlatGL::setColor(): the shader was created with uniform buffers enabled\n" "Shaders::FlatGL::setAlphaMask(): the shader was created with uniform buffers enabled\n" - "Shaders::FlatGL::setObjectId(): the shader was created with uniform buffers enabled\n"); + "Shaders::FlatGL::setObjectId(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setJointMatrices(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setJointMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setPerInstanceJointCount(): the shader was created with uniform buffers enabled\n"); } template void FlatGLTest::bindBufferUniformBuffersNotEnabled() { @@ -1135,6 +1419,8 @@ template void FlatGLTest::bindBufferUniformBuffersNotEna .bindTextureTransformationBuffer(buffer, 0, 16) .bindMaterialBuffer(buffer) .bindMaterialBuffer(buffer, 0, 16) + .bindJointBuffer(buffer) + .bindJointBuffer(buffer, 0, 16) .setDrawOffset(0); CORRADE_COMPARE(out.str(), "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" @@ -1145,6 +1431,8 @@ template void FlatGLTest::bindBufferUniformBuffersNotEna "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::bindJointBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindJointBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::FlatGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } #endif @@ -1284,6 +1572,30 @@ template void FlatGLTest::setObjectIdNotEnabled() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::setWrongJointCountOrId() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + FlatGL shader{typename FlatGL::Configuration{} + .setJointCount(5, 1)}; + + std::ostringstream out; + Error redirectError{&out}; + shader.setJointMatrices({MatrixTypeFor{}}); + shader.setJointMatrix(5, MatrixTypeFor{}); + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setJointMatrices(): expected 5 items but got 1\n" + "Shaders::FlatGL::setJointMatrix(): joint ID 5 is out of bounds for 5 joints\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 template void FlatGLTest::setWrongDrawOffset() { setTestCaseTemplateName(Utility::format("{}", dimensions)); @@ -2818,6 +3130,261 @@ template void FlatGLTest::renderObjectId3D() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::renderSkinning2D() { + auto&& data = RenderSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(data.jointCount && GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + + /* Same as in PhongGLTest::renderSkinning(), except in 2D, and same as in + MeshVisualizerGLTest::renderSkinning2D() */ + struct Vertex { + Vector2 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Top right corner gets moved to the right and up, top left just up, + bottom right just right, bottom left corner gets slightly scaled. + + 3--1 + | /| + |/ | + 2--0 */ + {{ 1.0f, -1.0f}, {0, 2, 0}, {1.0f, 50.0f, 0.5f}}, + {{ 1.0f, 1.0f}, {1, 0, 0}, {0.5f, 0.5f, 0.0f}}, + {{-1.0f, -1.0f}, {3, 4, 4}, {0.5f, 0.25f, 0.25f}}, + {{-1.0f, 1.0f}, {1, 0, 4}, {1.0f, 0.0f, 0.0f}}, + }; + + Matrix3 jointMatrices[]{ + Matrix3::translation(Vector2::xAxis(0.5f)), + Matrix3::translation(Vector2::yAxis(0.5f)), + Matrix3{Math::ZeroInit}, + Matrix3::scaling(Vector2{2.0f}), + Matrix3{Math::IdentityInit}, + }; + + GL::Buffer buffer{vertices}; + + GL::Mesh mesh{MeshPrimitive::TriangleStrip}; + mesh.setCount(4); + mesh.addVertexBuffer(buffer, 0, sizeof(Vertex), GL::DynamicAttribute{FlatGL2D::Position{}}); + for(auto&& attribute: data.attributes) + mesh.addVertexBuffer(buffer, 2*4 + attribute.first(), sizeof(Vertex), attribute.second()); + + FlatGL2D shader{FlatGL2D::Configuration{} + .setFlags(data.flags|flag) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + if(data.setDynamicPerVertexJointCount) + shader.setPerVertexJointCount(data.dynamicPerVertexJointCount, data.dynamicSecondaryPerVertexJointCount); + + if(flag == FlatGL2D::Flag{}) { + if(data.setJointMatricesOneByOne) { + shader + .setJointMatrix(0, jointMatrices[0]) + .setJointMatrix(1, jointMatrices[1]) + .setJointMatrix(2, jointMatrices[2]) + .setJointMatrix(3, jointMatrices[3]) + .setJointMatrix(4, jointMatrices[4]); + } else if(data.setJointMatrices) + shader.setJointMatrices(jointMatrices); + shader + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.5f})) + .draw(mesh); + } else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.5f})) + }}; + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[0] : Matrix3{}), + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[1] : Matrix3{}), + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[2] : Matrix3{}), + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[3] : Matrix3{}), + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[4] : Matrix3{}), + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join({_testDir, "TestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); +} + +template void FlatGLTest::renderSkinning3D() { + auto&& data = RenderSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(data.jointCount && GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + + /* Same as in PhongGLTest::renderSkinning() */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Top right corner gets moved to the right and up, top left just up, + bottom right just right, bottom left corner gets slightly scaled. + + 3--1 + | /| + |/ | + 2--0 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 2, 0}, {1.0f, 50.0f, 0.5f}}, + {{ 1.0f, 1.0f, 0.0f}, {1, 0, 0}, {0.5f, 0.5f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {3, 4, 4}, {0.5f, 0.25f, 0.25f}}, + {{-1.0f, 1.0f, 0.0f}, {1, 0, 4}, {1.0f, 0.0f, 0.0f}}, + }; + + Matrix4 jointMatrices[]{ + Matrix4::translation(Vector3::xAxis(0.5f)), + Matrix4::translation(Vector3::yAxis(0.5f)), + Matrix4{Math::ZeroInit}, + Matrix4::scaling(Vector3{2.0f}), + Matrix4{Math::IdentityInit}, + }; + + GL::Buffer buffer{vertices}; + + GL::Mesh mesh{MeshPrimitive::TriangleStrip}; + mesh.setCount(4); + mesh.addVertexBuffer(buffer, 0, sizeof(Vertex), GL::DynamicAttribute{FlatGL3D::Position{}}); + for(auto&& attribute: data.attributes) + mesh.addVertexBuffer(buffer, 3*4 + attribute.first(), sizeof(Vertex), attribute.second()); + + FlatGL3D shader{FlatGL3D::Configuration{} + .setFlags(data.flags|flag) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + if(data.setDynamicPerVertexJointCount) + shader.setPerVertexJointCount(data.dynamicPerVertexJointCount, data.dynamicSecondaryPerVertexJointCount); + + if(flag == FlatGL3D::Flag{}) { + if(data.setJointMatricesOneByOne) { + shader + .setJointMatrix(0, jointMatrices[0]) + .setJointMatrix(1, jointMatrices[1]) + .setJointMatrix(2, jointMatrices[2]) + .setJointMatrix(3, jointMatrices[3]) + .setJointMatrix(4, jointMatrices[4]); + } else if(data.setJointMatrices) + shader.setJointMatrices(jointMatrices); + shader + .setTransformationProjectionMatrix(Matrix4::scaling(Vector3{0.5f})) + .draw(mesh); + } else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix(Matrix4::scaling(Vector3{0.5f})) + }}; + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[0] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[1] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[2] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[3] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[4] : Matrix4{}), + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join({_testDir, "TestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); +} +#endif + template void FlatGLTest::renderInstanced2D() { auto&& data = RenderInstancedData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -3444,30 +4011,287 @@ template void FlatGLTest::renderInstanced3D() { } #ifndef MAGNUM_TARGET_GLES2 -void FlatGLTest::renderMulti2D() { - auto&& data = RenderMultiData[testCaseInstanceId()]; - setTestCaseDescription(data.name); - - #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); - if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); - #endif - +template void FlatGLTest::renderInstancedSkinning2D() { #ifndef MAGNUM_TARGET_GLES - if((data.flags & FlatGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif - if(data.flags >= FlatGL2D::Flag::MultiDraw) { + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); - #elif !defined(MAGNUM_TARGET_WEBGL) - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); - #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + + /* Similarly to renderSkinning2D() tests just 2D movement, differently and + clearly distinguisable for each instance */ + struct Vertex { + Vector2 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 3--1 + | /| + |/ | + 2--0 */ + {{ 1.0f, -1.0f}, {0, 0, 0}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 1.0f}, {0, 3, 0}, {0.0f, 1.0f, 0.0f}}, + {{-1.0f, -1.0f}, {0, 0, 1}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, 1.0f}, {4, 0, 0}, {1.0f, 0.0f, 0.0f}}, + }; + + Matrix3 instanceTransformations[]{ + Matrix3::translation({-1.5f, -1.5f}), + Matrix3::translation({ 1.5f, -1.5f}), + Matrix3::translation({ 0.0f, 1.5f}) + }; + + Matrix3 jointMatrices[]{ + /* First instance moves bottom left corner */ + {}, + Matrix3::translation({-0.5f, -0.5f}), + {}, + {}, + {}, + + /* Second instance moves bottom right corner */ + Matrix3::translation({0.5f, -0.5f}), + {}, + {}, + {}, + {}, + + /* Third instance moves both top corners */ + {}, + {}, + {}, + Matrix3::translation({0.5f, 0.5f}), + Matrix3::translation({-0.5f, 0.5f}), + }; + + GL::Mesh mesh{MeshPrimitive::TriangleStrip}; + mesh.setCount(4) + .addVertexBuffer(GL::Buffer{vertices}, 0, + FlatGL2D::Position{}, + FlatGL2D::JointIds{FlatGL2D::JointIds::Components::Three}, + FlatGL2D::Weights{FlatGL2D::Weights::Components::Three}) + .addVertexBufferInstanced(GL::Buffer{instanceTransformations}, 1, 0, + FlatGL2D::TransformationMatrix{}) + .setInstanceCount(3); + + FlatGL2D shader{FlatGL2D::Configuration{} + .setFlags(FlatGL2D::Flag::InstancedTransformation|flag) + .setJointCount(15, 3, 0)}; + + if(flag == FlatGL2D::Flag{}) { + shader + .setJointMatrices(jointMatrices) + .setPerInstanceJointCount(5) + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.3f})) + .draw(mesh); + } else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.3f})) + }}; + TransformationUniform2D jointMatricesUniformData[Containers::arraySize(jointMatrices)]; + Utility::copy( /* This API is so powerful it should be outlawed!! */ + Containers::arrayCast<2, const Vector3>(Containers::stridedArrayView(jointMatrices)), Containers::arrayCast<2, Vector4>(Containers::stridedArrayView(jointMatricesUniformData).slice(&TransformationUniform2D::transformationMatrix)).slice(&Vector4::xyz)); + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, + jointMatricesUniformData + }; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setPerInstanceJointCount(5) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "TestFiles/skinning-instanced.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +template void FlatGLTest::renderInstancedSkinning3D() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + + /* Similarly to renderSkinning3D() tests just 2D movement, differently and + clearly distinguisable for each instance */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 3--1 + | /| + |/ | + 2--0 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 0, 0}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0, 3, 0}, {0.0f, 1.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0, 0, 1}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, 1.0f, 0.0f}, {4, 0, 0}, {1.0f, 0.0f, 0.0f}}, + }; + + Matrix4 instanceTransformations[]{ + Matrix4::translation({-1.5f, -1.5f, 0.0f}), + Matrix4::translation({ 1.5f, -1.5f, 0.0f}), + Matrix4::translation({ 0.0f, 1.5f, 0.0f}) + }; + + Matrix4 jointMatrices[]{ + /* First instance moves bottom left corner */ + {}, + Matrix4::translation({-0.5f, -0.5f, 0.0f}), + {}, + {}, + {}, + + /* Second instance moves bottom right corner */ + Matrix4::translation({0.5f, -0.5f, 0.0f}), + {}, + {}, + {}, + {}, + + /* Third instance moves both top corners */ + {}, + {}, + {}, + Matrix4::translation({0.5f, 0.5f, 0.0f}), + Matrix4::translation({-0.5f, 0.5f, 0.0f}), + }; + + GL::Mesh mesh{MeshPrimitive::TriangleStrip}; + mesh.setCount(4) + .addVertexBuffer(GL::Buffer{vertices}, 0, + FlatGL3D::Position{}, + FlatGL3D::JointIds{FlatGL3D::JointIds::Components::Three}, + FlatGL3D::Weights{FlatGL3D::Weights::Components::Three}) + .addVertexBufferInstanced(GL::Buffer{instanceTransformations}, 1, 0, + FlatGL3D::TransformationMatrix{}) + .setInstanceCount(3); + + FlatGL3D shader{FlatGL3D::Configuration{} + .setFlags(FlatGL3D::Flag::InstancedTransformation|flag) + .setJointCount(15, 3, 0)}; + + if(flag == FlatGL3D::Flag{}) { + shader + .setJointMatrices(jointMatrices) + .setPerInstanceJointCount(5) + .setTransformationProjectionMatrix(Matrix4::scaling(Vector3{0.3f})) + .draw(mesh); + } else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix(Matrix4::scaling(Vector3{0.3f})) + }}; + TransformationUniform3D jointMatricesUniformData[Containers::arraySize(jointMatrices)]; + Utility::copy(jointMatrices, Containers::stridedArrayView(jointMatricesUniformData).slice(&TransformationUniform3D::transformationMatrix)); + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, + jointMatricesUniformData + }; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setPerInstanceJointCount(5) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + FlatMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "TestFiles/skinning-instanced.tga"), + (DebugTools::CompareImageToFile{_manager})); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +void FlatGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if((data.flags & FlatGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + if(data.flags >= FlatGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); #endif @@ -4128,6 +4952,478 @@ void FlatGLTest::renderMulti3D() { CORRADE_COMPARE(image.pixels()[56][40], data.expectedId[2]); /* Circle */ } } + +void FlatGLTest::renderMultiSkinning2D() { + auto&& data = RenderMultiSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= FlatGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + FlatGL2D shader{FlatGL2D::Configuration{} + .setFlags(FlatGL2D::Flag::UniformBuffers|data.flags) + .setDrawCount(data.drawCount) + .setMaterialCount(data.materialCount) + .setJointCount(data.jointCount, 2, 0)}; + + /* Similarly to renderSkinning2D() tests just 2D movement, differently and + clearly distinguisable for each draw */ + struct Vertex { + Vector2 position; + UnsignedInt jointIds[2]; + Float weights[2]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 3--1 5 9--8 + | /| /| | / + |/ | / | |/ + 2--0 6--4 7 */ + {{ 1.0f, -1.0f}, {0, 0}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f}, {0, 2}, {0.0f, 1.0f}}, + {{-1.0f, -1.0f}, {1, 2}, {1.0f, 0.0f}}, + {{-1.0f, 1.0f}, {0, 3}, {0.0f, 1.0f}}, + + {{ 1.0f, -1.0f}, {0, 3}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f}, {2, 1}, {1.0f, 0.0f}}, + {{-1.0f, -1.0f}, {0, 0}, {1.0f, 0.0f}}, + + {{-1.0f, -1.0f}, {0, 1}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f}, {1, 0}, {1.0f, 0.0f}}, + {{-1.0f, 1.0f}, {2, 2}, {0.5f, 0.5f}} + }; + + UnsignedInt indices[]{ + 0, 1, 2, + 2, 1, 3, + + 4, 5, 6, + + 7, 8, 9 + }; + + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(12) + .addVertexBuffer(GL::Buffer{vertices}, 0, + FlatGL2D::Position{}, + FlatGL2D::JointIds{FlatGL2D::JointIds::Components::Two}, + FlatGL2D::Weights{FlatGL2D::Weights::Components::Two}) + .setIndexBuffer(GL::Buffer{indices}, 0, MeshIndexType::UnsignedInt); + GL::MeshView square{mesh}; + square.setCount(6); + GL::MeshView triangle1{mesh}; + triangle1.setCount(3) + .setIndexRange(6); + GL::MeshView triangle2{mesh}; + triangle2.setCount(3) + .setIndexRange(9); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + 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(0x33ffff_rgbf); + materialData[1*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(0xffff33_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::scaling(Vector2{0.3f})* + Matrix3::translation({ 0.0f, -1.5f})); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::scaling(Vector2{0.3f})* + Matrix3::translation({ 1.5f, 1.5f})); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::scaling(Vector2{0.3f})* + Matrix3::translation({-1.5f, 1.5f})); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array jointData{Math::max(2*data.uniformIncrement + 4, 10u)}; + /* First draw moves both bottom corners */ + jointData[Math::max(0*data.uniformIncrement, 0u) + 0] = TransformationUniform2D{} + .setTransformationMatrix(Matrix3::translation({ 0.5f, -0.5f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 1] = TransformationUniform2D{} + .setTransformationMatrix(Matrix3::translation({-0.5f, -0.5f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 2] = TransformationUniform2D{}; + jointData[Math::max(0*data.uniformIncrement, 0u) + 3] = TransformationUniform2D{}; + /* Second draw overlaps with the first with two identity matrices (unless + the padding prevents that); moves top right corner */ + jointData[Math::max(1*data.uniformIncrement, 2u) + 0] = TransformationUniform2D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 1] = TransformationUniform2D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 2] = TransformationUniform2D{} + .setTransformationMatrix(Matrix3::translation({ 0.5f, 0.5f})); + jointData[Math::max(1*data.uniformIncrement, 2u) + 3] = TransformationUniform2D{}; + /* Third draw moves top left corner */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 0] = TransformationUniform2D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 1] = TransformationUniform2D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 2] = TransformationUniform2D{} + .setTransformationMatrix(Matrix3::translation({-0.5f, 0.5f})); + /* This one is unused but has to be here in order to be able to bind the + last three-component part while JOINT_COUNT is set to 4 */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 3] = TransformationUniform2D{}; + GL::Buffer jointUniform{GL::Buffer::TargetHint::Uniform, + jointData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material / joint offsets are zero if we have single draw, as those are + done with UBO offset bindings instead */ + drawData[0*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + /* Overlaps with the first joint set with two matrices, unless the + padding in the single-draw case prevents that */ + .setJointOffset(data.drawCount == 1 ? 0 : 2); + drawData[2*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 6); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + /* 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)); + shader.bindJointBuffer(jointUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform2D), + data.jointCount*sizeof(TransformationUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindJointBuffer(jointUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform2D), + data.jointCount*sizeof(TransformationUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + shader.draw(triangle1); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindJointBuffer(jointUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform2D), + data.jointCount*sizeof(TransformationUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + shader.draw(triangle2); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindJointBuffer(jointUniform) + .bindDrawBuffer(drawUniform); + + if(data.flags >= FlatGL2D::Flag::MultiDraw) + shader.draw({square, triangle1, triangle2}); + else { + shader.setDrawOffset(0) + .draw(square); + shader.setDrawOffset(1) + .draw(triangle1); + shader.setDrawOffset(2) + .draw(triangle2); + } + } + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "TestFiles/skinning-multi.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +void FlatGLTest::renderMultiSkinning3D() { + auto&& data = RenderMultiSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= FlatGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + FlatGL3D shader{FlatGL3D::Configuration{} + .setFlags(FlatGL3D::Flag::UniformBuffers|data.flags) + .setDrawCount(data.drawCount) + .setMaterialCount(data.materialCount) + .setJointCount(data.jointCount, 2, 0)}; + + /* Similarly to renderSkinning3D() tests just 2D movement, differently and + clearly distinguisable for each draw */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[2]; + Float weights[2]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 3--1 5 9--8 + | /| /| | / + |/ | / | |/ + 2--0 6--4 7 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 0}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0, 2}, {0.0f, 1.0f}}, + {{-1.0f, -1.0f, 0.0f}, {1, 2}, {1.0f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}, {0, 3}, {0.0f, 1.0f}}, + + {{ 1.0f, -1.0f, 0.0f}, {0, 3}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {2, 1}, {1.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0, 0}, {1.0f, 0.0f}}, + + {{-1.0f, -1.0f, 0.0f}, {0, 1}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {1, 0}, {1.0f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}, {2, 2}, {0.5f, 0.5f}} + }; + + UnsignedInt indices[]{ + 0, 1, 2, + 2, 1, 3, + + 4, 5, 6, + + 7, 8, 9 + }; + + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(12) + .addVertexBuffer(GL::Buffer{vertices}, 0, + FlatGL3D::Position{}, + FlatGL3D::JointIds{FlatGL3D::JointIds::Components::Two}, + FlatGL3D::Weights{FlatGL3D::Weights::Components::Two}) + .setIndexBuffer(GL::Buffer{indices}, 0, MeshIndexType::UnsignedInt); + GL::MeshView square{mesh}; + square.setCount(6); + GL::MeshView triangle1{mesh}; + triangle1.setCount(3) + .setIndexRange(6); + GL::MeshView triangle2{mesh}; + triangle2.setCount(3) + .setIndexRange(9); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + 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(0x33ffff_rgbf); + materialData[1*data.uniformIncrement] = FlatMaterialUniform{} + .setColor(0xffff33_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({ 0.0f, -1.5f, 0.0f})); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({ 1.5f, 1.5f, 0.0f})); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({-1.5f, 1.5f, 0.0f})); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array jointData{Math::max(2*data.uniformIncrement + 4, 10u)}; + /* First draw moves both bottom corners */ + jointData[Math::max(0*data.uniformIncrement, 0u) + 0] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({ 0.5f, -0.5f, 0.0f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 1] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({-0.5f, -0.5f, 0.0f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 2] = TransformationUniform3D{}; + jointData[Math::max(0*data.uniformIncrement, 0u) + 3] = TransformationUniform3D{}; + /* Second draw overlaps with the first with two identity matrices (unless + the padding prevents that); moves top right corner */ + jointData[Math::max(1*data.uniformIncrement, 2u) + 0] = TransformationUniform3D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 1] = TransformationUniform3D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 2] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({ 0.5f, 0.5f, 0.0f})); + jointData[Math::max(1*data.uniformIncrement, 2u) + 3] = TransformationUniform3D{}; + /* Third draw moves top left corner */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 0] = TransformationUniform3D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 1] = TransformationUniform3D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 2] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({-0.5f, 0.5f, 0.0f})); + /* This one is unused but has to be here in order to be able to bind the + last three-component part while JOINT_COUNT is set to 4 */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 3] = TransformationUniform3D{}; + GL::Buffer jointUniform{GL::Buffer::TargetHint::Uniform, + jointData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material / joint offsets are zero if we have single draw, as those are + done with UBO offset bindings instead */ + drawData[0*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + /* Overlaps with the first joint set with two matrices, unless the + padding in the single-draw case prevents that */ + .setJointOffset(data.drawCount == 1 ? 0 : 2); + drawData[2*data.uniformIncrement] = FlatDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 6); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + /* 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)); + shader.bindJointBuffer(jointUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindJointBuffer(jointUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + shader.draw(triangle1); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(FlatMaterialUniform), + sizeof(FlatMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindJointBuffer(jointUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + shader.draw(triangle2); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindJointBuffer(jointUniform) + .bindDrawBuffer(drawUniform); + + if(data.flags >= FlatGL3D::Flag::MultiDraw) + shader.draw({square, triangle1, triangle2}); + else { + shader.setDrawOffset(0) + .draw(square); + shader.setDrawOffset(1) + .draw(triangle1); + shader.setDrawOffset(2) + .draw(triangle2); + } + } + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "TestFiles/skinning-multi.tga"), + (DebugTools::CompareImageToFile{_manager})); +} #endif }}}} diff --git a/src/Magnum/Shaders/Test/FlatGL_Test.cpp b/src/Magnum/Shaders/Test/FlatGL_Test.cpp index 36489ce9d..f03a3af46 100644 --- a/src/Magnum/Shaders/Test/FlatGL_Test.cpp +++ b/src/Magnum/Shaders/Test/FlatGL_Test.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include "Magnum/Shaders/FlatGL.h" @@ -38,6 +38,10 @@ namespace Magnum { namespace Shaders { namespace Test { namespace { struct FlatGL_Test: TestSuite::Tester { explicit FlatGL_Test(); + #ifndef MAGNUM_TARGET_GLES2 + template void configurationSetJointCountInvalid(); + #endif + template void constructNoCreate(); template void constructCopy(); @@ -46,7 +50,38 @@ struct FlatGL_Test: TestSuite::Tester { void debugFlagsSupersets(); }; +#ifndef MAGNUM_TARGET_GLES2 +const struct { + const char* name; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; + const char* message; +} ConfigurationSetJointCountInvalidData[] { + {"per-vertex joint count too large", + 10, 5, 0, + "expected at most 4 per-vertex joints, got 5"}, + {"secondary per-vertex joint count too large", + 10, 0, 5, + "expected at most 4 secondary per-vertex joints, got 5"}, + {"joint count but no per-vertex joint count", + 10, 0, 0, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, + {"per-vertex joint count but no joint count", + 0, 2, 0, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, + {"secondary per-vertex joint count but no joint count", + 0, 0, 3, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, +}; +#endif + FlatGL_Test::FlatGL_Test() { + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &FlatGL_Test::configurationSetJointCountInvalid<2>, + &FlatGL_Test::configurationSetJointCountInvalid<3>}, + Containers::arraySize(ConfigurationSetJointCountInvalidData)); + #endif + addTests({&FlatGL_Test::constructNoCreate<2>, &FlatGL_Test::constructNoCreate<3>, @@ -58,6 +93,23 @@ FlatGL_Test::FlatGL_Test() { &FlatGL_Test::debugFlagsSupersets}); } +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGL_Test::configurationSetJointCountInvalid() { + auto&& data = ConfigurationSetJointCountInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + typename FlatGL::Configuration configuration; + + std::ostringstream out; + Error redirectError{&out}; + configuration.setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount); + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::FlatGL::Configuration::setJointCount(): {}\n", data.message)); +} +#endif + template void FlatGL_Test::constructNoCreate() { setTestCaseTemplateName(Utility::format("{}", dimensions)); diff --git a/src/Magnum/Shaders/Test/FlatTest.cpp b/src/Magnum/Shaders/Test/FlatTest.cpp index 1a9d71057..474afb2b1 100644 --- a/src/Magnum/Shaders/Test/FlatTest.cpp +++ b/src/Magnum/Shaders/Test/FlatTest.cpp @@ -90,6 +90,10 @@ void FlatTest::drawUniformConstructDefault() { CORRADE_COMPARE(b.materialId, 0); CORRADE_COMPARE(a.objectId, 0); CORRADE_COMPARE(b.objectId, 0); + CORRADE_COMPARE(a.jointOffset, 0); + CORRADE_COMPARE(b.jointOffset, 0); + CORRADE_COMPARE(a.perInstanceJointCount, 0); + CORRADE_COMPARE(b.perInstanceJointCount, 0); constexpr FlatDrawUniform ca; constexpr FlatDrawUniform cb{DefaultInit}; @@ -97,6 +101,10 @@ void FlatTest::drawUniformConstructDefault() { CORRADE_COMPARE(cb.materialId, 0); CORRADE_COMPARE(ca.objectId, 0); CORRADE_COMPARE(cb.objectId, 0); + CORRADE_COMPARE(ca.jointOffset, 0); + CORRADE_COMPARE(cb.jointOffset, 0); + CORRADE_COMPARE(ca.perInstanceJointCount, 0); + CORRADE_COMPARE(cb.perInstanceJointCount, 0); CORRADE_VERIFY(std::is_nothrow_default_constructible::value); CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -110,6 +118,7 @@ void FlatTest::drawUniformConstructNoInit() { FlatDrawUniform a; a.materialId = 5; a.objectId = 7; + a.perInstanceJointCount = 9; new(&a) FlatDrawUniform{NoInit}; { @@ -121,6 +130,7 @@ void FlatTest::drawUniformConstructNoInit() { #endif CORRADE_COMPARE(a.materialId, 5); CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.perInstanceJointCount, 9); } CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -132,17 +142,29 @@ void FlatTest::drawUniformConstructNoInit() { void FlatTest::drawUniformSetters() { FlatDrawUniform a; a.setMaterialId(5) - .setObjectId(7); + .setObjectId(7) + .setJointOffset(6) + .setPerInstanceJointCount(8); CORRADE_COMPARE(a.materialId, 5); CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.jointOffset, 6); + CORRADE_COMPARE(a.perInstanceJointCount, 8); } void FlatTest::drawUniformMaterialIdPacking() { FlatDrawUniform a; - a.setMaterialId(13765); + a.setMaterialId(13765) + /* second 16 bits unused */ + .setJointOffset(13767) + .setPerInstanceJointCount(63574); /* 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); + /* second 16 bits unused */ + + /* jointOffset in the low, perInstanceJointCount in the high */ + CORRADE_COMPARE(reinterpret_cast(&a)[2] & 0xffff, 13767); + CORRADE_COMPARE((reinterpret_cast(&a)[2] >> 16) & 0xffff, 63574); } void FlatTest::materialUniformConstructDefault() { diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index 9fcfbd346..416ee08f9 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -27,10 +27,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -92,6 +94,10 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { void construct2D(); void construct3D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructSkinning2D(); + void constructSkinning3D(); + #endif void constructAsync2D(); void constructAsync3D(); #ifndef MAGNUM_TARGET_GLES2 @@ -115,6 +121,10 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { void constructUniformBuffersInvalid3D(); #endif + #ifndef MAGNUM_TARGET_GLES2 + void setPerVertexJointCountInvalid2D(); + void setPerVertexJointCountInvalid3D(); + #endif #ifndef MAGNUM_TARGET_GLES2 void setUniformUniformBuffersEnabled2D(); void setUniformUniformBuffersEnabled3D(); @@ -147,6 +157,10 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { void setTangentBitangentNormalNotEnabled3D(); #endif #ifndef MAGNUM_TARGET_GLES2 + void setWrongJointCountOrId2D(); + void setWrongJointCountOrId3D(); + #endif + #ifndef MAGNUM_TARGET_GLES2 void setWrongDrawOffset2D(); void setWrongDrawOffset3D(); #endif @@ -183,12 +197,21 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { template void renderTangentBitangentNormal(); #endif + template void renderSkinningWireframe2D(); + template void renderSkinningWireframe3D(); + template void renderInstanced2D(); template void renderInstanced3D(); + #ifndef MAGNUM_TARGET_GLES2 + template void renderInstancedSkinningWireframe2D(); + template void renderInstancedSkinningWireframe3D(); + #endif #ifndef MAGNUM_TARGET_GLES2 void renderMulti2D(); void renderMulti3D(); + void renderMultiSkinningWireframe2D(); + void renderMultiSkinningWireframe3D(); #endif private: @@ -306,131 +329,169 @@ constexpr struct { }; #ifndef MAGNUM_TARGET_GLES2 +const struct { + const char* name; + MeshVisualizerGL2D::Flags flags2D; + MeshVisualizerGL3D::Flags flags3D; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; +} ConstructSkinningData[]{ + {"no skinning", {}, {}, + 0, 0, 0}, + {"one set", {}, {}, + 16, 4, 0}, + {"two partial sets", {}, {}, + 32, 2, 3}, + {"secondary set only", {}, {}, + 12, 0, 4}, + {"dynamic per-vertex sets", + MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, + 16, 4, 3}, +}; + constexpr struct { const char* name; MeshVisualizerGL2D::Flags flags; UnsignedInt materialCount, drawCount; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; } ConstructUniformBuffersData2D[] { {"classic fallback", MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, - 1, 1}, + 1, 1, 0, 0, 0}, {"", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, - 1, 1}, + 1, 1, 0, 0, 0}, /* SwiftShader has 256 uniform vectors at most, per-2D-draw is 4, per-material 4, two need to be left for drawOffset + viewportSize */ {"multiple materials, draws", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, - 8, 55}, + 8, 55, 0, 0, 0}, + {"skinning", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe, + 1, 1, 32, 3, 2}, + {"skinning, dynamic per-vertex sets", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + 1, 1, 32, 3, 4}, {"multidraw with wireframe w/o GS and vertex ID", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader|MeshVisualizerGL2D::Flag::VertexId, - 8, 55}, + 8, 55, 0, 0, 0}, #ifndef MAGNUM_TARGET_WEBGL {"multidraw with wireframe and primitive ID", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::PrimitiveId, - 8, 55}, + 8, 55, 0, 0, 0}, #endif + {"multidraw with wireframe w/o GS, instancing and dynamic primary skinning per-vertex sets", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader|MeshVisualizerGL2D::Flag::InstancedTransformation|MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + 8, 55, 16, 4, 0}, + {"multidraw with wireframe w/o GS and dynamic primary+secondary skinning per-vertex sets", MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader|MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + 8, 55, 16, 3, 4}, /* The rest is basically a copy of ConstructData2D with UniformBuffers added */ #ifndef MAGNUM_TARGET_WEBGL {"wireframe", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe, - 1, 1}, + 1, 1, 0, 0, 0}, #endif {"wireframe w/o GS", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::ObjectId, - 1, 1}, + 1, 1, 0, 0, 0}, {"instanced object ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::InstancedObjectId, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::ObjectIdTexture, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture array", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::ObjectIdTexture|MeshVisualizerGL2D::Flag::TextureArrays|MeshVisualizerGL2D::Flag::TextureTransformation, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture + instanced texture transformation", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::ObjectIdTexture|MeshVisualizerGL2D::Flag::InstancedTextureOffset, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture array + instanced texture transformation", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::ObjectIdTexture|MeshVisualizerGL2D::Flag::TextureArrays|MeshVisualizerGL2D::Flag::InstancedTextureOffset, - 1, 1}, + 1, 1, 0, 0, 0}, {"instanced object ID texture array + texture transformation", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::ObjectIdTexture|MeshVisualizerGL2D::Flag::InstancedObjectId|MeshVisualizerGL2D::Flag::TextureArrays|MeshVisualizerGL2D::Flag::TextureTransformation, - 1, 1}, + 1, 1, 0, 0, 0}, {"wireframe + object ID texture + instanced texture transformation", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::ObjectIdTexture|MeshVisualizerGL2D::Flag::InstancedTextureOffset, - 1, 1}, + 1, 1, 0, 0, 0}, {"vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::VertexId, - 1, 1}, + 1, 1, 0, 0, 0}, #ifndef MAGNUM_TARGET_WEBGL {"primitive ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::PrimitiveId, - 1, 1}, + 1, 1, 0, 0, 0}, #endif {"primitive ID from vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, - 1, 1} + 1, 1, 0, 0, 0} }; constexpr struct { const char* name; MeshVisualizerGL3D::Flags flags; UnsignedInt materialCount, drawCount; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; } ConstructUniformBuffersData3D[] { {"classic fallback", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, - 1, 1}, + 1, 1, 0, 0, 0}, {"", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, - 1, 1}, + 1, 1, 0, 0, 0}, /* SwiftShader has 256 uniform vectors at most, per-3D-draw is 4+4, per-material 4, plus 4 for projection */ {"multiple materials, draws", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, - 6, 28}, + 6, 28, 0, 0, 0}, + {"skinning", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe, + 1, 1, 32, 3, 2}, + {"skinning, dynamic per-vertex sets", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, + 1, 1, 32, 3, 4}, {"multidraw with wireframe w/o GS and vertex ID", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader|MeshVisualizerGL3D::Flag::VertexId, - 6, 28}, + 6, 28, 0, 0, 0}, #ifndef MAGNUM_TARGET_WEBGL {"multidraw with wireframe, primitive ID and TBN", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::PrimitiveId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, - 6, 28}, + 6, 28, 0, 0, 0}, #endif + {"multidraw with wireframe w/o GS, instancing and dynamic primary skinning per-vertex sets", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader|MeshVisualizerGL3D::Flag::InstancedTransformation|MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, + 8, 55, 16, 4, 0}, + {"multidraw with wireframe w/o GS and dynamic primary+secondary skinning per-vertex sets", MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader|MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, + 8, 55, 16, 3, 4}, /* The rest is basically a copy of ConstructData2D with UniformBuffers added */ #ifndef MAGNUM_TARGET_WEBGL {"wireframe", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe, - 1, 1}, + 1, 1, 0, 0, 0}, #endif {"wireframe w/o GS", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::ObjectId, - 1, 1}, + 1, 1, 0, 0, 0}, {"instanced object ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::InstancedObjectId, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::ObjectIdTexture, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture array", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::ObjectIdTexture|MeshVisualizerGL3D::Flag::TextureArrays|MeshVisualizerGL3D::Flag::TextureTransformation, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture + instanced texture transformation", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::ObjectIdTexture|MeshVisualizerGL3D::Flag::InstancedTextureOffset, - 1, 1}, + 1, 1, 0, 0, 0}, {"object ID texture array + instanced texture transformation", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::ObjectIdTexture|MeshVisualizerGL3D::Flag::TextureArrays|MeshVisualizerGL3D::Flag::InstancedTextureOffset, - 1, 1}, + 1, 1, 0, 0, 0}, {"instanced object ID texture array + texture transformation", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::ObjectIdTexture|MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::TextureArrays|MeshVisualizerGL3D::Flag::TextureTransformation, - 1, 1}, + 1, 1, 0, 0, 0}, {"wireframe + object ID texture + instanced texture transformation", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::ObjectIdTexture|MeshVisualizerGL3D::Flag::InstancedTextureOffset, - 1, 1}, + 1, 1, 0, 0, 0}, {"vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::VertexId, - 1, 1}, + 1, 1, 0, 0, 0}, #ifndef MAGNUM_TARGET_WEBGL {"primitive ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::PrimitiveId, - 1, 1}, + 1, 1, 0, 0, 0}, #endif {"primitive ID from vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, - 1, 1}, + 1, 1, 0, 0, 0}, #ifndef MAGNUM_TARGET_WEBGL {"tangent direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection, - 1, 1}, + 1, 1, 0, 0, 0}, {"bitangent direction from tangent", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection, - 1, 1}, + 1, 1, 0, 0, 0}, {"bitangent direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::BitangentDirection, - 1, 1}, + 1, 1, 0, 0, 0}, {"normal direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::NormalDirection, - 1, 1}, + 1, 1, 0, 0, 0}, {"tbn direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, - 1, 1}, + 1, 1, 0, 0, 0}, {"tbn direction with bitangent from tangent", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, - 1, 1}, + 1, 1, 0, 0, 0}, {"wireframe + vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId, - 1, 1}, + 1, 1, 0, 0, 0}, {"wireframe + T/N direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, - 1, 1}, + 1, 1, 0, 0, 0}, {"wireframe + instanced object ID + T/N direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, - 1, 1}, + 1, 1, 0, 0, 0}, {"wireframe + vertex ID + T/B direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection, - 1, 1} + 1, 1, 0, 0, 0} #endif }; #endif @@ -438,10 +499,12 @@ constexpr struct { constexpr struct { const char* name; MeshVisualizerGL2D::Flags flags; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; const char* message; } ConstructInvalidData2D[] { {"no feature enabled", MeshVisualizerGL2D::Flag::NoGeometryShader, /* not a feature flag */ + 0, 0, 0, #ifndef MAGNUM_TARGET_GLES2 "2D: at least one visualization feature has to be enabled" #else @@ -451,33 +514,48 @@ constexpr struct { #ifndef MAGNUM_TARGET_GLES2 {"both object and primitive ID", MeshVisualizerGL2D::Flag::ObjectId|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, + 0, 0, 0, ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"both instanced object and primitive ID", MeshVisualizerGL2D::Flag::InstancedObjectId|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, + 0, 0, 0, ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"both object and vertex ID", MeshVisualizerGL2D::Flag::ObjectId|MeshVisualizerGL2D::Flag::VertexId, + 0, 0, 0, ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"texture transformation but not textured", /* ObjectId shares bits with ObjectIdTexture but should still trigger the assert */ MeshVisualizerGL2D::Flag::TextureTransformation|MeshVisualizerGL2D::Flag::ObjectId, + 0, 0, 0, ": texture transformation enabled but the shader is not textured"}, {"texture arrays but not textured", /* ObjectId shares bits with ObjectIdTexture but should still trigger the assert */ MeshVisualizerGL2D::Flag::TextureArrays|MeshVisualizerGL2D::Flag::ObjectId, - ": texture arrays enabled but the shader is not textured"} + 0, 0, 0, + ": texture arrays enabled but the shader is not textured"}, + {"dynamic per-vertex joint count but no static per-vertex joint count", + MeshVisualizerGL2D::Flag::ObjectId|MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + 0, 0, 0, + "2D: dynamic per-vertex joint count enabled for zero joints"}, + {"instancing together with secondary per-vertex sets", + MeshVisualizerGL2D::Flag::ObjectId|MeshVisualizerGL2D::Flag::InstancedTransformation, + 10, 4, 1, + "2D: TransformationMatrix attribute binding conflicts with the SecondaryJointIds / SecondaryWeights attributes, use a non-instanced rendering with secondary weights instead"} #endif }; constexpr struct { const char* name; MeshVisualizerGL3D::Flags flags; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; const char* message; } ConstructInvalidData3D[] { {"no feature enabled", MeshVisualizerGL3D::Flag::NoGeometryShader, /* not a feature flag */ + 0, 0, 0, #ifndef MAGNUM_TARGET_GLES2 "3D: at least one visualization feature has to be enabled" #else @@ -487,35 +565,53 @@ constexpr struct { #ifndef MAGNUM_TARGET_GLES2 {"both object and primitive ID", MeshVisualizerGL3D::Flag::ObjectId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, + 0, 0, 0, ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"both instanced object and primitive ID", MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, + 0, 0, 0, ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"both vertex and primitive ID", MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, + 0, 0, 0, ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"texture transformation but not textured", /* ObjectId shares bits with ObjectIdTexture but should still trigger the assert */ MeshVisualizerGL3D::Flag::TextureTransformation|MeshVisualizerGL3D::Flag::ObjectId, + 0, 0, 0, ": texture transformation enabled but the shader is not textured"}, {"texture arrays but not textured", /* ObjectId shares bits with ObjectIdTexture but should still trigger the assert */ MeshVisualizerGL3D::Flag::TextureArrays|MeshVisualizerGL3D::Flag::ObjectId, + 0, 0, 0, ": texture arrays enabled but the shader is not textured"}, #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) {"geometry shader disabled but needed", MeshVisualizerGL3D::Flag::NoGeometryShader|MeshVisualizerGL3D::Flag::NormalDirection, + 0, 0, 0, "3D: geometry shader has to be enabled when rendering TBN direction"}, {"conflicting bitangent input", MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection, + 0, 0, 0, "3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive"}, {"conflicting bitangent and instanced object ID attribute", MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::InstancedObjectId, + 0, 0, 0, "3D: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead"}, #endif + #ifndef MAGNUM_TARGET_GLES2 + {"dynamic per-vertex joint count but no static per-vertex joint count", + MeshVisualizerGL3D::Flag::ObjectId|MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, + 0, 0, 0, + "3D: dynamic per-vertex joint count enabled for zero joints"}, + {"instancing together with secondary per-vertex sets", + MeshVisualizerGL3D::Flag::ObjectId|MeshVisualizerGL3D::Flag::InstancedTransformation, + 10, 4, 1, + "3D: TransformationMatrix attribute binding conflicts with the SecondaryJointIds / SecondaryWeights attributes, use a non-instanced rendering with secondary weights instead"} + #endif }; #ifndef MAGNUM_TARGET_GLES2 @@ -812,6 +908,91 @@ constexpr struct { }; #endif +#ifndef MAGNUM_TARGET_GLES2 +/* Same as in FlatGL and PhongGL tests */ +const struct { + const char* name; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; + UnsignedInt dynamicPerVertexJointCount, dynamicSecondaryPerVertexJointCount; + MeshVisualizerGL2D::Flags flags2D; + MeshVisualizerGL3D::Flags flags3D; + Containers::Array> attributes; + bool setDynamicPerVertexJointCount, setJointMatrices, setJointMatricesOneByOne; + const char* expected; +} RenderSkinningData[]{ + {"no skinning", 0, 0, 0, 0, 0, {}, {}, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::Three}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::Three}}, + }}, false, false, false, + "skinning-default.tga"}, + {"default joint matrices", 5, 3, 0, 0, 0, {}, {}, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::Three}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::Three}}, + }}, false, false, false, + "skinning-default.tga"}, + {"single set", 5, 3, 0, 0, 0, {}, {}, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::Three}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"single set, joint matrices one by one", 5, 3, 0, 0, 0, {}, {}, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::Three}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::Three}}, + }}, false, true, true, + "skinning.tga"}, + {"single set, dynamic, left at defaults", 5, 3, 0, 0, 0, + MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::Three}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"single set, dynamic", 5, 4, 0, 3, 0, + MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::Three}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::Three}}, + }}, true, true, false, + "skinning.tga"}, + {"two sets", 5, 1, 2, 0, 0, {}, {}, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::One}}, + {4, MeshVisualizerGL2D::SecondaryJointIds{MeshVisualizerGL2D::SecondaryJointIds::Components::Two}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::One}}, + {4*4, MeshVisualizerGL2D::SecondaryWeights{MeshVisualizerGL2D::SecondaryWeights::Components::Two}}, + }}, false, true, false, + "skinning.tga"}, + {"two sets, dynamic, left at defaults", 5, 1, 2, 0, 0, + MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::One}}, + {4, MeshVisualizerGL2D::SecondaryJointIds{MeshVisualizerGL2D::SecondaryJointIds::Components::Two}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::One}}, + {4*4, MeshVisualizerGL2D::SecondaryWeights{MeshVisualizerGL2D::SecondaryWeights::Components::Two}}, + }}, false, true, false, + "skinning.tga"}, + {"two sets, dynamic", 5, 4, 4, 1, 2, + MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::One}}, + {4, MeshVisualizerGL2D::SecondaryJointIds{MeshVisualizerGL2D::SecondaryJointIds::Components::Two}}, + {3*4, MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::One}}, + {4*4, MeshVisualizerGL2D::SecondaryWeights{MeshVisualizerGL2D::SecondaryWeights::Components::Two}}, + }}, true, true, false, + "skinning.tga"}, + {"only secondary set", 5, 0, 3, 0, 0, {}, {}, {InPlaceInit, { + {0, MeshVisualizerGL2D::SecondaryJointIds{MeshVisualizerGL2D::SecondaryJointIds::Components::Three}}, + {3*4, MeshVisualizerGL2D::SecondaryWeights{MeshVisualizerGL2D::SecondaryWeights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"only secondary set, dynamic", 5, 4, 4, 0, 3, + MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount, + MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, MeshVisualizerGL2D::SecondaryJointIds{MeshVisualizerGL2D::SecondaryJointIds::Components::Three}}, + {3*4, MeshVisualizerGL2D::SecondaryWeights{MeshVisualizerGL2D::SecondaryWeights::Components::Three}}, + }}, true, true, false, + "skinning.tga"}, +}; +#endif + const struct { const char* name; const char* expected; @@ -1092,6 +1273,23 @@ constexpr struct { 2, 3, 1, 0.0f, 0.0f}, }; + +/* Same as in FlatGL and PhongGL tests */ +const struct { + const char* name; + MeshVisualizerGL2D::Flags flags2D; + MeshVisualizerGL3D::Flags flags3D; + UnsignedInt materialCount, drawCount, jointCount; + UnsignedInt uniformIncrement; +} RenderMultiSkinningData[]{ + {"bind with offset", + {}, {}, 1, 1, 4, 16}, + {"draw offset", + {}, {}, 2, 3, 9, 1}, + {"multidraw", + MeshVisualizerGL2D::Flag::MultiDraw, + MeshVisualizerGL3D::Flag::MultiDraw, 2, 3, 9, 1} +}; #endif MeshVisualizerGLTest::MeshVisualizerGLTest() { @@ -1100,6 +1298,12 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { addInstancedTests({&MeshVisualizerGLTest::construct3D}, Containers::arraySize(ConstructData3D)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructSkinning2D, + &MeshVisualizerGLTest::constructSkinning3D}, + Containers::arraySize(ConstructSkinningData)); + #endif + addTests({&MeshVisualizerGLTest::constructAsync2D, &MeshVisualizerGLTest::constructAsync3D}); @@ -1130,13 +1334,14 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffersInvalid2D}, Containers::arraySize(ConstructUniformBuffersInvalidData2D)); - addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffersInvalid3D}, Containers::arraySize(ConstructUniformBuffersInvalidData3D)); #endif #ifndef MAGNUM_TARGET_GLES2 - addTests({&MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D, + addTests({&MeshVisualizerGLTest::setPerVertexJointCountInvalid2D, + &MeshVisualizerGLTest::setPerVertexJointCountInvalid3D, + &MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D, &MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D, &MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled2D, &MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled3D}); @@ -1172,6 +1377,8 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D, #endif #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::setWrongJointCountOrId2D, + &MeshVisualizerGLTest::setWrongJointCountOrId3D, &MeshVisualizerGLTest::setWrongDrawOffset2D, &MeshVisualizerGLTest::setWrongDrawOffset3D #endif @@ -1312,6 +1519,18 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::renderTeardown); #endif + #ifndef MAGNUM_TARGET_GLES2 + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &MeshVisualizerGLTest::renderSkinningWireframe2D, + &MeshVisualizerGLTest::renderSkinningWireframe2D, + &MeshVisualizerGLTest::renderSkinningWireframe3D, + &MeshVisualizerGLTest::renderSkinningWireframe3D}, + Containers::arraySize(RenderSkinningData), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #endif + /* MSVC needs explicit type due to default template args */ addInstancedTests({ &MeshVisualizerGLTest::renderInstanced2D, @@ -1322,8 +1541,6 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { Containers::arraySize(RenderInstancedData2D), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); - - /* MSVC needs explicit type due to default template args */ addInstancedTests({ &MeshVisualizerGLTest::renderInstanced3D, #ifndef MAGNUM_TARGET_GLES2 @@ -1334,16 +1551,32 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + /* MSVC needs explicit type due to default template args */ + addTests({ + &MeshVisualizerGLTest::renderInstancedSkinningWireframe2D, + &MeshVisualizerGLTest::renderInstancedSkinningWireframe2D, + &MeshVisualizerGLTest::renderInstancedSkinningWireframe3D, + &MeshVisualizerGLTest::renderInstancedSkinningWireframe3D}, + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #endif + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&MeshVisualizerGLTest::renderMulti2D}, Containers::arraySize(RenderMultiData2D), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); - addInstancedTests({&MeshVisualizerGLTest::renderMulti3D}, Containers::arraySize(RenderMultiData3D), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); + + addInstancedTests({&MeshVisualizerGLTest::renderMultiSkinningWireframe2D, + &MeshVisualizerGLTest::renderMultiSkinningWireframe3D}, + Containers::arraySize(RenderMultiSkinningData), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); #endif /* Load the plugins directly from the build tree. Otherwise they're either @@ -1504,9 +1737,72 @@ void MeshVisualizerGLTest::construct3D() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructSkinning2D() { + auto&& data = ConstructSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Configuration{} + /* At least one visualization feature has to be enabled; disable GS so + we don't need to check for it on ES */ + .setFlags(data.flags2D|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + CORRADE_COMPARE(shader.flags(), data.flags2D|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(shader.jointCount(), data.jointCount); + CORRADE_COMPARE(shader.perVertexJointCount(), data.perVertexJointCount); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), data.secondaryPerVertexJointCount); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +void MeshVisualizerGLTest::constructSkinning3D() { + auto&& data = ConstructSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Configuration{} + /* At least one visualization feature has to be enabled; disable GS so + we don't need to check for it on ES */ + .setFlags(data.flags3D|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + CORRADE_COMPARE(shader.flags(), data.flags3D|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(shader.jointCount(), data.jointCount); + CORRADE_COMPARE(shader.perVertexJointCount(), data.perVertexJointCount); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), data.secondaryPerVertexJointCount); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + void MeshVisualizerGLTest::constructAsync2D() { MeshVisualizerGL2D::CompileState state = MeshVisualizerGL2D::compile(MeshVisualizerGL2D::Configuration{} - .setFlags(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader)); + .setFlags(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) + /* Skinning properties tested in constructUniformBuffersAsync2D(), as + there we don't need to bother with ES2 */ + ); CORRADE_COMPARE(state.flags(), MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); while(!state.isLinkFinished()) @@ -1528,7 +1824,10 @@ void MeshVisualizerGLTest::constructAsync2D() { void MeshVisualizerGLTest::constructAsync3D() { MeshVisualizerGL3D::CompileState state = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Configuration{} - .setFlags(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader)); + .setFlags(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) + /* Skinning properties tested in constructUniformBuffersAsync3D(), as + there we don't need to bother with ES2 */ + ); CORRADE_COMPARE(state.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); while(!state.isLinkFinished()) @@ -1554,7 +1853,7 @@ void MeshVisualizerGLTest::constructUniformBuffers2D() { setTestCaseDescription(data.name); #ifndef MAGNUM_TARGET_GLES - if((data.flags & MeshVisualizerGL2D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + if((data.flags & MeshVisualizerGL2D::Flag::InstancedObjectId || data.jointCount) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif @@ -1616,10 +1915,14 @@ void MeshVisualizerGLTest::constructUniformBuffers2D() { MeshVisualizerGL2D shader{MeshVisualizerGL2D::Configuration{} .setFlags(data.flags) .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; + .setDrawCount(data.drawCount) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; CORRADE_COMPARE(shader.flags(), data.flags); CORRADE_COMPARE(shader.materialCount(), data.materialCount); CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_COMPARE(shader.jointCount(), data.jointCount); + CORRADE_COMPARE(shader.perVertexJointCount(), data.perVertexJointCount); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), data.secondaryPerVertexJointCount); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) @@ -1636,7 +1939,7 @@ void MeshVisualizerGLTest::constructUniformBuffers3D() { setTestCaseDescription(data.name); #ifndef MAGNUM_TARGET_GLES - if((data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + if((data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId || data.jointCount) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif @@ -1698,10 +2001,14 @@ void MeshVisualizerGLTest::constructUniformBuffers3D() { MeshVisualizerGL3D shader{MeshVisualizerGL3D::Configuration{} .setFlags(data.flags) .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; + .setDrawCount(data.drawCount) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; CORRADE_COMPARE(shader.flags(), data.flags); CORRADE_COMPARE(shader.materialCount(), data.materialCount); CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_COMPARE(shader.jointCount(), data.jointCount); + CORRADE_COMPARE(shader.perVertexJointCount(), data.perVertexJointCount); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), data.secondaryPerVertexJointCount); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) @@ -1717,15 +2024,21 @@ void MeshVisualizerGLTest::constructUniformBuffersAsync2D() { #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif MeshVisualizerGL2D::CompileState state = MeshVisualizerGL2D::compile(MeshVisualizerGL2D::Configuration{} .setFlags( MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) .setMaterialCount(8) - .setDrawCount(55)); + .setDrawCount(48) + .setJointCount(7, 3, 4)); CORRADE_COMPARE(state.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); CORRADE_COMPARE(state.materialCount(), 8); - CORRADE_COMPARE(state.drawCount(), 55); + CORRADE_COMPARE(state.drawCount(), 48); + CORRADE_COMPARE(state.jointCount(), 7); + CORRADE_COMPARE(state.perVertexJointCount(), 3); + CORRADE_COMPARE(state.secondaryPerVertexJointCount(), 4); while(!state.isLinkFinished()) Utility::System::sleep(100); @@ -1733,8 +2046,10 @@ void MeshVisualizerGLTest::constructUniformBuffersAsync2D() { MeshVisualizerGL2D shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); CORRADE_COMPARE(shader.materialCount(), 8); - CORRADE_COMPARE(shader.drawCount(), 55); - CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_COMPARE(shader.drawCount(), 48); + CORRADE_COMPARE(shader.jointCount(), 7); + CORRADE_COMPARE(shader.perVertexJointCount(), 3); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), 4); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) @@ -1755,10 +2070,14 @@ void MeshVisualizerGLTest::constructUniformBuffersAsync3D() { MeshVisualizerGL3D::CompileState state = MeshVisualizerGL3D::compile(MeshVisualizerGL3D::Configuration{} .setFlags(MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) .setMaterialCount(6) - .setDrawCount(28)); + .setDrawCount(24) + .setJointCount(7, 3, 4)); CORRADE_COMPARE(state.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); CORRADE_COMPARE(state.materialCount(), 6); - CORRADE_COMPARE(state.drawCount(), 28); + CORRADE_COMPARE(state.drawCount(), 24); + CORRADE_COMPARE(state.jointCount(), 7); + CORRADE_COMPARE(state.perVertexJointCount(), 3); + CORRADE_COMPARE(state.secondaryPerVertexJointCount(), 4); while(!state.isLinkFinished()) Utility::System::sleep(100); @@ -1766,8 +2085,10 @@ void MeshVisualizerGLTest::constructUniformBuffersAsync3D() { MeshVisualizerGL3D shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); CORRADE_COMPARE(state.materialCount(), 6); - CORRADE_COMPARE(state.drawCount(), 28); - CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_COMPARE(state.drawCount(), 24); + CORRADE_COMPARE(shader.jointCount(), 7); + CORRADE_COMPARE(shader.perVertexJointCount(), 3); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), 4); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) @@ -1782,7 +2103,10 @@ void MeshVisualizerGLTest::constructUniformBuffersAsync3D() { void MeshVisualizerGLTest::constructMove2D() { MeshVisualizerGL2D a{MeshVisualizerGL2D::Configuration{} - .setFlags(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader)}; + .setFlags(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) + /* Skinning properties tested in constructMoveUniformBuffers2D(), as + there we don't need to bother with ES2 */ + }; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -1802,7 +2126,10 @@ void MeshVisualizerGLTest::constructMove2D() { void MeshVisualizerGLTest::constructMove3D() { MeshVisualizerGL3D a{MeshVisualizerGL3D::Configuration{} - .setFlags(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader)}; + .setFlags(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) + /* Skinning properties tested in constructMoveUniformBuffers3D(), as + there we don't need to bother with ES2 */ + }; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -1825,12 +2152,15 @@ void MeshVisualizerGLTest::constructMoveUniformBuffers2D() { #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif MeshVisualizerGL2D a{MeshVisualizerGL2D::Configuration{} .setFlags(MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) .setMaterialCount(2) - .setDrawCount(5)}; + .setDrawCount(5) + .setJointCount(16, 4, 3)}; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -1841,6 +2171,9 @@ void MeshVisualizerGLTest::constructMoveUniformBuffers2D() { CORRADE_COMPARE(b.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); CORRADE_COMPARE(b.materialCount(), 2); CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_COMPARE(b.jointCount(), 16); + CORRADE_COMPARE(b.perVertexJointCount(), 4); + CORRADE_COMPARE(b.secondaryPerVertexJointCount(), 3); CORRADE_VERIFY(!a.id()); MeshVisualizerGL2D c{NoCreate}; @@ -1849,6 +2182,9 @@ void MeshVisualizerGLTest::constructMoveUniformBuffers2D() { CORRADE_COMPARE(c.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); CORRADE_COMPARE(c.materialCount(), 2); CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_COMPARE(c.jointCount(), 16); + CORRADE_COMPARE(c.perVertexJointCount(), 4); + CORRADE_COMPARE(c.secondaryPerVertexJointCount(), 3); CORRADE_VERIFY(!b.id()); } @@ -1856,12 +2192,15 @@ void MeshVisualizerGLTest::constructMoveUniformBuffers3D() { #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif MeshVisualizerGL3D a{MeshVisualizerGL3D::Configuration{} .setFlags(MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) .setMaterialCount(2) - .setDrawCount(5)}; + .setDrawCount(5) + .setJointCount(16, 4, 3)}; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -1872,6 +2211,9 @@ void MeshVisualizerGLTest::constructMoveUniformBuffers3D() { CORRADE_COMPARE(b.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); CORRADE_COMPARE(b.materialCount(), 2); CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_COMPARE(b.jointCount(), 16); + CORRADE_COMPARE(b.perVertexJointCount(), 4); + CORRADE_COMPARE(b.secondaryPerVertexJointCount(), 3); CORRADE_VERIFY(!a.id()); MeshVisualizerGL3D c{NoCreate}; @@ -1880,6 +2222,9 @@ void MeshVisualizerGLTest::constructMoveUniformBuffers3D() { CORRADE_COMPARE(c.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); CORRADE_COMPARE(c.materialCount(), 2); CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_COMPARE(c.jointCount(), 16); + CORRADE_COMPARE(c.perVertexJointCount(), 4); + CORRADE_COMPARE(c.secondaryPerVertexJointCount(), 3); CORRADE_VERIFY(!b.id()); } #endif @@ -1893,7 +2238,11 @@ void MeshVisualizerGLTest::constructInvalid2D() { std::ostringstream out; Error redirectError{&out}; MeshVisualizerGL2D{MeshVisualizerGL2D::Configuration{} - .setFlags(data.flags)}; + .setFlags(data.flags) + #ifndef MAGNUM_TARGET_GLES2 + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount) + #endif + }; CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL{}\n", data.message)); } @@ -1906,7 +2255,11 @@ void MeshVisualizerGLTest::constructInvalid3D() { std::ostringstream out; Error redirectError{&out}; MeshVisualizerGL3D{MeshVisualizerGL3D::Configuration{} - .setFlags(data.flags)}; + .setFlags(data.flags) + #ifndef MAGNUM_TARGET_GLES2 + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount) + #endif + }; CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL{}\n", data.message)); } @@ -1952,6 +2305,62 @@ void MeshVisualizerGLTest::constructUniformBuffersInvalid3D() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::setPerVertexJointCountInvalid2D() { + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* At least one visualization feature has to be enabled; disable GS so we + don't need to check for it on ES */ + MeshVisualizerGL2D a{MeshVisualizerGL2D::Configuration{} + .setFlags(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader)}; + MeshVisualizerGL2D b{MeshVisualizerGL2D::Configuration{} + .setFlags(MeshVisualizerGL2D::Flag::DynamicPerVertexJointCount|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) + .setJointCount(16, 3, 2)}; + + std::ostringstream out; + Error redirectError{&out}; + a.setPerVertexJointCount(3, 2); + b.setPerVertexJointCount(4); + b.setPerVertexJointCount(3, 3); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): the shader was not created with dynamic per-vertex joint count enabled\n" + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): expected at most 3 per-vertex joints, got 4\n" + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): expected at most 2 secondary per-vertex joints, got 3\n"); +} + +void MeshVisualizerGLTest::setPerVertexJointCountInvalid3D() { + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* At least one visualization feature has to be enabled; disable GS so we + don't need to check for it on ES */ + MeshVisualizerGL3D a{MeshVisualizerGL3D::Configuration{} + .setFlags(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader)}; + MeshVisualizerGL3D b{MeshVisualizerGL3D::Configuration{} + .setFlags(MeshVisualizerGL3D::Flag::DynamicPerVertexJointCount|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) + .setJointCount(16, 3, 2)}; + + std::ostringstream out; + Error redirectError{&out}; + a.setPerVertexJointCount(3, 2); + b.setPerVertexJointCount(4); + b.setPerVertexJointCount(3, 3); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): the shader was not created with dynamic per-vertex joint count enabled\n" + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): expected at most 3 per-vertex joints, got 4\n" + "Shaders::MeshVisualizerGL::setPerVertexJointCount(): expected at most 2 secondary per-vertex joints, got 3\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 void MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -1966,7 +2375,9 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D() { std::ostringstream out; Error redirectError{&out}; - shader.setTransformationProjectionMatrix({}) + shader + /* setPerVertexJointCount() works on both UBOs and classic */ + .setTransformationProjectionMatrix({}) .setTextureMatrix({}) .setTextureLayer({}) /* setViewportSize() works on both UBOs and classic */ @@ -1975,7 +2386,10 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D() { .setWireframeColor({}) .setWireframeWidth({}) .setColorMapTransformation({}, {}) - .setSmoothness({}); + .setSmoothness({}) + .setJointMatrices({}) + .setJointMatrix(0, {}) + .setPerInstanceJointCount(0); CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL2D::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" @@ -1985,7 +2399,10 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D() { "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was created with uniform buffers enabled\n" - "Shaders::MeshVisualizerGL2D::setSmoothness(): the shader was created with uniform buffers enabled\n"); + "Shaders::MeshVisualizerGL2D::setSmoothness(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::setJointMatrices(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::setJointMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setPerInstanceJointCount(): the shader was created with uniform buffers enabled\n"); } void MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D() { @@ -2001,7 +2418,9 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D() { std::ostringstream out; Error redirectError{&out}; - shader.setProjectionMatrix({}) + shader + /* setPerVertexJointCount() works on both UBOs and classic */ + .setProjectionMatrix({}) .setTransformationMatrix({}) .setTextureMatrix({}) .setTextureLayer({}) @@ -2011,7 +2430,10 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D() { .setWireframeColor({}) .setWireframeWidth({}) .setColorMapTransformation({}, {}) - .setSmoothness({}); + .setSmoothness({}) + .setJointMatrices({}) + .setJointMatrix(0, {}) + .setPerInstanceJointCount(0); CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL3D::setProjectionMatrix(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL3D::setTransformationMatrix(): the shader was created with uniform buffers enabled\n" @@ -2022,7 +2444,10 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D() { "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was created with uniform buffers enabled\n" - "Shaders::MeshVisualizerGL3D::setSmoothness(): the shader was created with uniform buffers enabled\n"); + "Shaders::MeshVisualizerGL3D::setSmoothness(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setJointMatrices(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setJointMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setPerInstanceJointCount(): the shader was created with uniform buffers enabled\n"); out.str({}); @@ -2055,6 +2480,8 @@ void MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled2D() { .bindTextureTransformationBuffer(buffer, 0, 16) .bindMaterialBuffer(buffer) .bindMaterialBuffer(buffer, 0, 16) + .bindJointBuffer(buffer) + .bindJointBuffer(buffer, 0, 16) .setDrawOffset(0); CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" @@ -2065,6 +2492,8 @@ void MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled2D() { "Shaders::MeshVisualizerGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindJointBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindJointBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } @@ -2087,6 +2516,8 @@ void MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled3D() { .bindTextureTransformationBuffer(buffer, 0, 16) .bindMaterialBuffer(buffer) .bindMaterialBuffer(buffer, 0, 16) + .bindJointBuffer(buffer) + .bindJointBuffer(buffer, 0, 16) .setDrawOffset(0); CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" @@ -2099,6 +2530,8 @@ void MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled3D() { "Shaders::MeshVisualizerGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindJointBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindJointBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } #endif @@ -2431,6 +2864,54 @@ void MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::setWrongJointCountOrId2D() { + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* At least one visualization feature has to be enabled; disable GS so we + don't need to check for it on ES */ + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Configuration{} + .setFlags(MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) + .setJointCount(5, 1)}; + + std::ostringstream out; + Error redirectError{&out}; + shader.setJointMatrices({Matrix3{}}); + shader.setJointMatrix(5, Matrix3{}); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL2D::setJointMatrices(): expected 5 items but got 1\n" + "Shaders::MeshVisualizerGL2D::setJointMatrix(): joint ID 5 is out of bounds for 5 joints\n"); +} + +void MeshVisualizerGLTest::setWrongJointCountOrId3D() { + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* At least one visualization feature has to be enabled; disable GS so we + don't need to check for it on ES */ + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Configuration{} + .setFlags(MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) + .setJointCount(5, 1)}; + + std::ostringstream out; + Error redirectError{&out}; + shader.setJointMatrices({Matrix4{}}); + shader.setJointMatrix(5, Matrix4{}); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL3D::setJointMatrices(): expected 5 items but got 1\n" + "Shaders::MeshVisualizerGL3D::setJointMatrix(): joint ID 5 is out of bounds for 5 joints\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 void MeshVisualizerGLTest::setWrongDrawOffset2D() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -4250,11 +4731,16 @@ template void MeshVisualizerGLTest::renderTangent } #endif -template void MeshVisualizerGLTest::renderInstanced2D() { - auto&& data = RenderInstancedData2D[testCaseInstanceId()]; +#ifndef MAGNUM_TARGET_GLES2 +template void MeshVisualizerGLTest::renderSkinningWireframe2D() { + auto&& data = RenderSkinningData[testCaseInstanceId()]; setTestCaseDescription(data.name); - #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { setTestCaseTemplateName("Flag::UniformBuffers"); @@ -4262,68 +4748,335 @@ template void MeshVisualizerGLTest::renderInstanc if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - } - #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { - #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); - #else - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); #endif } - #endif - #ifndef MAGNUM_TARGET_GLES - if((data.flags & MeshVisualizerGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); - #endif + /* Same as in FlatGLTest::renderSkinning2D(), except that the shared + vertices are duplicated in order to work with GS-less wireframe */ + struct Vertex { + Vector2 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Top right corner gets moved to the right and up, top left just up, + bottom right just right, bottom left corner gets slightly scaled. + + 5--4 1 + | / /| + |/ / | + 3 2--0 */ + {{ 1.0f, -1.0f}, {0, 2, 0}, {0.25f, 0.0f, 0.75f}}, + {{ 1.0f, 1.0f}, {1, 0, 0}, {0.5f, 0.5f, 0.0f}}, + {{-1.0f, -1.0f}, {3, 4, 4}, {0.5f, 0.25f, 0.25f}}, + {{-1.0f, -1.0f}, {3, 4, 4}, {0.5f, 0.25f, 0.25f}}, + {{ 1.0f, 1.0f}, {1, 0, 0}, {0.5f, 0.5f, 0.0f}}, + {{-1.0f, 1.0f}, {1, 0, 4}, {1.0f, 0.0f, 0.0f}}, + }; - #ifndef MAGNUM_TARGET_GLES - if((data.flags & MeshVisualizerGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); - #endif + Matrix3 jointMatrices[]{ + Matrix3::translation(Vector2::xAxis(0.5f)), + Matrix3::translation(Vector2::yAxis(0.5f)), + Matrix3{Math::ZeroInit}, + Matrix3::scaling(Vector2{2.0f}), + Matrix3{Math::IdentityInit}, + }; - #ifndef MAGNUM_TARGET_GLES2 - /* Interestingly enough, on SwiftShader it only fails in case UBOs are - used. Dafuq is this buggy crap?! */ - if(data.flags & MeshVisualizerGL2D::Flag::VertexId && !GL::Context::current().isExtensionSupported()) - CORRADE_SKIP("gl_VertexID not supported"); - #endif + GL::Buffer buffer{vertices}; - #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); - #elif defined(MAGNUM_TARGET_GLES2) - #ifndef MAGNUM_TARGET_WEBGL - if(!GL::Context::current().isExtensionSupported() && - !GL::Context::current().isExtensionSupported() && - !GL::Context::current().isExtensionSupported()) - CORRADE_SKIP("GL_{ANGLE,EXT,NV}_instanced_arrays is not supported"); - #else - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ANGLE::instanced_arrays::string() << "is not supported."); - #endif - #endif + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(6); + mesh.addVertexBuffer(buffer, 0, sizeof(Vertex), GL::DynamicAttribute{MeshVisualizerGL2D::Position{}}); + for(auto&& attribute: data.attributes) + mesh.addVertexBuffer(buffer, 2*4 + attribute.first(), sizeof(Vertex), attribute.second()); - Trade::MeshData circleData = Primitives::circle2DSolid(8, Primitives::Circle2DFlag::TextureCoordinates); - /* For a GS-less wireframe we have to deindex the mesh (but first turn the - triangle fan into an indexed mesh) */ - if(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) - circleData = MeshTools::duplicate(MeshTools::generateIndices(circleData)); - GL::Mesh circle = MeshTools::compile(circleData); + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Configuration{} + .setFlags(data.flags2D|flag|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + if(data.setDynamicPerVertexJointCount) + shader.setPerVertexJointCount(data.dynamicPerVertexJointCount, data.dynamicSecondaryPerVertexJointCount); - /* Three circles, each in a different location */ - struct { - Matrix3 transformation; - Vector3 textureOffsetLayer; - UnsignedInt objectId; - } instanceData[] { - {Matrix3::translation({-1.25f, -1.25f}), - /* 6 gets added to objectId, wrapping it around to 0, making it + if(flag == MeshVisualizerGL2D::Flag{}) { + if(data.setJointMatricesOneByOne) { + shader + .setJointMatrix(0, jointMatrices[0]) + .setJointMatrix(1, jointMatrices[1]) + .setJointMatrix(2, jointMatrices[2]) + .setJointMatrix(3, jointMatrices[3]) + .setJointMatrix(4, jointMatrices[4]); + } else if(data.setJointMatrices) + shader.setJointMatrices(jointMatrices); + shader + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.5f})) + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .draw(mesh); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.5f})) + }}; + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[0] : Matrix3{}), + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[1] : Matrix3{}), + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[2] : Matrix3{}), + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[3] : Matrix3{}), + TransformationUniform2D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[4] : Matrix3{}), + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join({_testDir, "MeshVisualizerTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); +} + +template void MeshVisualizerGLTest::renderSkinningWireframe3D() { + auto&& data = RenderSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + + /* Same as in FlatGLTest::renderSkinning3D(), except that the shared + vertices are duplicated in order to work with GS-less wireframe */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Top right corner gets moved to the right and up, top left just up, + bottom right just right, bottom left corner gets slightly scaled. + + 5--4 1 + | / /| + |/ / | + 3 2--0 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 2, 0}, {0.25f, 0.0f, 0.75f}}, + {{ 1.0f, 1.0f, 0.0f}, {1, 0, 0}, {0.5f, 0.5f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {3, 4, 4}, {0.5f, 0.25f, 0.25f}}, + {{-1.0f, -1.0f, 0.0f}, {3, 4, 4}, {0.5f, 0.25f, 0.25f}}, + {{ 1.0f, 1.0f, 0.0f}, {1, 0, 0}, {0.5f, 0.5f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}, {1, 0, 4}, {1.0f, 0.0f, 0.0f}}, + }; + + Matrix4 jointMatrices[]{ + Matrix4::translation(Vector3::xAxis(0.5f)), + Matrix4::translation(Vector3::yAxis(0.5f)), + Matrix4{Math::ZeroInit}, + Matrix4::scaling(Vector3{2.0f}), + Matrix4{Math::IdentityInit}, + }; + + GL::Buffer buffer{vertices}; + + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(6); + mesh.addVertexBuffer(buffer, 0, sizeof(Vertex), GL::DynamicAttribute{MeshVisualizerGL3D::Position{}}); + for(auto&& attribute: data.attributes) + mesh.addVertexBuffer(buffer, 3*4 + attribute.first(), sizeof(Vertex), attribute.second()); + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Configuration{} + .setFlags(data.flags3D|flag|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + if(data.setDynamicPerVertexJointCount) + shader.setPerVertexJointCount(data.dynamicPerVertexJointCount, data.dynamicSecondaryPerVertexJointCount); + + if(flag == MeshVisualizerGL3D::Flag{}) { + if(data.setJointMatricesOneByOne) { + shader + .setJointMatrix(0, jointMatrices[0]) + .setJointMatrix(1, jointMatrices[1]) + .setJointMatrix(2, jointMatrices[2]) + .setJointMatrix(3, jointMatrices[3]) + .setJointMatrix(4, jointMatrices[4]); + } else if(data.setJointMatrices) + shader.setJointMatrices(jointMatrices); + shader + .setTransformationMatrix(Matrix4::scaling(Vector3{0.5f})) + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .draw(mesh); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::scaling(Vector3{0.5f})) + }}; + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[0] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[1] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[2] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[3] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[4] : Matrix4{}), + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join({_testDir, "MeshVisualizerTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); +} +#endif + +template void MeshVisualizerGLTest::renderInstanced2D() { + auto&& data = RenderInstancedData2D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL2D::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + /* Interestingly enough, on SwiftShader it only fails in case UBOs are + used. Dafuq is this buggy crap?! */ + if(data.flags & MeshVisualizerGL2D::Flag::VertexId && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("gl_VertexID not supported"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); + #elif defined(MAGNUM_TARGET_GLES2) + #ifndef MAGNUM_TARGET_WEBGL + if(!GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported() && + !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("GL_{ANGLE,EXT,NV}_instanced_arrays is not supported"); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::instanced_arrays::string() << "is not supported."); + #endif + #endif + + Trade::MeshData circleData = Primitives::circle2DSolid(8, Primitives::Circle2DFlag::TextureCoordinates); + /* For a GS-less wireframe we have to deindex the mesh (but first turn the + triangle fan into an indexed mesh) */ + if(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) + circleData = MeshTools::duplicate(MeshTools::generateIndices(circleData)); + GL::Mesh circle = MeshTools::compile(circleData); + + /* Three circles, each in a different location */ + struct { + Matrix3 transformation; + Vector3 textureOffsetLayer; + UnsignedInt objectId; + } instanceData[] { + {Matrix3::translation({-1.25f, -1.25f}), + /* 6 gets added to objectId, wrapping it around to 0, making it visually close to the multidraw test */ {0.0f, 0.0f, 0.0f}, 6}, {Matrix3::translation({ 1.25f, -1.25f}), @@ -4799,76 +5552,351 @@ template void MeshVisualizerGLTest::renderInstanc } #ifndef MAGNUM_TARGET_GLES2 -void MeshVisualizerGLTest::renderMulti2D() { - auto&& data = RenderMultiData2D[testCaseInstanceId()]; - setTestCaseDescription(data.name); - +template void MeshVisualizerGLTest::renderInstancedSkinningWireframe2D() { #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif - #ifndef MAGNUM_TARGET_WEBGL - if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); - #else - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); #endif - } - #endif - if(data.flags >= MeshVisualizerGL2D::Flag::MultiDraw) { - #ifndef MAGNUM_TARGET_GLES - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); - #elif !defined(MAGNUM_TARGET_WEBGL) - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); - #else - if(!GL::Context::current().isExtensionSupported()) - CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); #endif } - #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) - if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) - CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); - #endif + /* Similarly to renderSkinning2D() tests just 2D movement, differently and + clearly distinguisable for each instance */ + struct Vertex { + Vector2 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 5--4 1 + | / /| + |/ / | + 3 2--0 */ + {{ 1.0f, -1.0f}, {0, 0, 0}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 1.0f}, {0, 3, 0}, {0.0f, 1.0f, 0.0f}}, + {{-1.0f, -1.0f}, {0, 0, 1}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, -1.0f}, {0, 0, 1}, {0.0f, 0.0f, 1.0f}}, + {{ 1.0f, 1.0f}, {0, 3, 0}, {0.0f, 1.0f, 0.0f}}, + {{-1.0f, 1.0f}, {4, 0, 0}, {1.0f, 0.0f, 0.0f}}, + }; - MeshVisualizerGL2D shader{MeshVisualizerGL2D::Configuration{} - .setFlags(MeshVisualizerGL2D::Flag::UniformBuffers|data.flags) - .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; - shader.setViewportSize(Vector2{RenderSize}); - if(data.flags & (MeshVisualizerGL2D::Flag::VertexId|MeshVisualizerGL2D::Flag::ObjectId)) - shader.bindColorMapTexture(_colorMapTexture); + Matrix3 instanceTransformations[]{ + Matrix3::translation({-1.5f, -1.5f}), + Matrix3::translation({ 1.5f, -1.5f}), + Matrix3::translation({ 0.0f, 1.5f}) + }; - GL::Texture2D objectIdTexture{NoCreate}; - GL::Texture2DArray objectIdTextureArray{NoCreate}; - if(data.flags >= MeshVisualizerGL2D::Flag::ObjectIdTexture) { - /* This should match transformation done for the diffuse/normal - texture */ - if(data.flags & MeshVisualizerGL2D::Flag::TextureArrays) { - /* Each slice has half height, second slice has the data in the - right half */ - const UnsignedShort imageData[]{ - 5, 0, 0, 0, - 0, 5, 0, 0, + Matrix3 jointMatrices[]{ + /* First instance moves bottom left corner */ + {}, + Matrix3::translation({-0.5f, -0.5f}), + {}, + {}, + {}, + + /* Second instance moves bottom right corner */ + Matrix3::translation({0.5f, -0.5f}), + {}, + {}, + {}, + {}, + + /* Third instance moves both top corners */ + {}, + {}, + {}, + Matrix3::translation({0.5f, 0.5f}), + Matrix3::translation({-0.5f, 0.5f}), + }; - 0, 0, 3, 0, - 0, 0, 0, 3, + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(6) + .addVertexBuffer(GL::Buffer{vertices}, 0, + MeshVisualizerGL2D::Position{}, + MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::Three}, + MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::Three}) + .addVertexBufferInstanced(GL::Buffer{instanceTransformations}, 1, 0, + MeshVisualizerGL2D::TransformationMatrix{}) + .setInstanceCount(3); - 1, 0, 0, 0, - 0, 1, 0, 0 - }; - ImageView3D image{PixelFormat::R16UI, {4, 2, 3}, imageData}; + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Configuration{} + .setFlags(MeshVisualizerGL2D::Flag::InstancedTransformation|flag|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) + .setJointCount(15, 3, 0)}; - objectIdTextureArray = GL::Texture2DArray{}; - objectIdTextureArray.setMinificationFilter(GL::SamplerFilter::Nearest) - .setMagnificationFilter(GL::SamplerFilter::Nearest) + if(flag == MeshVisualizerGL2D::Flag{}) { + shader + .setJointMatrices(jointMatrices) + .setPerInstanceJointCount(5) + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.3f})) + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .draw(mesh); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.3f})) + }}; + TransformationUniform2D jointMatricesUniformData[Containers::arraySize(jointMatrices)]; + Utility::copy( /* This API is so powerful it should be outlawed!! */ + Containers::arrayCast<2, const Vector3>(Containers::stridedArrayView(jointMatrices)), Containers::arrayCast<2, Vector4>(Containers::stridedArrayView(jointMatricesUniformData).slice(&TransformationUniform2D::transformationMatrix)).slice(&Vector4::xyz)); + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, + jointMatricesUniformData + }; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + .setPerInstanceJointCount(5) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "MeshVisualizerTestFiles/skinning-instanced.tga"), + /* SwiftShader has minor differences in the output */ + (DebugTools::CompareImageToFile{_manager, 0.68f, 0.005f})); +} + +template void MeshVisualizerGLTest::renderInstancedSkinningWireframe3D() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + + /* Similarly to renderSkinning3D() tests just 2D movement, differently and + clearly distinguisable for each instance */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 5--4 1 + | / /| + |/ / | + 3 2--0 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 0, 0}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0, 3, 0}, {0.0f, 1.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0, 0, 1}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0, 0, 1}, {0.0f, 0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0, 3, 0}, {0.0f, 1.0f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}, {4, 0, 0}, {1.0f, 0.0f, 0.0f}}, + }; + + Matrix4 instanceTransformations[]{ + Matrix4::translation({-1.5f, -1.5f, 0.0f}), + Matrix4::translation({ 1.5f, -1.5f, 0.0f}), + Matrix4::translation({ 0.0f, 1.5f, 0.0f}) + }; + + Matrix4 jointMatrices[]{ + /* First instance moves bottom left corner */ + {}, + Matrix4::translation({-0.5f, -0.5f, 0.0f}), + {}, + {}, + {}, + + /* Second instance moves bottom right corner */ + Matrix4::translation({0.5f, -0.5f, 0.0f}), + {}, + {}, + {}, + {}, + + /* Third instance moves both top corners */ + {}, + {}, + {}, + Matrix4::translation({0.5f, 0.5f, 0.0f}), + Matrix4::translation({-0.5f, 0.5f, 0.0f}), + }; + + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(6) + .addVertexBuffer(GL::Buffer{vertices}, 0, + MeshVisualizerGL3D::Position{}, + MeshVisualizerGL3D::JointIds{MeshVisualizerGL3D::JointIds::Components::Three}, + MeshVisualizerGL3D::Weights{MeshVisualizerGL3D::Weights::Components::Three}) + .addVertexBufferInstanced(GL::Buffer{instanceTransformations}, 1, 0, + MeshVisualizerGL3D::TransformationMatrix{}) + .setInstanceCount(3); + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Configuration{} + .setFlags(MeshVisualizerGL3D::Flag::InstancedTransformation|flag|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) + .setJointCount(15, 3, 0)}; + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader + .setJointMatrices(jointMatrices) + .setPerInstanceJointCount(5) + .setTransformationMatrix(Matrix4::scaling(Vector3{0.3f})) + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .draw(mesh); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::scaling(Vector3{0.3f})) + }}; + TransformationUniform3D jointMatricesUniformData[Containers::arraySize(jointMatrices)]; + Utility::copy(jointMatrices, Containers::stridedArrayView(jointMatricesUniformData).slice(&TransformationUniform3D::transformationMatrix)); + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, + jointMatricesUniformData + }; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + .setPerInstanceJointCount(5) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "MeshVisualizerTestFiles/skinning-instanced.tga"), + /* SwiftShader has minor differences in the output */ + (DebugTools::CompareImageToFile{_manager, 0.68f, 0.005f})); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::renderMulti2D() { + auto&& data = RenderMultiData2D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + } + #endif + + if(data.flags >= MeshVisualizerGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Configuration{} + .setFlags(MeshVisualizerGL2D::Flag::UniformBuffers|data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount)}; + shader.setViewportSize(Vector2{RenderSize}); + if(data.flags & (MeshVisualizerGL2D::Flag::VertexId|MeshVisualizerGL2D::Flag::ObjectId)) + shader.bindColorMapTexture(_colorMapTexture); + + GL::Texture2D objectIdTexture{NoCreate}; + GL::Texture2DArray objectIdTextureArray{NoCreate}; + if(data.flags >= MeshVisualizerGL2D::Flag::ObjectIdTexture) { + /* This should match transformation done for the diffuse/normal + texture */ + if(data.flags & MeshVisualizerGL2D::Flag::TextureArrays) { + /* Each slice has half height, second slice has the data in the + right half */ + const UnsignedShort imageData[]{ + 5, 0, 0, 0, + 0, 5, 0, 0, + + 0, 0, 3, 0, + 0, 0, 0, 3, + + 1, 0, 0, 0, + 0, 1, 0, 0 + }; + ImageView3D image{PixelFormat::R16UI, {4, 2, 3}, imageData}; + + objectIdTextureArray = GL::Texture2DArray{}; + objectIdTextureArray.setMinificationFilter(GL::SamplerFilter::Nearest) + .setMagnificationFilter(GL::SamplerFilter::Nearest) .setWrapping(GL::SamplerWrapping::ClampToEdge) .setStorage(1, GL::TextureFormat::R16UI, image.size()) .setSubImage(0, {}, image); @@ -5455,6 +6483,476 @@ void MeshVisualizerGLTest::renderMulti3D() { Utility::Path::join({_testDir, "MeshVisualizerTestFiles", data.expected}), (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); } + +void MeshVisualizerGLTest::renderMultiSkinningWireframe2D() { + auto&& data = RenderMultiSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags2D >= MeshVisualizerGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Configuration{} + .setFlags(MeshVisualizerGL2D::Flag::UniformBuffers|data.flags2D|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader) + .setDrawCount(data.drawCount) + .setMaterialCount(data.materialCount) + .setJointCount(data.jointCount, 2, 0)}; + + /* Similarly to renderSkinning2D() tests just 2D movement, differently and + clearly distinguisable for each draw */ + struct Vertex { + Vector2 position; + UnsignedInt jointIds[2]; + Float weights[2]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 5--4 1 7 11--10 + | / /| /| | / + |/ / | / | |/ + 3 2--0 8--6 9 */ + {{ 1.0f, -1.0f}, {0, 0}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f}, {0, 2}, {0.0f, 1.0f}}, + {{-1.0f, -1.0f}, {1, 2}, {1.0f, 0.0f}}, + {{-1.0f, -1.0f}, {1, 2}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f}, {0, 2}, {0.0f, 1.0f}}, + {{-1.0f, 1.0f}, {0, 3}, {0.0f, 1.0f}}, + + {{ 1.0f, -1.0f}, {0, 3}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f}, {2, 1}, {1.0f, 0.0f}}, + {{-1.0f, -1.0f}, {0, 0}, {1.0f, 0.0f}}, + + {{-1.0f, -1.0f}, {0, 1}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f}, {1, 0}, {1.0f, 0.0f}}, + {{-1.0f, 1.0f}, {2, 2}, {0.5f, 0.5f}} + }; + + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(12) + .addVertexBuffer(GL::Buffer{vertices}, 0, + MeshVisualizerGL2D::Position{}, + MeshVisualizerGL2D::JointIds{MeshVisualizerGL2D::JointIds::Components::Two}, + MeshVisualizerGL2D::Weights{MeshVisualizerGL2D::Weights::Components::Two}); + GL::MeshView square{mesh}; + square.setCount(6); + GL::MeshView triangle1{mesh}; + triangle1.setCount(3) + .setBaseVertex(6); + GL::MeshView triangle2{mesh}; + triangle2.setCount(3) + .setBaseVertex(9); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }};; + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + 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] = MeshVisualizerMaterialUniform{} + .setColor(0xffffcc_rgbf) + .setWireframeColor(0xcc0000_rgbf); + materialData[1*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xccffff_rgbf) + .setWireframeColor(0x0000cc_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::scaling(Vector2{0.3f})* + Matrix3::translation({ 0.0f, -1.5f})); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::scaling(Vector2{0.3f})* + Matrix3::translation({ 1.5f, 1.5f})); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::scaling(Vector2{0.3f})* + Matrix3::translation({-1.5f, 1.5f})); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array jointData{Math::max(2*data.uniformIncrement + 4, 10u)}; + /* First draw moves both bottom corners */ + jointData[Math::max(0*data.uniformIncrement, 0u) + 0] = TransformationUniform2D{} + .setTransformationMatrix(Matrix3::translation({ 0.5f, -0.5f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 1] = TransformationUniform2D{} + .setTransformationMatrix(Matrix3::translation({-0.5f, -0.5f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 2] = TransformationUniform2D{}; + jointData[Math::max(0*data.uniformIncrement, 0u) + 3] = TransformationUniform2D{}; + /* Second draw overlaps with the first with two identity matrices (unless + the padding prevents that); moves top right corner */ + jointData[Math::max(1*data.uniformIncrement, 2u) + 0] = TransformationUniform2D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 1] = TransformationUniform2D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 2] = TransformationUniform2D{} + .setTransformationMatrix(Matrix3::translation({ 0.5f, 0.5f})); + jointData[Math::max(1*data.uniformIncrement, 2u) + 3] = TransformationUniform2D{}; + /* Third draw moves top left corner */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 0] = TransformationUniform2D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 1] = TransformationUniform2D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 2] = TransformationUniform2D{} + .setTransformationMatrix(Matrix3::translation({-0.5f, 0.5f})); + /* This one is unused but has to be here in order to be able to bind the + last three-component part while JOINT_COUNT is set to 4 */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 3] = TransformationUniform2D{}; + GL::Buffer jointUniform{GL::Buffer::TargetHint::Uniform, + jointData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material / joint offsets are zero if we have single draw, as those are + done with UBO offset bindings instead */ + drawData[0*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + /* Overlaps with the first joint set with two matrices, unless the + padding in the single-draw case prevents that */ + .setJointOffset(data.drawCount == 1 ? 0 : 2); + drawData[2*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 6); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindJointBuffer(jointUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform2D), + data.jointCount*sizeof(TransformationUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindJointBuffer(jointUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform2D), + data.jointCount*sizeof(TransformationUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(triangle1); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindJointBuffer(jointUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform2D), + data.jointCount*sizeof(TransformationUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(triangle2); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindJointBuffer(jointUniform) + .bindDrawBuffer(drawUniform); + + if(data.flags2D >= MeshVisualizerGL2D::Flag::MultiDraw) + shader.draw({square, triangle1, triangle2}); + else { + shader.setDrawOffset(0) + .draw(square); + shader.setDrawOffset(1) + .draw(triangle1); + shader.setDrawOffset(2) + .draw(triangle2); + } + } + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "MeshVisualizerTestFiles/skinning-multi.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +void MeshVisualizerGLTest::renderMultiSkinningWireframe3D() { + auto&& data = RenderMultiSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags3D >= MeshVisualizerGL3D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Configuration{} + .setFlags(MeshVisualizerGL3D::Flag::UniformBuffers|data.flags3D|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader) + .setDrawCount(data.drawCount) + .setMaterialCount(data.materialCount) + .setJointCount(data.jointCount, 2, 0)}; + + /* Similarly to renderSkinning3D() tests just 2D movement, differently and + clearly distinguisable for each draw */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[2]; + Float weights[2]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 5--4 1 7 11--10 + | / /| /| | / + |/ / | / | |/ + 3 2--0 8--6 9 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 0}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0, 2}, {0.0f, 1.0f}}, + {{-1.0f, -1.0f, 0.0f}, {1, 2}, {1.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {1, 2}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0, 2}, {0.0f, 1.0f}}, + {{-1.0f, 1.0f, 0.0f}, {0, 3}, {0.0f, 1.0f}}, + + {{ 1.0f, -1.0f, 0.0f}, {0, 3}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {2, 1}, {1.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0, 0}, {1.0f, 0.0f}}, + + {{-1.0f, -1.0f, 0.0f}, {0, 1}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {1, 0}, {1.0f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}, {2, 2}, {0.5f, 0.5f}} + }; + + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(12) + .addVertexBuffer(GL::Buffer{vertices}, 0, + MeshVisualizerGL3D::Position{}, + MeshVisualizerGL3D::JointIds{MeshVisualizerGL3D::JointIds::Components::Two}, + MeshVisualizerGL3D::Weights{MeshVisualizerGL3D::Weights::Components::Two}); + GL::MeshView square{mesh}; + square.setCount(6); + GL::MeshView triangle1{mesh}; + triangle1.setCount(3) + .setBaseVertex(6); + GL::MeshView triangle2{mesh}; + triangle2.setCount(3) + .setBaseVertex(9); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }};; + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + 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] = MeshVisualizerMaterialUniform{} + .setColor(0xffffcc_rgbf) + .setWireframeColor(0xcc0000_rgbf); + materialData[1*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xccffff_rgbf) + .setWireframeColor(0x0000cc_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationData{2*data.uniformIncrement + 1}; + transformationData[0*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({ 0.0f, -1.5f, 0.0f})); + transformationData[1*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({ 1.5f, 1.5f, 0.0f})); + transformationData[2*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({-1.5f, 1.5f, 0.0f})); + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, transformationData}; + + Containers::Array jointData{Math::max(2*data.uniformIncrement + 4, 10u)}; + /* First draw moves both bottom corners */ + jointData[Math::max(0*data.uniformIncrement, 0u) + 0] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({ 0.5f, -0.5f, 0.0f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 1] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({-0.5f, -0.5f, 0.0f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 2] = TransformationUniform3D{}; + jointData[Math::max(0*data.uniformIncrement, 0u) + 3] = TransformationUniform3D{}; + /* Second draw overlaps with the first with two identity matrices (unless + the padding prevents that); moves top right corner */ + jointData[Math::max(1*data.uniformIncrement, 2u) + 0] = TransformationUniform3D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 1] = TransformationUniform3D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 2] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({ 0.5f, 0.5f, 0.0f})); + jointData[Math::max(1*data.uniformIncrement, 2u) + 3] = TransformationUniform3D{}; + /* Third draw moves top left corner */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 0] = TransformationUniform3D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 1] = TransformationUniform3D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 2] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({-0.5f, 0.5f, 0.0f})); + /* This one is unused but has to be here in order to be able to bind the + last three-component part while JOINT_COUNT is set to 4 */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 3] = TransformationUniform3D{}; + GL::Buffer jointUniform{GL::Buffer::TargetHint::Uniform, + jointData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material / joint offsets are zero if we have single draw, as those are + done with UBO offset bindings instead */ + drawData[0*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + /* Overlaps with the first joint set with two matrices, unless the + padding in the single-draw case prevents that */ + .setJointOffset(data.drawCount == 1 ? 0 : 2); + drawData[2*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 6); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + shader.bindProjectionBuffer(projectionUniform); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindJointBuffer(jointUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindJointBuffer(jointUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(triangle1); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindJointBuffer(jointUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(triangle2); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationBuffer(transformationUniform) + .bindJointBuffer(jointUniform) + .bindDrawBuffer(drawUniform); + + if(data.flags3D >= MeshVisualizerGL3D::Flag::MultiDraw) + shader.draw({square, triangle1, triangle2}); + else { + shader.setDrawOffset(0) + .draw(square); + shader.setDrawOffset(1) + .draw(triangle1); + shader.setDrawOffset(2) + .draw(triangle2); + } + } + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "MeshVisualizerTestFiles/skinning-multi.tga"), + (DebugTools::CompareImageToFile{_manager})); +} #endif }}}} diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp index f3747dcd1..b87be32a2 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "Magnum/Shaders/MeshVisualizerGL.h" @@ -36,6 +37,11 @@ namespace Magnum { namespace Shaders { namespace Test { namespace { struct MeshVisualizerGL_Test: TestSuite::Tester { explicit MeshVisualizerGL_Test(); + #ifndef MAGNUM_TARGET_GLES2 + void configurationSetJointCountInvalid2D(); + void configurationSetJointCountInvalid3D(); + #endif + void constructNoCreate2D(); void constructNoCreate3D(); @@ -54,7 +60,38 @@ struct MeshVisualizerGL_Test: TestSuite::Tester { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +const struct { + const char* name; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; + const char* message; +} ConfigurationSetJointCountInvalidData[] { + {"per-vertex joint count too large", + 10, 5, 0, + "expected at most 4 per-vertex joints, got 5"}, + {"secondary per-vertex joint count too large", + 10, 0, 5, + "expected at most 4 secondary per-vertex joints, got 5"}, + {"joint count but no per-vertex joint count", + 10, 0, 0, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, + {"per-vertex joint count but no joint count", + 0, 2, 0, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, + {"secondary per-vertex joint count but no joint count", + 0, 0, 3, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, +}; +#endif + MeshVisualizerGL_Test::MeshVisualizerGL_Test() { + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &MeshVisualizerGL_Test::configurationSetJointCountInvalid2D, + &MeshVisualizerGL_Test::configurationSetJointCountInvalid3D}, + Containers::arraySize(ConfigurationSetJointCountInvalidData)); + #endif + addTests({ &MeshVisualizerGL_Test::constructNoCreate2D, &MeshVisualizerGL_Test::constructNoCreate3D, @@ -75,6 +112,36 @@ MeshVisualizerGL_Test::MeshVisualizerGL_Test() { }); } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGL_Test::configurationSetJointCountInvalid2D() { + auto&& data = ConfigurationSetJointCountInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + CORRADE_SKIP_IF_NO_ASSERT(); + + MeshVisualizerGL2D::Configuration configuration; + + std::ostringstream out; + Error redirectError{&out}; + configuration.setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount); + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL2D::Configuration::setJointCount(): {}\n", data.message)); +} + +void MeshVisualizerGL_Test::configurationSetJointCountInvalid3D() { + auto&& data = ConfigurationSetJointCountInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + CORRADE_SKIP_IF_NO_ASSERT(); + + MeshVisualizerGL3D::Configuration configuration; + + std::ostringstream out; + Error redirectError{&out}; + configuration.setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount); + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL3D::Configuration::setJointCount(): {}\n", data.message)); +} +#endif + void MeshVisualizerGL_Test::constructNoCreate2D() { { MeshVisualizerGL2D shader{NoCreate}; diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp index d4fa65f21..76afafbe0 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp @@ -105,6 +105,10 @@ void MeshVisualizerTest::drawUniform2DConstructDefault() { CORRADE_COMPARE(b.materialId, 0); CORRADE_COMPARE(a.objectId, 0); CORRADE_COMPARE(b.objectId, 0); + CORRADE_COMPARE(a.jointOffset, 0); + CORRADE_COMPARE(b.jointOffset, 0); + CORRADE_COMPARE(a.perInstanceJointCount, 0); + CORRADE_COMPARE(b.perInstanceJointCount, 0); constexpr MeshVisualizerDrawUniform2D ca; constexpr MeshVisualizerDrawUniform2D cb{DefaultInit}; @@ -112,6 +116,10 @@ void MeshVisualizerTest::drawUniform2DConstructDefault() { CORRADE_COMPARE(cb.materialId, 0); CORRADE_COMPARE(ca.objectId, 0); CORRADE_COMPARE(cb.objectId, 0); + CORRADE_COMPARE(ca.jointOffset, 0); + CORRADE_COMPARE(cb.jointOffset, 0); + CORRADE_COMPARE(ca.perInstanceJointCount, 0); + CORRADE_COMPARE(cb.perInstanceJointCount, 0); CORRADE_VERIFY(std::is_nothrow_default_constructible::value); CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -125,6 +133,7 @@ void MeshVisualizerTest::drawUniform2DConstructNoInit() { MeshVisualizerDrawUniform2D a; a.materialId = 73; a.objectId = 7; + a.perInstanceJointCount = 9; new(&a) MeshVisualizerDrawUniform2D{NoInit}; { @@ -136,6 +145,7 @@ void MeshVisualizerTest::drawUniform2DConstructNoInit() { #endif CORRADE_COMPARE(a.materialId, 73); CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.perInstanceJointCount, 9); } CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -147,17 +157,29 @@ void MeshVisualizerTest::drawUniform2DConstructNoInit() { void MeshVisualizerTest::drawUniform2DSetters() { MeshVisualizerDrawUniform2D a; a.setMaterialId(73) - .setObjectId(7); + .setObjectId(7) + .setJointOffset(6) + .setPerInstanceJointCount(8); CORRADE_COMPARE(a.materialId, 73); CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.jointOffset, 6); + CORRADE_COMPARE(a.perInstanceJointCount, 8); } void MeshVisualizerTest::drawUniform2DMaterialIdPacking() { MeshVisualizerDrawUniform2D a; - a.setMaterialId(13765); + a.setMaterialId(13765) + /* second 16 bits unused */ + .setJointOffset(13767) + .setPerInstanceJointCount(63574); /* 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); + /* second 16 bits unused */ + + /* jointOffset in the low, perInstanceJointCount in the high */ + CORRADE_COMPARE(reinterpret_cast(&a)[2] & 0xffff, 13767); + CORRADE_COMPARE((reinterpret_cast(&a)[2] >> 16) & 0xffff, 63574); } void MeshVisualizerTest::drawUniform3DConstructDefault() { @@ -177,6 +199,10 @@ void MeshVisualizerTest::drawUniform3DConstructDefault() { CORRADE_COMPARE(b.materialId, 0); CORRADE_COMPARE(a.objectId, 0); CORRADE_COMPARE(b.objectId, 0); + CORRADE_COMPARE(a.jointOffset, 0); + CORRADE_COMPARE(b.jointOffset, 0); + CORRADE_COMPARE(a.perInstanceJointCount, 0); + CORRADE_COMPARE(b.perInstanceJointCount, 0); constexpr MeshVisualizerDrawUniform3D ca; constexpr MeshVisualizerDrawUniform3D cb{DefaultInit}; @@ -194,6 +220,10 @@ void MeshVisualizerTest::drawUniform3DConstructDefault() { CORRADE_COMPARE(cb.materialId, 0); CORRADE_COMPARE(ca.objectId, 0); CORRADE_COMPARE(cb.objectId, 0); + CORRADE_COMPARE(ca.jointOffset, 0); + CORRADE_COMPARE(cb.jointOffset, 0); + CORRADE_COMPARE(ca.perInstanceJointCount, 0); + CORRADE_COMPARE(cb.perInstanceJointCount, 0); CORRADE_VERIFY(std::is_nothrow_default_constructible::value); CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -208,6 +238,7 @@ void MeshVisualizerTest::drawUniform3DConstructNoInit() { a.normalMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; a.materialId = 5; a.objectId = 7; + a.perInstanceJointCount = 9; new(&a) MeshVisualizerDrawUniform3D{NoInit}; { @@ -220,6 +251,7 @@ void MeshVisualizerTest::drawUniform3DConstructNoInit() { CORRADE_COMPARE(a.normalMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); CORRADE_COMPARE(a.materialId, 5); CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.perInstanceJointCount, 9); } CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -232,7 +264,9 @@ void MeshVisualizerTest::drawUniform3DSetters() { MeshVisualizerDrawUniform3D a; a.setNormalMatrix(Matrix4::rotationX(90.0_degf).normalMatrix()) .setMaterialId(5) - .setObjectId(7); + .setObjectId(7) + .setJointOffset(6) + .setPerInstanceJointCount(8); CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ Vector4{1.0f, 0.0f, 0.0f, 0.0f}, Vector4{0.0f, 0.0f, 1.0f, 0.0f}, @@ -240,14 +274,24 @@ void MeshVisualizerTest::drawUniform3DSetters() { })); CORRADE_COMPARE(a.materialId, 5); CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.jointOffset, 6); + CORRADE_COMPARE(a.perInstanceJointCount, 8); } void MeshVisualizerTest::drawUniform3DMaterialIdPacking() { MeshVisualizerDrawUniform3D a; - a.setMaterialId(13765); + a.setMaterialId(13765) + /* second 16 bits unused */ + .setJointOffset(13767) + .setPerInstanceJointCount(63574); /* The normalMatrix field is 3x4 floats, materialId should be right after in the low 16 bits on both LE and BE */ CORRADE_COMPARE(reinterpret_cast(&a)[12] & 0xffff, 13765); + /* second 16 bits unused */ + + /* jointOffset in the low, perInstanceJointCount in the high */ + CORRADE_COMPARE(reinterpret_cast(&a)[14] & 0xffff, 13767); + CORRADE_COMPARE((reinterpret_cast(&a)[14] >> 16) & 0xffff, 63574); } void MeshVisualizerTest::materialUniformConstructDefault() { diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-default.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-default.tga new file mode 100644 index 000000000..e48d4859f Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-default.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-instanced.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-instanced.tga new file mode 100644 index 000000000..0541bb316 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-instanced.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-multi.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-multi.tga new file mode 100644 index 000000000..b18175db3 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning-multi.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning.tga new file mode 100644 index 000000000..3db9983a7 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/skinning.tga differ diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 828cb0e72..806cd03f2 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -26,12 +26,14 @@ #include #include +#include #include #include #include #include /** @todo remove once AbstractImporter is -free */ #include #include +#include #include #include #include @@ -85,6 +87,9 @@ struct PhongGLTest: GL::OpenGLTester { explicit PhongGLTest(); void construct(); + #ifndef MAGNUM_TARGET_GLES2 + void constructSkinning(); + #endif void constructAsync(); #ifndef MAGNUM_TARGET_GLES2 void constructUniformBuffers(); @@ -101,6 +106,9 @@ struct PhongGLTest: GL::OpenGLTester { void constructUniformBuffersInvalid(); #endif + #ifndef MAGNUM_TARGET_GLES2 + void setPerVertexJointCountInvalid(); + #endif #ifndef MAGNUM_TARGET_GLES2 void setUniformUniformBuffersEnabled(); void bindBufferUniformBuffersNotEnabled(); @@ -122,6 +130,9 @@ struct PhongGLTest: GL::OpenGLTester { #endif void setWrongLightCountOrId(); #ifndef MAGNUM_TARGET_GLES2 + void setWrongJointCountOrId(); + #endif + #ifndef MAGNUM_TARGET_GLES2 void setWrongDrawOffset(); #endif @@ -163,10 +174,18 @@ struct PhongGLTest: GL::OpenGLTester { template void renderZeroLights(); + #ifndef MAGNUM_TARGET_GLES2 + template void renderSkinning(); + #endif + template void renderInstanced(); + #ifndef MAGNUM_TARGET_GLES2 + template void renderInstancedSkinning(); + #endif #ifndef MAGNUM_TARGET_GLES2 void renderMulti(); + void renderMultiSkinning(); #endif private: @@ -266,60 +285,94 @@ constexpr struct { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +const struct { + const char* name; + PhongGL::Flags flags; + UnsignedInt lightCount, jointCount, perVertexJointCount, secondaryPerVertexJointCount; +} ConstructSkinningData[]{ + {"no skinning", {}, + 1, 0, 0, 0}, + {"one set", {}, + 1, 16, 4, 0}, + {"two partial sets", {}, + 1, 32, 2, 3}, + {"secondary set only", {}, + 1, 12, 0, 4}, + {"dynamic per-vertex sets", PhongGL::Flag::DynamicPerVertexJointCount, + 1, 16, 4, 3}, + {"zero lights, one set", {}, + 0, 15, 4, 0}, + {"multiple lights, one set", {}, + 3, 15, 4, 0}, + {"multiple lights, two sets, dynamic per-vertex sets", + PhongGL::Flag::DynamicPerVertexJointCount, + 5, 10, 4, 4} +}; +#endif + #ifndef MAGNUM_TARGET_GLES2 constexpr struct { const char* name; PhongGL::Flags flags; UnsignedInt lightCount, materialCount, drawCount; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; } ConstructUniformBuffersData[]{ {"classic fallback", {}, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"", PhongGL::Flag::UniformBuffers, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, /* SwiftShader has 256 uniform vectors at most, per-3D-draw is 4+4, per-material 4, per-light 4 plus 4 for projection */ {"multiple lights, materials, draws", PhongGL::Flag::UniformBuffers, - 8, 8, 24}, + 8, 8, 24, 0, 0, 0}, {"multiple lights, materials, draws + light culling", PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling, - 8, 8, 24}, + 8, 8, 24, 0, 0, 0}, {"zero lights", PhongGL::Flag::UniformBuffers, - 0, 16, 24}, + 0, 16, 24, 0, 0, 0}, {"ambient + diffuse + specular texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"ambient + diffuse + specular texture + texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureTransformation, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"ambient + diffuse + specular texture array + texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::TextureTransformation, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"normal texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NormalTexture, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"normal texture + separate bitangents", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NormalTexture|PhongGL::Flag::Bitangent, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"alpha mask", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AlphaMask, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"object ID", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectId, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"object ID texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectIdTexture, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"object ID texture array", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectIdTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::TextureTransformation, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"object ID texture + instanced texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectIdTexture|PhongGL::Flag::InstancedTextureOffset, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"object ID texture array + instanced texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectIdTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::InstancedTextureOffset, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"instanced object ID texture array + texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectIdTexture|PhongGL::Flag::InstancedObjectId|PhongGL::Flag::TextureArrays|PhongGL::Flag::TextureTransformation, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"object ID texture + diffuse texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectIdTexture|PhongGL::Flag::DiffuseTexture, - 1, 1, 1}, + 1, 1, 1, 0, 0, 0}, {"no specular", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NoSpecular, - 1, 1, 1}, - {"multidraw with all the things", PhongGL::Flag::MultiDraw|PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::AmbientTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::AlphaMask|PhongGL::Flag::ObjectId|PhongGL::Flag::InstancedTextureOffset|PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedObjectId|PhongGL::Flag::LightCulling, - 8, 16, 24}, + 1, 1, 1, 0, 0, 0}, + {"skinning", PhongGL::Flag::UniformBuffers, + 1, 1, 1, 32, 3, 2}, + {"skinning, dynamic per-vertex sets", PhongGL::Flag::UniformBuffers|PhongGL::Flag::DynamicPerVertexJointCount, + 1, 1, 1, 32, 3, 4}, + {"multidraw with all the things except secondary per-vertex sets", PhongGL::Flag::MultiDraw|PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::AmbientTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::AlphaMask|PhongGL::Flag::ObjectId|PhongGL::Flag::InstancedTextureOffset|PhongGL::Flag::InstancedTransformation|PhongGL::Flag::InstancedObjectId|PhongGL::Flag::LightCulling|PhongGL::Flag::DynamicPerVertexJointCount, + 8, 16, 24, 16, 4, 0}, + {"multidraw with all the things except instancing", PhongGL::Flag::MultiDraw|PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::AmbientTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::NormalTexture|PhongGL::Flag::TextureArrays|PhongGL::Flag::AlphaMask|PhongGL::Flag::ObjectId|PhongGL::Flag::LightCulling|PhongGL::Flag::DynamicPerVertexJointCount, + 8, 16, 24, 16, 3, 4}, }; #endif constexpr struct { const char* name; PhongGL::Flags flags; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; const char* message; } ConstructInvalidData[] { {"texture transformation but not textured", @@ -330,20 +383,34 @@ constexpr struct { |PhongGL::Flag::ObjectId #endif , + 0, 0, 0, "texture transformation enabled but the shader is not textured"}, #ifndef MAGNUM_TARGET_GLES2 {"texture arrays but not textured", /* ObjectId shares bits with ObjectIdTexture but should still trigger the assert */ PhongGL::Flag::TextureArrays|PhongGL::Flag::ObjectId, + 0, 0, 0, "texture arrays enabled but the shader is not textured"}, {"conflicting bitangent and instanced object id attribute", PhongGL::Flag::Bitangent|PhongGL::Flag::InstancedObjectId, + 0, 0, 0, "Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead"}, #endif {"specular texture but no specular", PhongGL::Flag::SpecularTexture|PhongGL::Flag::NoSpecular, - "specular texture requires the shader to not have specular disabled"} + 0, 0, 0, + "specular texture requires the shader to not have specular disabled"}, + #ifndef MAGNUM_TARGET_GLES2 + {"dynamic per-vertex joint count but no static per-vertex joint count", + PhongGL::Flag::DynamicPerVertexJointCount, + 0, 0, 0, + "dynamic per-vertex joint count enabled for zero joints"}, + {"instancing together with secondary per-vertex sets", + PhongGL::Flag::InstancedTransformation, + 10, 4, 1, + "TransformationMatrix attribute binding conflicts with the SecondaryJointIds / SecondaryWeights attributes, use a non-instanced rendering with secondary weights instead"} + #endif }; #ifndef MAGNUM_TARGET_GLES2 @@ -765,6 +832,81 @@ const struct { }; #endif +#ifndef MAGNUM_TARGET_GLES2 +/* Same as in FlatGL and MeshVisualizerGL tests */ +const struct { + const char* name; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; + UnsignedInt dynamicPerVertexJointCount, dynamicSecondaryPerVertexJointCount; + PhongGL::Flags flags; + Containers::Array> attributes; + bool setDynamicPerVertexJointCount, setJointMatrices, setJointMatricesOneByOne; + const char* expected; +} RenderSkinningData[]{ + {"no skinning", 0, 0, 0, 0, 0, {}, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::Three}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::Three}}, + }}, false, false, false, + "skinning-default.tga"}, + {"default joint matrices", 5, 3, 0, 0, 0, {}, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::Three}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::Three}}, + }}, false, false, false, + "skinning-default.tga"}, + {"single set", 5, 3, 0, 0, 0, {}, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::Three}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"single set, joint matrices one by one", 5, 3, 0, 0, 0, {}, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::Three}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::Three}}, + }}, false, true, true, + "skinning.tga"}, + {"single set, dynamic, left at defaults", 5, 3, 0, 0, 0, PhongGL::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::Three}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"single set, dynamic", 5, 4, 0, 3, 0, PhongGL::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::Three}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::Three}}, + }}, true, true, false, + "skinning.tga"}, + {"two sets", 5, 1, 2, 0, 0, {}, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::One}}, + {4, PhongGL::SecondaryJointIds{PhongGL::SecondaryJointIds::Components::Two}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::One}}, + {4*4, PhongGL::SecondaryWeights{PhongGL::SecondaryWeights::Components::Two}}, + }}, false, true, false, + "skinning.tga"}, + {"two sets, dynamic, left at defaults", 5, 1, 2, 0, 0, PhongGL::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::One}}, + {4, PhongGL::SecondaryJointIds{PhongGL::SecondaryJointIds::Components::Two}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::One}}, + {4*4, PhongGL::SecondaryWeights{PhongGL::SecondaryWeights::Components::Two}}, + }}, false, true, false, + "skinning.tga"}, + {"two sets, dynamic", 5, 4, 4, 1, 2, PhongGL::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, PhongGL::JointIds{PhongGL::JointIds::Components::One}}, + {4, PhongGL::SecondaryJointIds{PhongGL::SecondaryJointIds::Components::Two}}, + {3*4, PhongGL::Weights{PhongGL::Weights::Components::One}}, + {4*4, PhongGL::SecondaryWeights{PhongGL::SecondaryWeights::Components::Two}}, + }}, true, true, false, + "skinning.tga"}, + {"only secondary set", 5, 0, 3, 0, 0, {}, {InPlaceInit, { + {0, PhongGL::SecondaryJointIds{PhongGL::SecondaryJointIds::Components::Three}}, + {3*4, PhongGL::SecondaryWeights{PhongGL::SecondaryWeights::Components::Three}}, + }}, false, true, false, + "skinning.tga"}, + {"only secondary set, dynamic", 5, 4, 4, 0, 3, PhongGL::Flag::DynamicPerVertexJointCount, {InPlaceInit, { + {0, PhongGL::SecondaryJointIds{PhongGL::SecondaryJointIds::Components::Three}}, + {3*4, PhongGL::SecondaryWeights{PhongGL::SecondaryWeights::Components::Three}}, + }}, true, true, false, + "skinning.tga"}, +}; +#endif + constexpr struct { const char* name; const char* expected; @@ -944,12 +1086,32 @@ constexpr struct { 50.34f, 0.131f}, /** @todo test normal and per-draw scaling when there's usable texture */ }; + +/* Same as in FlatGL and MeshVisualizerGL tests */ +const struct { + const char* name; + PhongGL::Flags flags; + UnsignedInt materialCount, drawCount, jointCount; + UnsignedInt uniformIncrement; +} RenderMultiSkinningData[]{ + {"bind with offset", + {}, 1, 1, 4, 16}, + {"draw offset", + {}, 2, 3, 9, 1}, + {"multidraw", + PhongGL::Flag::MultiDraw, 2, 3, 9, 1} +}; #endif PhongGLTest::PhongGLTest() { addInstancedTests({&PhongGLTest::construct}, Containers::arraySize(ConstructData)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&PhongGLTest::constructSkinning}, + Containers::arraySize(ConstructSkinningData)); + #endif + addTests({&PhongGLTest::constructAsync}); #ifndef MAGNUM_TARGET_GLES2 @@ -976,7 +1138,8 @@ PhongGLTest::PhongGLTest() { #endif #ifndef MAGNUM_TARGET_GLES2 - addTests({&PhongGLTest::setUniformUniformBuffersEnabled, + addTests({&PhongGLTest::setPerVertexJointCountInvalid, + &PhongGLTest::setUniformUniformBuffersEnabled, &PhongGLTest::bindBufferUniformBuffersNotEnabled}); #endif @@ -1004,6 +1167,9 @@ PhongGLTest::PhongGLTest() { #endif &PhongGLTest::setWrongLightCountOrId, #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::setWrongJointCountOrId, + #endif + #ifndef MAGNUM_TARGET_GLES2 &PhongGLTest::setWrongDrawOffset #endif }); @@ -1145,6 +1311,16 @@ PhongGLTest::PhongGLTest() { #endif ); + #ifndef MAGNUM_TARGET_GLES2 + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &PhongGLTest::renderSkinning, + &PhongGLTest::renderSkinning}, + Containers::arraySize(RenderSkinningData), + &PhongGLTest::renderSetup, + &PhongGLTest::renderTeardown); + #endif + /* MSVC needs explicit type due to default template args */ addInstancedTests({ &PhongGLTest::renderInstanced, @@ -1162,11 +1338,25 @@ PhongGLTest::PhongGLTest() { #endif ); + #ifndef MAGNUM_TARGET_GLES2 + /* MSVC needs explicit type due to default template args */ + addTests({ + &PhongGLTest::renderInstancedSkinning, + &PhongGLTest::renderInstancedSkinning}, + &PhongGLTest::renderSetup, + &PhongGLTest::renderTeardown); + #endif + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&PhongGLTest::renderMulti}, Containers::arraySize(RenderMultiData), &PhongGLTest::renderObjectIdSetup, &PhongGLTest::renderObjectIdTeardown); + + addInstancedTests({&PhongGLTest::renderMultiSkinning}, + Containers::arraySize(RenderMultiSkinningData), + &PhongGLTest::renderSetup, + &PhongGLTest::renderTeardown); #endif /* Load the plugins directly from the build tree. Otherwise they're either @@ -1220,10 +1410,43 @@ void PhongGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::constructSkinning() { + auto&& data = ConstructSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + PhongGL shader{PhongGL::Configuration{} + .setFlags(data.flags) + .setLightCount(data.lightCount) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.jointCount(), data.jointCount); + CORRADE_COMPARE(shader.perVertexJointCount(), data.perVertexJointCount); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), data.secondaryPerVertexJointCount); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + void PhongGLTest::constructAsync() { PhongGL::CompileState state = PhongGL::compile(PhongGL::Configuration{} .setFlags(PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset) - .setLightCount(3)); + .setLightCount(3) + /* Skinning properties tested in constructUniformBuffersAsync(), as + there we don't need to bother with ES2 */ + ); CORRADE_COMPARE(state.flags(), PhongGL::Flag::SpecularTexture|PhongGL::Flag::InstancedTextureOffset); CORRADE_COMPARE(state.lightCount(), 3); @@ -1253,7 +1476,7 @@ void PhongGLTest::constructUniformBuffers() { #ifndef MAGNUM_TARGET_GLES if((data.flags & PhongGL::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); - if((data.flags & PhongGL::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + if((data.flags & PhongGL::Flag::ObjectId || data.jointCount) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); if((data.flags & PhongGL::Flag::TextureArrays) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); @@ -1276,11 +1499,15 @@ void PhongGLTest::constructUniformBuffers() { .setFlags(data.flags) .setLightCount(data.lightCount) .setMaterialCount(data.materialCount) - .setDrawCount(data.drawCount)}; + .setDrawCount(data.drawCount) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; CORRADE_COMPARE(shader.flags(), data.flags); CORRADE_COMPARE(shader.lightCount(), data.lightCount); CORRADE_COMPARE(shader.materialCount(), data.materialCount); CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_COMPARE(shader.jointCount(), data.jointCount); + CORRADE_COMPARE(shader.perVertexJointCount(), data.perVertexJointCount); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), data.secondaryPerVertexJointCount); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) @@ -1296,27 +1523,37 @@ void PhongGLTest::constructUniformBuffersAsync() { #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif PhongGL::CompileState state = PhongGL::compile(PhongGL::Configuration{} .setFlags(PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling) - .setLightCount(8) - .setMaterialCount(8) - .setDrawCount(24)); + /* SwiftShader has 256 uniform vectors at most, per-3D-draw is 4+4, + per-material 4, per-light 4, per joint 4 plus 4 for projection */ + .setLightCount(2) + .setMaterialCount(5) + .setDrawCount(24) + .setJointCount(7, 3, 4)); CORRADE_COMPARE(state.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); - CORRADE_COMPARE(state.lightCount(), 8); - CORRADE_COMPARE(state.materialCount(), 8); + CORRADE_COMPARE(state.lightCount(), 2); + CORRADE_COMPARE(state.materialCount(), 5); CORRADE_COMPARE(state.drawCount(), 24); + CORRADE_COMPARE(state.jointCount(), 7); + CORRADE_COMPARE(state.perVertexJointCount(), 3); + CORRADE_COMPARE(state.secondaryPerVertexJointCount(), 4); while(!state.isLinkFinished()) Utility::System::sleep(100); PhongGL shader{std::move(state)}; CORRADE_COMPARE(shader.flags(), PhongGL::Flag::UniformBuffers|PhongGL::Flag::LightCulling); - CORRADE_COMPARE(shader.lightCount(), 8); - CORRADE_COMPARE(shader.materialCount(), 8); + CORRADE_COMPARE(shader.lightCount(), 2); + CORRADE_COMPARE(shader.materialCount(), 5); CORRADE_COMPARE(shader.drawCount(), 24); - CORRADE_VERIFY(shader.isLinkFinished()); + CORRADE_COMPARE(shader.jointCount(), 7); + CORRADE_COMPARE(shader.perVertexJointCount(), 3); + CORRADE_COMPARE(shader.secondaryPerVertexJointCount(), 4); CORRADE_VERIFY(shader.id()); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) @@ -1332,7 +1569,10 @@ void PhongGLTest::constructUniformBuffersAsync() { void PhongGLTest::constructMove() { PhongGL a{PhongGL::Configuration{} .setFlags(PhongGL::Flag::AlphaMask) - .setLightCount(3)}; + .setLightCount(3) + /* Skinning properties tested in constructMoveUniformBuffers(), as + there we don't need to bother with ES2 */ + }; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -1357,13 +1597,16 @@ void PhongGLTest::constructMoveUniformBuffers() { #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif PhongGL a{PhongGL::Configuration{} .setFlags(PhongGL::Flag::UniformBuffers) .setLightCount(3) .setMaterialCount(2) - .setDrawCount(5)}; + .setDrawCount(5) + .setJointCount(16, 4, 3)}; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -1375,6 +1618,9 @@ void PhongGLTest::constructMoveUniformBuffers() { CORRADE_COMPARE(b.lightCount(), 3); CORRADE_COMPARE(b.materialCount(), 2); CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_COMPARE(b.jointCount(), 16); + CORRADE_COMPARE(b.perVertexJointCount(), 4); + CORRADE_COMPARE(b.secondaryPerVertexJointCount(), 3); CORRADE_VERIFY(!a.id()); PhongGL c{NoCreate}; @@ -1384,6 +1630,9 @@ void PhongGLTest::constructMoveUniformBuffers() { CORRADE_COMPARE(c.lightCount(), 3); CORRADE_COMPARE(c.materialCount(), 2); CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_COMPARE(c.jointCount(), 16); + CORRADE_COMPARE(c.perVertexJointCount(), 4); + CORRADE_COMPARE(c.secondaryPerVertexJointCount(), 3); CORRADE_VERIFY(!b.id()); } #endif @@ -1397,7 +1646,11 @@ void PhongGLTest::constructInvalid() { std::ostringstream out; Error redirectError{&out}; PhongGL{PhongGL::Configuration{} - .setFlags(data.flags)}; + .setFlags(data.flags) + #ifndef MAGNUM_TARGET_GLES2 + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount) + #endif + }; CORRADE_COMPARE(out.str(), Utility::formatString( "Shaders::PhongGL: {}\n", data.message)); } @@ -1426,6 +1679,32 @@ void PhongGLTest::constructUniformBuffersInvalid() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::setPerVertexJointCountInvalid() { + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + PhongGL a{PhongGL::Configuration{}}; + PhongGL b{PhongGL::Configuration{} + .setFlags(PhongGL::Flag::DynamicPerVertexJointCount) + .setJointCount(16, 3, 2)}; + + std::ostringstream out; + Error redirectError{&out}; + a.setPerVertexJointCount(3, 2); + b.setPerVertexJointCount(4); + b.setPerVertexJointCount(3, 3); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setPerVertexJointCount(): the shader was not created with dynamic per-vertex joint count enabled\n" + "Shaders::PhongGL::setPerVertexJointCount(): expected at most 3 per-vertex joints, got 4\n" + "Shaders::PhongGL::setPerVertexJointCount(): expected at most 2 secondary per-vertex joints, got 3\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 void PhongGLTest::setUniformUniformBuffersEnabled() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -1440,26 +1719,31 @@ void PhongGLTest::setUniformUniformBuffersEnabled() { std::ostringstream out; Error redirectError{&out}; - shader.setAmbientColor({}) - .setDiffuseColor({}) - .setNormalTextureScale({}) - .setSpecularColor({}) - .setShininess({}) - .setAlphaMask({}) - .setObjectId({}) - .setTransformationMatrix({}) - .setNormalMatrix({}) - .setProjectionMatrix({}) - .setTextureMatrix({}) - .setTextureLayer({}) - .setLightPositions(std::initializer_list{}) - .setLightPosition(0, Vector4{}) - .setLightColors(std::initializer_list{}) - .setLightColor(0, Color3{}) - .setLightSpecularColors({}) - .setLightSpecularColor(0, {}) - .setLightRanges({}) - .setLightRange(0, {}); + shader + /* setPerVertexJointCount() works on both UBOs and classic */ + .setAmbientColor({}) + .setDiffuseColor({}) + .setNormalTextureScale({}) + .setSpecularColor({}) + .setShininess({}) + .setAlphaMask({}) + .setObjectId({}) + .setTransformationMatrix({}) + .setNormalMatrix({}) + .setProjectionMatrix({}) + .setTextureMatrix({}) + .setTextureLayer({}) + .setLightPositions(std::initializer_list{}) + .setLightPosition(0, Vector4{}) + .setLightColors(std::initializer_list{}) + .setLightColor(0, Color3{}) + .setLightSpecularColors({}) + .setLightSpecularColor(0, {}) + .setLightRanges({}) + .setLightRange(0, {}) + .setJointMatrices({}) + .setJointMatrix(0, {}) + .setPerInstanceJointCount(0); CORRADE_COMPARE(out.str(), "Shaders::PhongGL::setAmbientColor(): the shader was created with uniform buffers enabled\n" "Shaders::PhongGL::setDiffuseColor(): the shader was created with uniform buffers enabled\n" @@ -1480,7 +1764,10 @@ void PhongGLTest::setUniformUniformBuffersEnabled() { "Shaders::PhongGL::setLightSpecularColors(): the shader was created with uniform buffers enabled\n" "Shaders::PhongGL::setLightSpecularColor(): the shader was created with uniform buffers enabled\n" "Shaders::PhongGL::setLightRanges(): the shader was created with uniform buffers enabled\n" - "Shaders::PhongGL::setLightRange(): the shader was created with uniform buffers enabled\n"); + "Shaders::PhongGL::setLightRange(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setJointMatrices(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setJointMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setPerInstanceJointCount(): the shader was created with uniform buffers enabled\n"); } void PhongGLTest::bindBufferUniformBuffersNotEnabled() { @@ -1503,6 +1790,8 @@ void PhongGLTest::bindBufferUniformBuffersNotEnabled() { .bindMaterialBuffer(buffer, 0, 16) .bindLightBuffer(buffer) .bindLightBuffer(buffer, 0, 16) + .bindJointBuffer(buffer) + .bindJointBuffer(buffer, 0, 16) .setDrawOffset(0); CORRADE_COMPARE(out.str(), "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" @@ -1517,6 +1806,8 @@ void PhongGLTest::bindBufferUniformBuffersNotEnabled() { "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindJointBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindJointBuffer(): the shader was not created with uniform buffers enabled\n" "Shaders::PhongGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } #endif @@ -1709,6 +2000,23 @@ void PhongGLTest::setWrongLightCountOrId() { "Shaders::PhongGL::setLightRange(): light ID 5 is out of bounds for 5 lights\n"); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::setWrongJointCountOrId() { + CORRADE_SKIP_IF_NO_ASSERT(); + + PhongGL shader{PhongGL::Configuration{} + .setJointCount(5, 1)}; + + std::ostringstream out; + Error redirectError{&out}; + shader.setJointMatrices({Matrix4{}}) + .setJointMatrix(5, Matrix4{}); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setJointMatrices(): expected 5 items but got 1\n" + "Shaders::PhongGL::setJointMatrix(): joint ID 5 is out of bounds for 5 joints\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 void PhongGLTest::setWrongDrawOffset() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -3667,6 +3975,143 @@ template void PhongGLTest::renderZeroLights() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +template void PhongGLTest::renderSkinning() { + auto&& data = RenderSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(data.jointCount && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(data.jointCount && GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + + /* Tests just 2D movement, no lights, no normals, as that should be pretty + independent of the skinning process. That also makes it easy to reuse + for Flat2D/3D and MeshVisualizer shaders. */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Top right corner gets moved to the right and up, top left just up, + bottom right just right, bottom left corner gets slightly scaled. + + 3--1 + | /| + |/ | + 2--0 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 2, 0}, {1.0f, 50.0f, 0.5f}}, + {{ 1.0f, 1.0f, 0.0f}, {1, 0, 0}, {0.5f, 0.5f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {3, 4, 4}, {0.5f, 0.25f, 0.25f}}, + {{-1.0f, 1.0f, 0.0f}, {1, 0, 4}, {1.0f, 0.0f, 0.0f}}, + }; + + Matrix4 jointMatrices[]{ + Matrix4::translation(Vector3::xAxis(0.5f)), + Matrix4::translation(Vector3::yAxis(0.5f)), + Matrix4{Math::ZeroInit}, + Matrix4::scaling(Vector3{2.0f}), + Matrix4{Math::IdentityInit}, + }; + + GL::Buffer buffer{vertices}; + + GL::Mesh mesh{MeshPrimitive::TriangleStrip}; + mesh.setCount(4); + mesh.addVertexBuffer(buffer, 0, sizeof(Vertex), GL::DynamicAttribute{PhongGL::Position{}}); + for(auto&& attribute: data.attributes) + mesh.addVertexBuffer(buffer, 3*4 + attribute.first(), sizeof(Vertex), attribute.second()); + + PhongGL shader{PhongGL::Configuration{} + .setFlags(data.flags|flag) + .setLightCount(0) + .setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount)}; + if(data.setDynamicPerVertexJointCount) + shader.setPerVertexJointCount(data.dynamicPerVertexJointCount, data.dynamicSecondaryPerVertexJointCount); + + if(flag == PhongGL::Flag{}) { + if(data.setJointMatricesOneByOne) { + shader + .setJointMatrix(0, jointMatrices[0]) + .setJointMatrix(1, jointMatrices[1]) + .setJointMatrix(2, jointMatrices[2]) + .setJointMatrix(3, jointMatrices[3]) + .setJointMatrix(4, jointMatrices[4]); + } else if(data.setJointMatrices) + shader.setJointMatrices(jointMatrices); + shader + .setAmbientColor(0xffffff_rgbf) + .setTransformationMatrix(Matrix4::scaling(Vector3{0.5f})) + .draw(mesh); + } else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::scaling(Vector3{0.5f})) + }}; + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[0] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[1] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[2] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[3] : Matrix4{}), + TransformationUniform3D{} + .setTransformationMatrix(data.setJointMatrices ? + jointMatrices[4] : Matrix4{}), + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0xffffff_rgbf) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join({_testDir, "TestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); +} +#endif + template void PhongGLTest::renderInstanced() { auto&& data = RenderInstancedData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -4077,6 +4522,142 @@ template void PhongGLTest::renderInstanced() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +template void PhongGLTest::renderInstancedSkinning() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + } + + /* Similarly to renderSkinning() tests just 2D movement, differently and + clearly distinguisable for each instance */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[3]; + Float weights[3]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 3--1 + | /| + |/ | + 2--0 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 0, 0}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0, 3, 0}, {0.0f, 1.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0, 0, 1}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, 1.0f, 0.0f}, {4, 0, 0}, {1.0f, 0.0f, 0.0f}}, + }; + + Matrix4 instanceTransformations[]{ + Matrix4::translation({-1.5f, -1.5f, 0.0f}), + Matrix4::translation({ 1.5f, -1.5f, 0.0f}), + Matrix4::translation({ 0.0f, 1.5f, 0.0f}) + }; + + Matrix4 jointMatrices[]{ + /* First instance moves bottom left corner */ + {}, + Matrix4::translation({-0.5f, -0.5f, 0.0f}), + {}, + {}, + {}, + + /* Second instance moves bottom right corner */ + Matrix4::translation({0.5f, -0.5f, 0.0f}), + {}, + {}, + {}, + {}, + + /* Third instance moves both top corners */ + {}, + {}, + {}, + Matrix4::translation({0.5f, 0.5f, 0.0f}), + Matrix4::translation({-0.5f, 0.5f, 0.0f}), + }; + + GL::Mesh mesh{MeshPrimitive::TriangleStrip}; + mesh.setCount(4) + .addVertexBuffer(GL::Buffer{vertices}, 0, + PhongGL::Position{}, + PhongGL::JointIds{PhongGL::JointIds::Components::Three}, + PhongGL::Weights{PhongGL::Weights::Components::Three}) + .addVertexBufferInstanced(GL::Buffer{instanceTransformations}, 1, 0, + PhongGL::TransformationMatrix{}) + .setInstanceCount(3); + + PhongGL shader{PhongGL::Configuration{} + .setFlags(PhongGL::Flag::InstancedTransformation|flag) + .setLightCount(0) + .setJointCount(15, 3, 0)}; + + if(flag == PhongGL::Flag{}) { + shader + .setJointMatrices(jointMatrices) + .setPerInstanceJointCount(5) + .setAmbientColor(0xffffff_rgbf) + .setTransformationMatrix(Matrix4::scaling(Vector3{0.3f})) + .draw(mesh); + } else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::scaling(Vector3{0.3f})) + }}; + TransformationUniform3D jointMatricesUniformData[Containers::arraySize(jointMatrices)]; + Utility::copy(jointMatrices, Containers::stridedArrayView(jointMatricesUniformData).slice(&TransformationUniform3D::transformationMatrix)); + GL::Buffer jointMatricesUniform{GL::Buffer::TargetHint::Uniform, + jointMatricesUniformData + }; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + .setPerInstanceJointCount(5) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0xffffff_rgbf) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindJointBuffer(jointMatricesUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "TestFiles/skinning-instanced.tga"), + (DebugTools::CompareImageToFile{_manager})); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 void PhongGLTest::renderMulti() { auto&& data = RenderMultiData[testCaseInstanceId()]; @@ -4467,6 +5048,246 @@ void PhongGLTest::renderMulti() { CORRADE_COMPARE(image.pixels()[56][40], data.expectedId[2]); /* Circle */ } } + +void PhongGLTest::renderMultiSkinning() { + auto&& data = RenderMultiSkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= PhongGL::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed (joint) arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + PhongGL shader{PhongGL::Configuration{} + .setFlags(PhongGL::Flag::UniformBuffers|data.flags) + .setLightCount(0) + .setDrawCount(data.drawCount) + .setMaterialCount(data.materialCount) + .setJointCount(data.jointCount, 2, 0)}; + + /* Similarly to renderSkinning() tests just 2D movement, differently and + clearly distinguisable for each draw */ + struct Vertex { + Vector3 position; + UnsignedInt jointIds[2]; + Float weights[2]; + } vertices[]{ + /* Each corner affected by exactly one matrix, but at different item + in the array + + 3--1 5 9--8 + | /| /| | / + |/ | / | |/ + 2--0 6--4 7 */ + {{ 1.0f, -1.0f, 0.0f}, {0, 0}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0, 2}, {0.0f, 1.0f}}, + {{-1.0f, -1.0f, 0.0f}, {1, 2}, {1.0f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}, {0, 3}, {0.0f, 1.0f}}, + + {{ 1.0f, -1.0f, 0.0f}, {0, 3}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {2, 1}, {1.0f, 0.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0, 0}, {1.0f, 0.0f}}, + + {{-1.0f, -1.0f, 0.0f}, {0, 1}, {0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {1, 0}, {1.0f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}, {2, 2}, {0.5f, 0.5f}} + }; + + UnsignedInt indices[]{ + 0, 1, 2, + 2, 1, 3, + + 4, 5, 6, + + 7, 8, 9 + }; + + GL::Mesh mesh{MeshPrimitive::Triangles}; + mesh.setCount(12) + .addVertexBuffer(GL::Buffer{vertices}, 0, + PhongGL::Position{}, + PhongGL::JointIds{PhongGL::JointIds::Components::Two}, + PhongGL::Weights{PhongGL::Weights::Components::Two}) + .setIndexBuffer(GL::Buffer{indices}, 0, MeshIndexType::UnsignedInt); + GL::MeshView square{mesh}; + square.setCount(6); + GL::MeshView triangle1{mesh}; + triangle1.setCount(3) + .setIndexRange(6); + GL::MeshView triangle2{mesh}; + triangle2.setCount(3) + .setIndexRange(9); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }};; + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + 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] = PhongMaterialUniform{} + .setAmbientColor(0x33ffff_rgbf); + materialData[1*data.uniformIncrement] = PhongMaterialUniform{} + .setAmbientColor(0xffff33_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationData{2*data.uniformIncrement + 1}; + transformationData[0*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({ 0.0f, -1.5f, 0.0f})); + transformationData[1*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({ 1.5f, 1.5f, 0.0f})); + transformationData[2*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::scaling(Vector3{0.3f})* + Matrix4::translation({-1.5f, 1.5f, 0.0f})); + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, transformationData}; + + Containers::Array jointData{Math::max(2*data.uniformIncrement + 4, 10u)}; + /* First draw moves both bottom corners */ + jointData[Math::max(0*data.uniformIncrement, 0u) + 0] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({ 0.5f, -0.5f, 0.0f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 1] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({-0.5f, -0.5f, 0.0f})); + jointData[Math::max(0*data.uniformIncrement, 0u) + 2] = TransformationUniform3D{}; + jointData[Math::max(0*data.uniformIncrement, 0u) + 3] = TransformationUniform3D{}; + /* Second draw overlaps with the first with two identity matrices (unless + the padding prevents that); moves top right corner */ + jointData[Math::max(1*data.uniformIncrement, 2u) + 0] = TransformationUniform3D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 1] = TransformationUniform3D{}; + jointData[Math::max(1*data.uniformIncrement, 2u) + 2] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({ 0.5f, 0.5f, 0.0f})); + jointData[Math::max(1*data.uniformIncrement, 2u) + 3] = TransformationUniform3D{}; + /* Third draw moves top left corner */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 0] = TransformationUniform3D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 1] = TransformationUniform3D{}; + jointData[Math::max(2*data.uniformIncrement, 6u) + 2] = TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation({-0.5f, 0.5f, 0.0f})); + /* This one is unused but has to be here in order to be able to bind the + last three-component part while JOINT_COUNT is set to 4 */ + jointData[Math::max(2*data.uniformIncrement, 6u) + 3] = TransformationUniform3D{}; + GL::Buffer jointUniform{GL::Buffer::TargetHint::Uniform, + jointData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material / joint offsets are zero if we have single draw, as those are + done with UBO offset bindings instead */ + drawData[0*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + /* Overlaps with the first joint set with two matrices, unless the + padding in the single-draw case prevents that */ + .setJointOffset(data.drawCount == 1 ? 0 : 2); + drawData[2*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setJointOffset(data.drawCount == 1 ? 0 : 6); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + shader.bindProjectionBuffer(projectionUniform); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindJointBuffer(jointUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindJointBuffer(jointUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + shader.draw(triangle1); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindJointBuffer(jointUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + data.jointCount*sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + shader.draw(triangle2); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationBuffer(transformationUniform) + .bindJointBuffer(jointUniform) + .bindDrawBuffer(drawUniform); + + if(data.flags >= PhongGL::Flag::MultiDraw) + shader.draw({square, triangle1, triangle2}); + else { + shader.setDrawOffset(0) + .draw(square); + shader.setDrawOffset(1) + .draw(triangle1); + shader.setDrawOffset(2) + .draw(triangle2); + } + } + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "TestFiles/skinning-multi.tga"), + (DebugTools::CompareImageToFile{_manager})); +} #endif }}}} diff --git a/src/Magnum/Shaders/Test/PhongGL_Test.cpp b/src/Magnum/Shaders/Test/PhongGL_Test.cpp index 2db58160b..434f37ad5 100644 --- a/src/Magnum/Shaders/Test/PhongGL_Test.cpp +++ b/src/Magnum/Shaders/Test/PhongGL_Test.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "Magnum/Shaders/PhongGL.h" @@ -36,6 +37,10 @@ namespace Magnum { namespace Shaders { namespace Test { namespace { struct PhongGL_Test: TestSuite::Tester { explicit PhongGL_Test(); + #ifndef MAGNUM_TARGET_GLES2 + void configurationSetJointCountInvalid(); + #endif + void constructNoCreate(); void constructCopy(); @@ -44,7 +49,36 @@ struct PhongGL_Test: TestSuite::Tester { void debugFlagsSupersets(); }; +#ifndef MAGNUM_TARGET_GLES2 +const struct { + const char* name; + UnsignedInt jointCount, perVertexJointCount, secondaryPerVertexJointCount; + const char* message; +} ConfigurationSetJointCountInvalidData[] { + {"per-vertex joint count too large", + 10, 5, 0, + "expected at most 4 per-vertex joints, got 5"}, + {"secondary per-vertex joint count too large", + 10, 0, 5, + "expected at most 4 secondary per-vertex joints, got 5"}, + {"joint count but no per-vertex joint count", + 10, 0, 0, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, + {"per-vertex joint count but no joint count", + 0, 2, 0, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, + {"secondary per-vertex joint count but no joint count", + 0, 0, 3, + "either both joint count and (secondary) per-vertex joint count has to be non-zero, or all zero"}, +}; +#endif + PhongGL_Test::PhongGL_Test() { + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&PhongGL_Test::configurationSetJointCountInvalid}, + Containers::arraySize(ConfigurationSetJointCountInvalidData)); + #endif + addTests({&PhongGL_Test::constructNoCreate, &PhongGL_Test::constructCopy, @@ -53,6 +87,22 @@ PhongGL_Test::PhongGL_Test() { &PhongGL_Test::debugFlagsSupersets}); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGL_Test::configurationSetJointCountInvalid() { + auto&& data = ConfigurationSetJointCountInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + CORRADE_SKIP_IF_NO_ASSERT(); + + PhongGL::Configuration configuration; + + std::ostringstream out; + Error redirectError{&out}; + configuration.setJointCount(data.jointCount, data.perVertexJointCount, data.secondaryPerVertexJointCount); + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::PhongGL::Configuration::setJointCount(): {}\n", data.message)); +} +#endif + void PhongGL_Test::constructNoCreate() { { PhongGL shader{NoCreate}; diff --git a/src/Magnum/Shaders/Test/PhongTest.cpp b/src/Magnum/Shaders/Test/PhongTest.cpp index 0a4e4e40d..859d98595 100644 --- a/src/Magnum/Shaders/Test/PhongTest.cpp +++ b/src/Magnum/Shaders/Test/PhongTest.cpp @@ -117,6 +117,10 @@ void PhongTest::drawUniformConstructDefault() { CORRADE_COMPARE(b.lightOffset, 0); CORRADE_COMPARE(a.lightCount, 0xffffu); CORRADE_COMPARE(b.lightCount, 0xffffu); + CORRADE_COMPARE(a.jointOffset, 0); + CORRADE_COMPARE(b.jointOffset, 0); + CORRADE_COMPARE(a.perInstanceJointCount, 0); + CORRADE_COMPARE(b.perInstanceJointCount, 0); constexpr PhongDrawUniform ca; constexpr PhongDrawUniform cb{DefaultInit}; @@ -138,6 +142,10 @@ void PhongTest::drawUniformConstructDefault() { CORRADE_COMPARE(cb.lightOffset, 0); CORRADE_COMPARE(ca.lightCount, 0xffffu); CORRADE_COMPARE(cb.lightCount, 0xffffu); + CORRADE_COMPARE(ca.jointOffset, 0); + CORRADE_COMPARE(cb.jointOffset, 0); + CORRADE_COMPARE(ca.perInstanceJointCount, 0); + CORRADE_COMPARE(cb.perInstanceJointCount, 0); CORRADE_VERIFY(std::is_nothrow_default_constructible::value); CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -152,6 +160,7 @@ void PhongTest::drawUniformConstructNoInit() { a.normalMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; a.materialId = 5; a.lightCount = 7; + a.perInstanceJointCount = 9; new(&a) PhongDrawUniform{NoInit}; { @@ -164,6 +173,7 @@ void PhongTest::drawUniformConstructNoInit() { CORRADE_COMPARE(a.normalMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); CORRADE_COMPARE(a.materialId, 5); CORRADE_COMPARE(a.lightCount, 7); + CORRADE_COMPARE(a.perInstanceJointCount, 9); } CORRADE_VERIFY(std::is_nothrow_constructible::value); @@ -177,7 +187,9 @@ void PhongTest::drawUniformSetters() { a.setNormalMatrix(Matrix4::rotationX(90.0_degf).normalMatrix()) .setMaterialId(5) .setObjectId(7) - .setLightOffsetCount(9, 13); + .setLightOffsetCount(9, 13) + .setJointOffset(6) + .setPerInstanceJointCount(8); CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ Vector4{1.0f, 0.0f, 0.0f, 0.0f}, Vector4{0.0f, 0.0f, 1.0f, 0.0f}, @@ -187,13 +199,17 @@ void PhongTest::drawUniformSetters() { CORRADE_COMPARE(a.objectId, 7); CORRADE_COMPARE(a.lightOffset, 9); CORRADE_COMPARE(a.lightCount, 13); + CORRADE_COMPARE(a.jointOffset, 6); + CORRADE_COMPARE(a.perInstanceJointCount, 8); } void PhongTest::drawUniformPacking() { PhongDrawUniform a; a.setMaterialId(13765) /* second 16 bits unused */ - .setLightOffsetCount(13766, 63573); + .setLightOffsetCount(13766, 63573) + .setJointOffset(13767) + .setPerInstanceJointCount(63574); /* The normalMatrix field is 3x4 floats, materialId should be right after in the low 16 bits on both LE and BE */ CORRADE_COMPARE(reinterpret_cast(&a)[12] & 0xffff, 13765); @@ -203,6 +219,10 @@ void PhongTest::drawUniformPacking() { in the high */ CORRADE_COMPARE(reinterpret_cast(&a)[14] & 0xffff, 13766); CORRADE_COMPARE((reinterpret_cast(&a)[14] >> 16) & 0xffff, 63573); + + /* jointOffset in the low, perInstanceJointCount in the high */ + CORRADE_COMPARE(reinterpret_cast(&a)[15] & 0xffff, 13767); + CORRADE_COMPARE((reinterpret_cast(&a)[15] >> 16) & 0xffff, 63574); } void PhongTest::materialUniformConstructDefault() { diff --git a/src/Magnum/Shaders/Test/TestFiles/skinning-default.tga b/src/Magnum/Shaders/Test/TestFiles/skinning-default.tga new file mode 100644 index 000000000..d103a35ab Binary files /dev/null and b/src/Magnum/Shaders/Test/TestFiles/skinning-default.tga differ diff --git a/src/Magnum/Shaders/Test/TestFiles/skinning-instanced.tga b/src/Magnum/Shaders/Test/TestFiles/skinning-instanced.tga new file mode 100644 index 000000000..7e8c9616b Binary files /dev/null and b/src/Magnum/Shaders/Test/TestFiles/skinning-instanced.tga differ diff --git a/src/Magnum/Shaders/Test/TestFiles/skinning-multi.tga b/src/Magnum/Shaders/Test/TestFiles/skinning-multi.tga new file mode 100644 index 000000000..44980c651 Binary files /dev/null and b/src/Magnum/Shaders/Test/TestFiles/skinning-multi.tga differ diff --git a/src/Magnum/Shaders/Test/TestFiles/skinning.tga b/src/Magnum/Shaders/Test/TestFiles/skinning.tga new file mode 100644 index 000000000..07ee14f2f Binary files /dev/null and b/src/Magnum/Shaders/Test/TestFiles/skinning.tga differ