diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index 654ca8559..b59b99af9 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -26,6 +26,7 @@ #include "Compile.h" #include +#include #include #include @@ -68,15 +69,50 @@ GL::Mesh compileInternal(const Trade::MeshData& meshData, GL::Buffer&& indices, /* Vertex data */ GL::Buffer verticesRef = GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array); - /* Ensure each known attribute gets bound only once. There's 16 generic - attributes at most, for each remember the mesh attribute index that got - bound to it first, or ~UnsignedInt{} if none yet. */ + /* Except for joint IDs and weights which are treated separately and can + have a secondary set, ensure each known attribute gets bound only once. + There's 16 generic attributes at most, for each remember the mesh + attribute index that got bound to it first, or ~UnsignedInt{} if none + yet. */ /** @todo revisit when there are secondary generic texture coordinates, colors, etc */ Containers::StaticArray<16, UnsignedInt> boundAttributes{DirectInit, ~UnsignedInt{}}; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt jointIdAttributeCount = 0; + UnsignedInt weightAttributeCount = 0; + #endif for(UnsignedInt i = 0; i != meshData.attributeCount(); ++i) { - Containers::Optional attribute; + auto addAttribute = [&](GL::DynamicAttribute attribute, std::size_t offset) { + /* Ensure each attribute gets bound only once -- so for example + when there are two texture coordinate sets, we don't bind them + both to the same slot, effectively ignoring the first one. + Similarly warn if an attribute has a location conflicting with + another one (such as ObjectId and Bitangent). */ + if(boundAttributes[attribute.location()] != ~UnsignedInt{}) { + Warning{} << "MeshTools::compile(): ignoring" << meshData.attributeName(i) << meshData.attributeId(i) << "as its biding slot is already occupied by" << meshData.attributeName(boundAttributes[attribute.location()]) << meshData.attributeId(boundAttributes[attribute.location()]); + return; + } + + /* Remeber where this attribute got bound, including all subsequent + vectors for matrix attributes */ + for(UnsignedInt j = 0; j != attribute.vectors(); ++j) + boundAttributes[attribute.location() + j] = i; + + /* Negative strides are not supported by GL, zero strides are + understood as tightly packed instead of all attributes having + the same value */ + const Int stride = meshData.attributeStride(i); + CORRADE_ASSERT(stride > 0, + "MeshTools::compile():" << meshData.attributeName(i) << "stride of" << stride << "bytes isn't supported by OpenGL", ); + + /* For the first attribute move the buffer in, for all others use + the reference */ + if(vertices.id()) mesh.addVertexBuffer(std::move(vertices), + meshData.attributeOffset(i) + offset, stride, attribute); + else mesh.addVertexBuffer(verticesRef, meshData.attributeOffset(i) + offset, + stride, attribute); + }; /* Ignore implementation-specific formats because GL needs three separate values to describe them so there's no way to put them in a @@ -92,85 +128,101 @@ GL::Mesh compileInternal(const Trade::MeshData& meshData, GL::Buffer&& indices, case Trade::MeshAttribute::Position: /* Pick 3D position always, the format will properly reduce it to a 2-component version if needed */ - attribute.emplace(Shaders::GenericGL3D::Position{}, format); - break; + addAttribute(GL::DynamicAttribute{Shaders::GenericGL3D::Position{}, format}, 0); + continue; case Trade::MeshAttribute::TextureCoordinates: /** @todo have GenericGL2D derived from Generic that has all attribute definitions common for 2D and 3D */ - attribute.emplace(Shaders::GenericGL2D::TextureCoordinates{}, format); - break; + addAttribute(GL::DynamicAttribute{Shaders::GenericGL2D::TextureCoordinates{}, format}, 0); + continue; case Trade::MeshAttribute::Color: /** @todo have GenericGL2D derived from Generic that has all attribute definitions common for 2D and 3D */ /* Pick Color4 always, the format will properly reduce it to a 3-component version if needed */ - attribute.emplace(Shaders::GenericGL2D::Color4{}, format); - break; + addAttribute(GL::DynamicAttribute{Shaders::GenericGL2D::Color4{}, format}, 0); + continue; case Trade::MeshAttribute::Tangent: /* Pick Tangent4 always, the format will properly reduce it to a 3-component version if needed */ - attribute.emplace(Shaders::GenericGL3D::Tangent4{}, format); - break; + addAttribute(GL::DynamicAttribute{Shaders::GenericGL3D::Tangent4{}, format}, 0); + continue; case Trade::MeshAttribute::Bitangent: - attribute.emplace(Shaders::GenericGL3D::Bitangent{}, format); - break; + addAttribute(GL::DynamicAttribute{Shaders::GenericGL3D::Bitangent{}, format}, 0); + continue; case Trade::MeshAttribute::Normal: - attribute.emplace(Shaders::GenericGL3D::Normal{}, format); - break; + addAttribute(GL::DynamicAttribute{Shaders::GenericGL3D::Normal{}, format}, 0); + continue; #ifndef MAGNUM_TARGET_GLES2 + case Trade::MeshAttribute::JointIds: { + const UnsignedInt componentCount = meshData.attributeArraySize(i); + const std::size_t componentSize = vertexFormatSize(format); + for(UnsignedInt j = 0; j < componentCount; j += 4) { + const VertexFormat arrayFormat = vertexFormat(format, Math::min(componentCount - j, 4u), false); + /** @todo have GenericGL2D derived from Generic that has + all attribute definitions common for 2D and 3D */ + if(jointIdAttributeCount == 0) + addAttribute(GL::DynamicAttribute{Shaders::GenericGL2D::JointIds{}, arrayFormat}, j*componentSize); + else if(jointIdAttributeCount == 1) + addAttribute(GL::DynamicAttribute{Shaders::GenericGL2D::SecondaryJointIds{}, arrayFormat}, j*componentSize); + else { + Warning w; + w << "MeshTools::compile(): ignoring"; + if(j != 0) + w << "remaining" << componentCount - j << "components of"; + w << "joint ID / weights attribute" << meshData.attributeId(i) << Debug::nospace << ", only two sets are supported at most"; + break; + } + ++jointIdAttributeCount; + } + } continue; + case Trade::MeshAttribute::Weights: { + const UnsignedInt componentCount = meshData.attributeArraySize(i); + const std::size_t componentSize = vertexFormatSize(format); + for(UnsignedInt j = 0; j < componentCount; j += 4) { + const VertexFormat arrayFormat = vertexFormat(format, Math::min(componentCount - j, 4u), isVertexFormatNormalized(format)); + /** @todo have GenericGL2D derived from Generic that has + all attribute definitions common for 2D and 3D */ + if(weightAttributeCount == 0) + addAttribute(GL::DynamicAttribute{Shaders::GenericGL2D::Weights{}, arrayFormat}, j*componentSize); + else if(weightAttributeCount == 1) + addAttribute(GL::DynamicAttribute{Shaders::GenericGL2D::SecondaryWeights{}, arrayFormat}, j*componentSize); + else { + /* Warning printed for joints already, the mesh is + expected to have the same count of both so the + warning would be redundant */ + break; + } + ++weightAttributeCount; + } + } continue; case Trade::MeshAttribute::ObjectId: /** @todo have GenericGL2D derived from Generic that has all attribute definitions common for 2D and 3D */ - attribute.emplace(Shaders::GenericGL2D::ObjectId{}, format); - break; + addAttribute(GL::DynamicAttribute{Shaders::GenericGL2D::ObjectId{}, format}, 0); + continue; #endif - /* To avoid the compiler warning that we didn't handle an enum value. For these a runtime warning is printed below. */ /* LCOV_EXCL_START */ #ifdef MAGNUM_TARGET_GLES2 case Trade::MeshAttribute::ObjectId: - #endif + case Trade::MeshAttribute::JointIds: + case Trade::MeshAttribute::Weights: break; + #endif /* LCOV_EXCL_STOP */ } - if(!attribute) { - if(!Trade::isMeshAttributeCustom(meshData.attributeName(i)) || !(flags & CompileFlag::NoWarnOnCustomAttributes)) - Warning{} << "MeshTools::compile(): ignoring unknown/unsupported attribute" << meshData.attributeName(i); - continue; - } - - /* Ensure each attribute slot gets bound only once -- so for example - when there are two texture coordinate sets, we don't bind them both - to the same slot, effectively ignoring the first one. Similarly - warn if an attribute has a location conflicting with another one - (such as ObjectId and Bitangent). */ - if(boundAttributes[attribute->location()] != ~UnsignedInt{}) { - Warning{} << "MeshTools::compile(): ignoring" << meshData.attributeName(i) << meshData.attributeId(i) << "as its biding slot is already occupied by" << meshData.attributeName(boundAttributes[attribute->location()]) << meshData.attributeId(boundAttributes[attribute->location()]); - continue; - } - - /* Remeber where this attribute got bound, including all subsequent - vectors for matrix attributes */ - for(UnsignedInt j = 0; j != attribute->vectors(); ++j) - boundAttributes[attribute->location() + j] = i; - - /* Negative strides are not supported by GL, zero strides are - understood as tightly packed instead of all attributes having the - same value */ - const Int stride = meshData.attributeStride(i); - CORRADE_ASSERT(stride > 0, - "MeshTools::compile():" << meshData.attributeName(i) << "stride of" << stride << "bytes isn't supported by OpenGL", GL::Mesh{}); - - /* For the first attribute move the buffer in, for all others use the - reference */ - if(vertices.id()) mesh.addVertexBuffer(std::move(vertices), - meshData.attributeOffset(i), stride, *attribute); - else mesh.addVertexBuffer(verticesRef, meshData.attributeOffset(i), - stride, *attribute); + /* If we got here, the attribute was not recognized */ + if(!Trade::isMeshAttributeCustom(meshData.attributeName(i)) || !(flags & CompileFlag::NoWarnOnCustomAttributes)) + Warning{} << "MeshTools::compile(): ignoring unknown/unsupported attribute" << meshData.attributeName(i); } + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_INTERNAL_ASSERT(jointIdAttributeCount == weightAttributeCount); + #endif + if(meshData.isIndexed()) { /* If the type is implementation-specific, we have no way to know if it's strided, so just assume it is */ @@ -501,4 +553,28 @@ GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags) { CORRADE_IGNORE_DEPRECATED_POP #endif +#ifndef MAGNUM_TARGET_GLES2 +Containers::Pair compiledPerVertexJointCount(const Trade::MeshData& meshData) { + UnsignedInt primaryCount = 0, secondaryCount = 0; + for(UnsignedInt i = 0; i != meshData.attributeCount(); ++i) { + /* The mesh is expected to have the same count and array size of + JointIds and Weights, so it's enough to do it just for one of + them */ + if(meshData.attributeName(i) != Trade::MeshAttribute::JointIds) + continue; + + const UnsignedInt componentCount = meshData.attributeArraySize(i); + for(UnsignedInt i = 0; i < componentCount; i += 4) { + if(!primaryCount) + primaryCount = Math::min(componentCount - i, 4u); + else if(!secondaryCount) + secondaryCount = Math::min(componentCount - i, 4u); + else break; + } + } + + return {primaryCount, secondaryCount}; +} +#endif + }} diff --git a/src/Magnum/MeshTools/Compile.h b/src/Magnum/MeshTools/Compile.h index 1bf658826..20b5c0be4 100644 --- a/src/Magnum/MeshTools/Compile.h +++ b/src/Magnum/MeshTools/Compile.h @@ -121,6 +121,11 @@ possibly also an index buffer, if the mesh is indexed. - If the mesh contains @ref Trade::MeshAttribute::Color, these are bound to @ref Shaders::GenericGL::Color3 / @relativeref{Shaders::GenericGL,Color4} based on their type. +- If the mesh contains @ref Trade::MeshAttribute::JointIds and + @ref Trade::MeshAttribute::Weights, these are bound to + @ref Shaders::GenericGL::JointIds / @relativeref{Shaders::GenericGL,SecondaryJointIds} and + @relativeref{Shaders::GenericGL,Weights} / @relativeref{Shaders::GenericGL,SecondaryWeights} according to rules + described in @ref compiledPerVertexJointCount(). - If the mesh contains @ref Trade::MeshAttribute::ObjectId, these are bound to @ref Shaders::GenericGL::ObjectId. However, if the mesh contains a @ref Trade::MeshAttribute::Bitangent as well, only the first appearing of @@ -284,6 +289,49 @@ CORRADE_DEPRECATED("use compile(const Trade::MeshData&, CompileFlags) instead") CORRADE_IGNORE_DEPRECATED_POP #endif +#ifndef MAGNUM_TARGET_GLES2 +/** +@brief Compiled per-vertex joint count for given mesh data +@m_since_latest + +Returns the count of bound primary and secondary per-vertex joint IDs and +weights that a mesh returned from @ref compile(const Trade::MeshData&, CompileFlags) +would contain. The function goes over all @ref Trade::MeshAttribute::JointIds +and @relativeref{Trade::MeshAttribute,Weights} attributes present in the mesh +and assigns them to the primary and secondary binding points: + +- If the mesh contains just one instance of joint ID and weight attributes + and their @ref Trade::MeshData::attributeArraySize() is not larger than + 4, they occupy just the primary binding slot. The second returned value + is @cpp 0 @ce. +- If the mesh contains more than one instance of joint ID and weight + attributes and array size of the first instance is not larger than 4, the + first instance goes to the primary binding slot and the first up to 4 + array components of the second instance go to the secondary slot. Remaining + array components of the second instance and all remaining instances of + joint ID and weight attributes are ignored. +- If array size of the first instance of joint ID and weight attributes is + larger than 4, the first slot uses the first 4 array components and the + second the next up to 4 array components. Remaining array components of the + first instance and all remaining instances of joint ID and weight + attributes are ignored. + +Useful to get subsequently fed to +@ref Shaders::FlatGL::Configuration::setJointCount() or to +@ref Shaders::FlatGL::setPerVertexJointCount() if +@ref Shaders::FlatGL::Flag::DynamicPerVertexJointCount is enabled, and +similarly with other builtin shaders. + +@note This function is available only if Magnum is compiled with + @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features + for more information. + +@requires_gles30 Not defined on OpenGL ES 2.0 builds. +@requires_webgl20 Not defined on WebGL 1.0 builds. +*/ +MAGNUM_MESHTOOLS_EXPORT Containers::Pair compiledPerVertexJointCount(const Trade::MeshData& meshData); +#endif + }} #else #error this header is available only in the OpenGL build diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 5281f7976..7dcd839a0 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -105,6 +105,7 @@ if(MAGNUM_BUILD_GL_TESTS) CompileTestFiles/phong.tga CompileTestFiles/phong-smooth.tga CompileTestFiles/phong-flat.tga + CompileTestFiles/skinning.tga CompileTestFiles/textured2D.tga CompileTestFiles/textured3D.tga) target_include_directories(MeshToolsCompileGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) diff --git a/src/Magnum/MeshTools/Test/CompileGLTest.cpp b/src/Magnum/MeshTools/Test/CompileGLTest.cpp index 8701e36a3..3cdb21709 100644 --- a/src/Magnum/MeshTools/Test/CompileGLTest.cpp +++ b/src/Magnum/MeshTools/Test/CompileGLTest.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,8 @@ #include "Magnum/GL/Texture.h" #include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Color.h" +#include "Magnum/Math/Half.h" +#include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" #include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/Duplicate.h" @@ -106,6 +109,12 @@ struct CompileGLTest: GL::OpenGLTester { void packedAttributes(); + #ifndef MAGNUM_TARGET_GLES2 + /* Tests also compiledPerVertexJointCount() */ + void skinning(); + void skinningPackedAttributes(); + #endif + void conflictingAttributes(); void unsupportedIndexStride(); @@ -203,6 +212,138 @@ const struct { {"positions, object id, nonindexed", Flag::ObjectId|Flag::NonIndexed, {}} }; +constexpr std::ptrdiff_t SkinningDataStride = sizeof(Vector2) + 7*sizeof(UnsignedInt) + 7*sizeof(Float); + +const struct { + const char* name; + Containers::Array attributes; + UnsignedInt expectedJointCount, expectedSecondaryJointCount; + const char* expectedMessage; +} SkinningData[]{ + /* First two and last two weights are zeros thus the middle 3 components + are enough to give the full output */ + {"single 3-component attribute", {InPlaceInit, { + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2) + 2*sizeof(UnsignedInt), + 4, SkinningDataStride, 3}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt) + 2*sizeof(Float), + 4, SkinningDataStride, 3}, + }}, 3, 0, ""}, + {"single 7-component attribute", {InPlaceInit, { + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2), + 4, SkinningDataStride, 7}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt), + 4, SkinningDataStride, 7}, + }}, 4, 3, ""}, + {"3-component and a 4-component attribute", {InPlaceInit, { + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2), + 4, SkinningDataStride, 3}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt), + 4, SkinningDataStride, 3}, + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2) + 3*sizeof(UnsignedInt), + 4, SkinningDataStride, 4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt) + 3*sizeof(Float), + 4, SkinningDataStride, 4}, + }}, 3, 4, ""}, + {"4-component and a 3-component attribute", {InPlaceInit, { + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2), + 4, SkinningDataStride, 4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt), + 4, SkinningDataStride, 4}, + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2) + 4*sizeof(UnsignedInt), + 4, SkinningDataStride, 3}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt) + 4*sizeof(Float), + 4, SkinningDataStride, 3}, + }}, 4, 3, ""}, + {"1-component and a 5-component attribute", {InPlaceInit, { + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2) + 1*sizeof(UnsignedInt), + 4, SkinningDataStride, 1}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt) + 1*sizeof(Float), + 4, SkinningDataStride, 1}, + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2) + 2*sizeof(UnsignedInt), + 4, SkinningDataStride, 5}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt) + 2*sizeof(Float), + 4, SkinningDataStride, 5}, + }}, 1, 4, "MeshTools::compile(): ignoring remaining 1 components of joint ID / weights attribute 1, only two sets are supported at most\n"}, + {"3-component, 2-component and a 2-component attribute", {InPlaceInit, { + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2), + 4, SkinningDataStride, 3}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt), + 4, SkinningDataStride, 3}, + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2) + 3*sizeof(UnsignedInt), + 4, SkinningDataStride, 2}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt) + 3*sizeof(Float), + 4, SkinningDataStride, 2}, + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2) + 5*sizeof(UnsignedInt), + 4, SkinningDataStride, 2}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt) + 5*sizeof(Float), + 4, SkinningDataStride, 2}, + }}, 3, 2, "MeshTools::compile(): ignoring joint ID / weights attribute 2, only two sets are supported at most\n"}, + {"single 7-component attribute, and then a 9-component attribute", {InPlaceInit, { + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + sizeof(Vector2), + 4, SkinningDataStride, 7}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + sizeof(Vector2) + 7*sizeof(UnsignedInt), + 4, SkinningDataStride, 7}, + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedInt, + 0, + 4, SkinningDataStride, 9}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + 0, + 4, SkinningDataStride, 9}, + /* The warning should be printed just once for the whole attribute, not + again for every group of four components */ + }}, 4, 3, "MeshTools::compile(): ignoring joint ID / weights attribute 1, only two sets are supported at most\n"}, +}; + constexpr std::ptrdiff_t ConflictingAttributesDataStride = 3*sizeof(Vector2) + sizeof(UnsignedInt) + sizeof(Vector3); const struct { @@ -243,6 +384,7 @@ const struct { }}, Flag::ObjectId, 26234, "flat2D.tga", "ignoring Trade::MeshAttribute::Bitangent 0 as its biding slot is already occupied by Trade::MeshAttribute::ObjectId 0"}, #endif + /* Conflicting skinning attributes tested directly in skinning() */ /** @todo test also a conflict with instanced transformation + secondary joints & weights, once instanced transformation is a builtin attribute */ @@ -313,6 +455,17 @@ CompileGLTest::CompileGLTest() { &CompileGLTest::renderSetup, &CompileGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&CompileGLTest::skinning}, + Containers::arraySize(SkinningData), + &CompileGLTest::renderSetup, + &CompileGLTest::renderTeardown); + + addTests({&CompileGLTest::skinningPackedAttributes}, + &CompileGLTest::renderSetup, + &CompileGLTest::renderTeardown); + #endif + addInstancedTests({&CompileGLTest::conflictingAttributes}, Containers::arraySize(ConflictingAttributesData), &CompileGLTest::renderSetup, @@ -1140,6 +1293,163 @@ void CompileGLTest::packedAttributes() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +void CompileGLTest::skinning() { + auto&& data = SkinningData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Same as in FlatGLTest/PhongGLTest/MeshVisualizerGLTest, just padded + with two dummy values at the beginning and at the end */ + struct Vertex { + Vector2 position; + UnsignedInt jointIds[7]; + Float weights[7]; + } vertexData[]{ + /* 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, 0, 0, 2, 0, 0, 0}, + {0.0f, 0.0f, 1.0f, 50.0f, 0.5f, 0.0f, 0.0f}}, + {{ 1.0f, 1.0f}, + {0, 0, 1, 0, 0, 0, 0}, + {0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f}}, + {{-1.0f, -1.0f}, + {0, 0, 3, 4, 4, 0, 0}, + {0.0f, 0.0f, 0.5f, 0.25f, 0.25f, 0.0f, 0.0f}}, + {{-1.0f, 1.0f}, + {0, 0, 1, 0, 4, 0, 0}, + {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f}}, + }; + static_assert(sizeof(Vertex) == SkinningDataStride, ""); + auto vertices = Containers::stridedArrayView(vertexData); + + 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}, + }; + + Containers::Array attributeData; + arrayAppend(attributeData, InPlaceInit, Trade::MeshAttribute::Position, + vertices.slice(&Vertex::position)); + arrayAppend(attributeData, data.attributes); + + Trade::MeshData meshData{MeshPrimitive::TriangleStrip, {}, vertexData, std::move(attributeData)}; + + Containers::Pair jointCount = compiledPerVertexJointCount(meshData); + CORRADE_COMPARE(jointCount.first(), data.expectedJointCount); + CORRADE_COMPARE(jointCount.second(), data.expectedSecondaryJointCount); + + GL::Mesh mesh{NoCreate}; + std::ostringstream out; + { + Warning redirectWarning{&out}; + mesh = compile(meshData); + } + CORRADE_COMPARE(out.str(), data.expectedMessage); + + 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."); + + _framebuffer.clear(GL::FramebufferClear::Color); + + Shaders::FlatGL2D shader{Shaders::FlatGL2D::Configuration{} + .setJointCount(Containers::arraySize(jointMatrices), jointCount.first(), jointCount.second())}; + shader.setJointMatrices(jointMatrices) + .setTransformationProjectionMatrix(Matrix3::scaling(Vector2{0.5f})) + .draw(mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Path::join(MESHTOOLS_TEST_DIR, "CompileTestFiles/skinning.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +void CompileGLTest::skinningPackedAttributes() { + /* Same as skinning(), just with the attributes packed, and packed + differently for each set; and using a 3D shader */ + struct Vertex { + Vector2 position; + UnsignedShort jointIds[4]; + UnsignedByte secondaryJointIds[3]; + Float weights[4]; + Half secondaryWeights[3]; + } vertexData[]{ + {{ 1.0f, -1.0f}, + {0, 0, 0, 2}, {0, 0, 0}, + {0.0f, 0.0f, 1.0f, 50.0f}, { 0.5_h, 0.0_h, 0.0_h}}, + {{ 1.0f, 1.0f}, + {0, 0, 1, 0}, {0, 0, 0}, + {0.0f, 0.0f, 0.5f, 0.5f}, { 0.0_h, 0.0_h, 0.0_h}}, + {{-1.0f, -1.0f}, + {0, 0, 3, 4}, {4, 0, 0}, + {0.0f, 0.0f, 0.5f, 0.25f}, {0.25_h, 0.0_h, 0.0_h}}, + {{-1.0f, 1.0f}, + {0, 0, 1, 0}, {4, 0, 0}, + {0.0f, 0.0f, 1.0f, 0.0f}, { 0.0_h, 0.0_h, 0.0_h}}, + }; + auto vertices = Containers::stridedArrayView(vertexData); + + 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}, + }; + + Trade::MeshData meshData{MeshPrimitive::TriangleStrip, {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + vertices.slice(&Vertex::position)}, + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedShort, + vertices.slice(&Vertex::jointIds), 4}, + Trade::MeshAttributeData{Trade::MeshAttribute::JointIds, + VertexFormat::UnsignedByte, + vertices.slice(&Vertex::secondaryJointIds), 3}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Float, + vertices.slice(&Vertex::weights), 4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Weights, + VertexFormat::Half, + vertices.slice(&Vertex::secondaryWeights), 3}, + }}; + + GL::Mesh mesh = compile(meshData); + + 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."); + + _framebuffer.clear(GL::FramebufferClear::Color); + + Shaders::FlatGL3D shader{Shaders::FlatGL3D::Configuration{} + .setJointCount(Containers::arraySize(jointMatrices), 4, 3)}; + shader.setJointMatrices(jointMatrices) + .setTransformationProjectionMatrix(Matrix4::scaling(Vector3{0.5f})) + .draw(mesh); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Path::join(MESHTOOLS_TEST_DIR, "CompileTestFiles/skinning.tga"), + (DebugTools::CompareImageToFile{_manager})); +} +#endif + void CompileGLTest::conflictingAttributes() { auto&& data = ConflictingAttributesData[testCaseInstanceId()]; setTestCaseDescription(data.name); diff --git a/src/Magnum/MeshTools/Test/CompileTestFiles/skinning.tga b/src/Magnum/MeshTools/Test/CompileTestFiles/skinning.tga new file mode 100644 index 000000000..6598cc312 Binary files /dev/null and b/src/Magnum/MeshTools/Test/CompileTestFiles/skinning.tga differ