diff --git a/doc/changelog.dox b/doc/changelog.dox index 1b6e10446..78fc6a8c0 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -207,6 +207,9 @@ See also: available also in multi-draw and instanced scenarios - @ref Shaders::FlatGL and @ref Shaders::PhongGL now support object ID textures in addition to uniform and per-vertex object ID +- Support for instanced drawing in @ref Shaders::MeshVisualizerGL2D and + @ref Shaders::MeshVisualizerGL3D for better feature parity with the other + sahders - Added @ref Shaders::PhongGL::setNormalTextureScale(), consuming the recently added @ref Trade::MaterialAttribute::NormalTextureScale material attribute diff --git a/doc/snippets/MagnumShaders-gl.cpp b/doc/snippets/MagnumShaders-gl.cpp index 33ee36319..be439bb2f 100644 --- a/doc/snippets/MagnumShaders-gl.cpp +++ b/doc/snippets/MagnumShaders-gl.cpp @@ -615,6 +615,22 @@ mesh.setInstanceCount(Containers::arraySize(instanceData)) /* [PhongGL-usage-instancing] */ } +{ +GL::Mesh mesh; +/* [MeshVisualizerGL2D-usage-instancing] */ +Matrix3 instancedTransformations[] { + Matrix3::translation({1.0f, 2.0f}), + Matrix3::translation({2.0f, 1.0f}), + Matrix3::translation({3.0f, 0.0f}), + // ... +}; + +mesh.setInstanceCount(Containers::arraySize(instancedTransformations)) + .addVertexBufferInstanced(GL::Buffer{instancedTransformations}, 1, 0, + Shaders::MeshVisualizerGL2D::TransformationMatrix{}); +/* [MeshVisualizerGL2D-usage-instancing] */ +} + /* internal compiler error: in gimplify_init_constructor, at gimplify.c:4271 on GCC 4.8 in the [60] array */ #if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 @@ -801,6 +817,30 @@ shader } #endif +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +{ +GL::Mesh mesh; +/* [MeshVisualizerGL3D-usage-instancing] */ +struct { + Matrix4 transformation; + Matrix3x3 normal; +} instanceData[] { + {Matrix4::translation({1.0f, 2.0f, 0.0f}), {}}, + {Matrix4::translation({2.0f, 1.0f, 0.0f}), {}}, + {Matrix4::translation({3.0f, 0.0f, 1.0f}), {}}, + // ... +}; +for(auto& instance: instanceData) + instance.normal = instance.transformation.normalMatrix(); + +mesh.setInstanceCount(Containers::arraySize(instanceData)) + .addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, + Shaders::MeshVisualizerGL3D::TransformationMatrix{}, + Shaders::MeshVisualizerGL3D::NormalMatrix{}); +/* [MeshVisualizerGL3D-usage-instancing] */ +} +#endif + #if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 { /* [PhongGL-usage-colored1] */ diff --git a/src/Magnum/Shaders/MeshVisualizer.vert b/src/Magnum/Shaders/MeshVisualizer.vert index 8e3a8732f..745a7192d 100644 --- a/src/Magnum/Shaders/MeshVisualizer.vert +++ b/src/Magnum/Shaders/MeshVisualizer.vert @@ -255,6 +255,26 @@ layout(location = OBJECT_ID_ATTRIBUTE_LOCATION) in highp uint instanceObjectId; #endif +#ifdef INSTANCED_TRANSFORMATION +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = TRANSFORMATION_MATRIX_ATTRIBUTE_LOCATION) +#endif +in highp + #ifdef TWO_DIMENSIONS + mat3 + #else + mat4 + #endif + instancedTransformationMatrix; + +#if defined(TANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = NORMAL_MATRIX_ATTRIBUTE_LOCATION) +#endif +in highp mat3 instancedNormalMatrix; +#endif +#endif + /* Outputs */ #if defined(WIREFRAME_RENDERING) && defined(NO_GEOMETRY_SHADER) @@ -342,25 +362,40 @@ void main() { #endif #ifdef TWO_DIMENSIONS - gl_Position.xywz = vec4(transformationProjectionMatrix*vec3(position, 1.0), 0.0); + gl_Position.xywz = vec4(transformationProjectionMatrix* + #ifdef INSTANCED_TRANSFORMATION + instancedTransformationMatrix* + #endif + vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) - highp const vec4 transformedPosition4 = transformationMatrix*position; + highp const vec4 transformedPosition4 = transformationMatrix* + #ifdef INSTANCED_TRANSFORMATION + instancedTransformationMatrix* + #endif + position; gl_Position = projectionMatrix*transformedPosition4; #else #error #endif + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) + mat3 finalNormalMatrix = normalMatrix + #ifdef INSTANCED_TRANSFORMATION + *instancedNormalMatrix + #endif + ; + #endif #ifdef TANGENT_DIRECTION - tangentEndpoint = projectionMatrix*(transformedPosition4 + vec4(normalize(normalMatrix*tangent.xyz)*lineLength, 0.0)); + tangentEndpoint = projectionMatrix*(transformedPosition4 + vec4(normalize(finalNormalMatrix*tangent.xyz)*lineLength, 0.0)); #endif #ifdef BITANGENT_FROM_TANGENT_DIRECTION vec3 bitangent = cross(normal, tangent.xyz)*tangent.w; #endif #if defined(BITANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) - bitangentEndpoint = projectionMatrix*(transformedPosition4 + vec4(normalize(normalMatrix*bitangent)*lineLength, 0.0)); + bitangentEndpoint = projectionMatrix*(transformedPosition4 + vec4(normalize(finalNormalMatrix*bitangent)*lineLength, 0.0)); #endif #ifdef NORMAL_DIRECTION - normalEndpoint = projectionMatrix*(transformedPosition4 + vec4(normalize(normalMatrix*normal)*lineLength, 0.0)); + normalEndpoint = projectionMatrix*(transformedPosition4 + vec4(normalize(finalNormalMatrix*normal)*lineLength, 0.0)); #endif #if defined(WIREFRAME_RENDERING) && defined(NO_GEOMETRY_SHADER) diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 9a61eee5d..8d8c9e27e 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -162,6 +162,9 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra vert.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + #endif + .addSource(_flags & FlagBase::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") + #ifndef MAGNUM_TARGET_GLES2 .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "") #endif @@ -414,6 +417,8 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags if(flags >= Flag::InstancedObjectId) bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif + if(flags & Flag::InstancedTransformation) + bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!context.isVersionSupported(GL::Version::GL310)) @@ -732,6 +737,13 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags if(flags >= Flag::InstancedObjectId) bindAttributeLocation(ObjectId::Location, "instanceObjectId"); #endif + if(flags & Flag::InstancedTransformation) { + bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) + bindAttributeLocation(NormalMatrix::Location, "instancedNormalMatrix"); + #endif + } #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & Flag::TangentDirection || flags & Flag::BitangentFromTangentDirection) @@ -1014,6 +1026,9 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flag value) { #ifndef MAGNUM_TARGET_GLES2 _c(ObjectId) _c(InstancedObjectId) + #endif + _c(InstancedTransformation) + #ifndef MAGNUM_TARGET_GLES2 _c(VertexId) #ifndef MAGNUM_TARGET_WEBGL _c(PrimitiveId) @@ -1048,6 +1063,9 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flag value) { #ifndef MAGNUM_TARGET_GLES2 _c(ObjectId) _c(InstancedObjectId) + #endif + _c(InstancedTransformation) + #ifndef MAGNUM_TARGET_GLES2 _c(VertexId) #ifndef MAGNUM_TARGET_WEBGL _c(PrimitiveId) @@ -1074,6 +1092,9 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flags value) { #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGL2D::Flag::InstancedObjectId, /* Superset of ObjectId */ MeshVisualizerGL2D::Flag::ObjectId, + #endif + MeshVisualizerGL2D::Flag::InstancedTransformation, + #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGL2D::Flag::VertexId, MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL @@ -1102,6 +1123,9 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flags value) { #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGL3D::Flag::InstancedObjectId, /* Superset of ObjectId */ MeshVisualizerGL3D::Flag::ObjectId, + #endif + MeshVisualizerGL3D::Flag::InstancedTransformation, + #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGL3D::Flag::VertexId, MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index 8f9b8e228..a4bc3ead2 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -49,6 +49,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr complex */ Wireframe = 1 << 0, NoGeometryShader = 1 << 1, + InstancedTransformation = 1 << 13, #ifndef MAGNUM_TARGET_GLES2 ObjectId = 1 << 12, InstancedObjectId = (1 << 2)|ObjectId, @@ -142,7 +143,25 @@ configure the shader. The shader expects that you enable wireframe visualization by passing an appropriate @ref Flag to the constructor --- there's no default behavior with nothing enabled. The shader is a 2D variant of @ref MeshVisualizerGL3D with -mostly identical workflow. See its documentation for more information. +mostly identical workflow. See its documentation for more information, +workflows that differ are shown below. + +@section Shaders-MeshVisualizerGL2D-instancing Instanced rendering + +Enabling @ref Flag::InstancedTransformation will turn the shader into an +instanced one. It'll take per-instance transformation from the +@ref TransformationMatrix attribute, applying it before the matrix set by +@ref setTransformationProjectionMatrix(). The snippet below shows adding a +buffer with per-instance transformation to a mesh: + +@snippet MagnumShaders-gl.cpp MeshVisualizerGL2D-usage-instancing + +@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} + in OpenGL ES 2.0. +@requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} in WebGL + 1.0. */ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisualizerGLBase { public: @@ -177,6 +196,21 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua typedef GenericGL2D::ObjectId ObjectId; #endif + /** + * @brief (Instanced) transformation matrix + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Matrix3. + * Used only if @ref Flag::InstancedTransformation is set. + * @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} in OpenGL ES 2.0. + * @requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} + * in WebGL 1.0. + */ + typedef GenericGL2D::TransformationMatrix TransformationMatrix; + enum: UnsignedInt { /** * Color shader output. @ref shaders-generic "Generic output", @@ -259,6 +293,24 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua #endif #endif + /** + * Instanced transformation. Retrieves a per-instance + * transformation matrix from the @ref TransformationMatrix + * attribute and uses it together with the matrix coming from + * @ref setTransformationProjectionMatrix() or + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix + * (first the per-instance, then the uniform matrix). See + * @ref Shaders-MeshVisualizerGL2D-instancing for more information. + * @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} in OpenGL ES 2.0. + * @requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} + * in WebGL 1.0. + * @m_since_latest + */ + InstancedTransformation = 1 << 13, + #ifndef MAGNUM_TARGET_GLES2 /** * Use uniform buffers. Expects that uniform data are supplied via @@ -420,7 +472,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @brief Set transformation and projection matrix * @return Reference to self (for method chaining) * - * Initial value is an identity matrix. + * Initial value is an identity matrix. If + * @ref Flag::InstancedTransformation is set, the per-instance + * transformation matrix coming from the @ref TransformationMatrix + * attribute is applied first, before this one. * * Expects that @ref Flag::UniformBuffers is not set, in that case fill * @ref TransformationProjectionUniform2D::transformationProjectionMatrix @@ -855,6 +910,27 @@ non-indexed @ref MeshPrimitive::Triangles. is not available in WebGL 1.0. @requires_webgl20 `gl_VertexID` is not available in WebGL 1.0. +@section Shaders-MeshVisualizerGL3D-instancing Instanced rendering + +Enabling @ref Flag::InstancedTransformation will turn the shader into an +instanced one. It'll take per-instance transformation from the +@ref TransformationMatrix attribute, applying it before the matrix set by +@ref setTransformationMatrix(). If one of @ref Flag::TangentDirection, +@ref Flag::BitangentDirection or @ref Flag::NormalDirection is set, +additionally also a normal matrix from the @ref NormalMatrix attribute is +taken, applied before the matrix set by @ref setNormalMatrix(). The snippet +below shows adding a buffer with per-instance transformation to a mesh, +including a normal matrix attribute for correct TBN visualization: + +@snippet MagnumShaders-gl.cpp MeshVisualizerGL3D-usage-instancing + +@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} + in OpenGL ES 2.0. +@requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} in WebGL + 1.0. + @section Shaders-MeshVisualizerGL3D-ubo Uniform buffers See @ref shaders-usage-ubo for a high-level overview that applies to all @@ -974,6 +1050,40 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua typedef GenericGL3D::ObjectId ObjectId; #endif + /** + * @brief (Instanced) transformation matrix + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Matrix4. + * Used only if @ref Flag::InstancedTransformation is set. + * @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} in OpenGL ES 2.0. + * @requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} + * in WebGL 1.0. + */ + typedef GenericGL3D::TransformationMatrix TransformationMatrix; + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + /** + * @brief (Instanced) normal matrix + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Matrix3x3. + * Used only if @ref Flag::InstancedTransformation and at least one of + * @ref Flag::TangentDirection, @ref Flag::BitangentDirection or + * @ref Flag::NormalDirection is set. + * @requires_gl33 Extension @gl_extension{ARB,geometry_shader4} and + * @gl_extension{ARB,instanced_arrays} + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} + * / @gl_extension{EXT,geometry_shader} + * @requires_gles Geometry shaders are not available in WebGL. + */ + typedef GenericGL3D::NormalMatrix NormalMatrix; + #endif + enum: UnsignedInt { /** * Color shader output. @ref shaders-generic "Generic output", @@ -1164,6 +1274,26 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua NormalDirection = 1 << 9, #endif + /** + * Instanced transformation. Retrieves a per-instance + * transformation and normal matrix from the + * @ref TransformationMatrix / @ref NormalMatrix attributes and + * uses them together with matrices coming from + * @ref setTransformationMatrix() and @ref setNormalMatrix() or + * @ref TransformationUniform3D::transformationMatrix and + * @ref MeshVisualizerDrawUniform3D::normalMatrix (first the + * per-instance, then the uniform matrix). See + * @ref Shaders-MeshVisualizerGL3D-instancing for more information. + * @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} in OpenGL ES 2.0. + * @requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} + * in WebGL 1.0. + * @m_since_latest + */ + InstancedTransformation = 1 << 13, + #ifndef MAGNUM_TARGET_GLES2 /** * Use uniform buffers. Expects that uniform data are supplied via @@ -1355,7 +1485,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @brief Set transformation matrix * @return Reference to self (for method chaining) * - * Initial value is an identity matrix. + * Initial value is an identity matrix. If + * @ref Flag::InstancedTransformation is set, the per-instance + * transformation coming from the @ref TransformationMatrix attribute + * is applied first, before this one. * * Expects that @ref Flag::UniformBuffers is not set, in that case fill * @ref TransformationUniform3D::transformationMatrix and call @@ -1387,7 +1520,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is * enabled. The matrix doesn't need to be normalized, as * renormalization is done per-fragment anyway. - * Initial value is an identity matrix. + * Initial value is an identity matrix. If + * @ref Flag::InstancedTransformation is set, the per-instance normal + * matrix coming from the @ref NormalMatrix attribute is applied first, + * before this one. * * Expects that @ref Flag::UniformBuffers is not set, in that case fill * @ref MeshVisualizerDrawUniform3D::normalMatrix and call diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index b9572dc46..2adffd2ca 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -234,6 +234,15 @@ if(BUILD_GL_TESTS) MeshVisualizerTestFiles/wireframe-wide3D.tga MeshVisualizerTestFiles/wireframe2D.tga MeshVisualizerTestFiles/wireframe3D.tga + MeshVisualizerTestFiles/instanced-wireframe2D.tga + MeshVisualizerTestFiles/instanced-wireframe3D.tga + MeshVisualizerTestFiles/instanced-wireframe-nogeo2D.tga + MeshVisualizerTestFiles/instanced-wireframe-nogeo3D.tga + MeshVisualizerTestFiles/instanced-wireframe-tbn3D.tga + MeshVisualizerTestFiles/instanced-vertexid2D.tga + MeshVisualizerTestFiles/instanced-vertexid3D.tga + MeshVisualizerTestFiles/instanced-instancedobjectid2D.tga + MeshVisualizerTestFiles/instanced-instancedobjectid3D.tga MeshVisualizerTestFiles/multidraw-wireframe2D.tga MeshVisualizerTestFiles/multidraw-wireframe3D.tga MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index d4e6a183a..f14242248 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -49,6 +49,7 @@ #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/Math/Swizzle.h" #include "Magnum/MeshTools/Combine.h" #include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/Duplicate.h" @@ -160,6 +161,9 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { template void renderTangentBitangentNormal(); #endif + template void renderInstanced2D(); + template void renderInstanced3D(); + #ifndef MAGNUM_TARGET_GLES2 void renderMulti2D(); void renderMulti3D(); @@ -593,6 +597,65 @@ constexpr struct { }; #endif +const struct { + const char* name; + const char* expected; + MeshVisualizerGL2D::Flags flags; + Float maxThreshold, meanThreshold; +} RenderInstancedData2D[]{ + #ifndef MAGNUM_TARGET_WEBGL + {"wireframe", "instanced-wireframe2D.tga", + MeshVisualizerGL2D::Flag::Wireframe, + 0.0f, 0.0f}, + #endif + {"wireframe w/o GS", "instanced-wireframe-nogeo2D.tga", + MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, + /* SwiftShader has a few rounding errors on edges */ + 73.67f, 0.230f}, + #ifndef MAGNUM_TARGET_GLES2 + {"vertex ID", "instanced-vertexid2D.tga", + MeshVisualizerGL2D::Flag::VertexId, + /* SwiftShader has a few rounding errors on edges */ + 138.7f, 0.08f}, + {"instanced object ID", "instanced-instancedobjectid2D.tga", + MeshVisualizerGL2D::Flag::InstancedObjectId, + /* SwiftShader has a few rounding errors on edges */ + 133.0f, 0.12f}, + #endif +}; + +const struct { + const char* name; + const char* expected; + MeshVisualizerGL3D::Flags flags; + Float maxThreshold, meanThreshold; +} RenderInstancedData3D[]{ + #ifndef MAGNUM_TARGET_WEBGL + {"wireframe", "instanced-wireframe3D.tga", + MeshVisualizerGL3D::Flag::Wireframe, + 0.0f, 0.0f}, + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + {"wireframe + TBN", "instanced-wireframe-tbn3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, + 0.0f, 0.0f}, + #endif + {"wireframe w/o GS", "instanced-wireframe-nogeo3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, + /* SwiftShader has a minor rounding error */ + 7.334f, 0.192f}, + #ifndef MAGNUM_TARGET_GLES2 + {"vertex ID", "instanced-vertexid3D.tga", + MeshVisualizerGL3D::Flag::VertexId, + /* SwiftShader has a minor rounding error */ + 5.667f, 0.034f}, + {"instanced object ID", "instanced-instancedobjectid3D.tga", + MeshVisualizerGL3D::Flag::InstancedObjectId, + /* SwiftShader has an off-by-one error on certain colors */ + 0.334f, 0.042f}, + #endif +}; + #ifndef MAGNUM_TARGET_GLES2 constexpr struct { const char* name; @@ -945,6 +1008,28 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::renderTeardown); #endif + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &MeshVisualizerGLTest::renderInstanced2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderInstanced2D, + #endif + }, + Containers::arraySize(RenderInstancedData2D), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &MeshVisualizerGLTest::renderInstanced3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderInstanced3D, + #endif + }, + Containers::arraySize(RenderInstancedData3D), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 addInstancedTests({&MeshVisualizerGLTest::renderMulti2D}, Containers::arraySize(RenderMultiData2D), @@ -2060,7 +2145,8 @@ template void MeshVisualizerGLTest::renderDefault /* Dropping the alpha channel, as it's always 1.0 */ Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), Utility::Directory::join(_testDir, "MeshVisualizerTestFiles/defaults-objectid2D.tga"), - (DebugTools::CompareImageToFile{_manager})); + /* SwiftShader has a few rounding errors on edges */ + (DebugTools::CompareImageToFile{_manager, 24.67f, 0.11f})); } template void MeshVisualizerGLTest::renderDefaultsObjectId3D() { @@ -2116,7 +2202,9 @@ template void MeshVisualizerGLTest::renderDefault /* Dropping the alpha channel, as it's always 1.0 */ Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), Utility::Directory::join(_testDir, "MeshVisualizerTestFiles/defaults-objectid3D.tga"), - (DebugTools::CompareImageToFile{_manager})); + /* SwiftShader has a few rounding errors on edges and off-by-two + pixels */ + (DebugTools::CompareImageToFile{_manager, 24.67f, 2.55f})); } template void MeshVisualizerGLTest::renderDefaultsInstancedObjectId2D() { @@ -3425,6 +3513,355 @@ template void MeshVisualizerGLTest::renderTangent } #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(!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); + /* 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; + UnsignedInt objectId; + } instanceData[] { + /* 6 gets added to objectId, wrapping it around to 0, making it + visually close to the multidraw test */ + {Matrix3::translation({-1.25f, -1.25f}), 6}, + {Matrix3::translation({ 1.25f, -1.25f}), 10}, + {Matrix3::translation({ 0.00f, 1.25f}), 14}, + }; + + circle + .addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, + MeshVisualizerGL2D::TransformationMatrix{}, + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGL2D::ObjectId{} + #else + 4 + #endif + ) + .setInstanceCount(3); + + MeshVisualizerGL2D shader{ + MeshVisualizerGL2D::Flag::InstancedTransformation|data.flags|flag}; + shader.setViewportSize(Vector2{RenderSize}); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & (MeshVisualizerGL2D::Flag::VertexId|MeshVisualizerGL2D::Flag::ObjectId)) + shader.bindColorMapTexture(_colorMapTexture); + #endif + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader + .setColor(0xffffcc_rgbf) + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})); + if(data.flags & MeshVisualizerGL2D::Flag::Wireframe) + shader.setWireframeColor(0xcc0000_rgbf); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & MeshVisualizerGL2D::Flag::VertexId) + shader.setColorMapTransformation(0.5f/circleData.vertexCount(), 1.0f/circleData.vertexCount()); + else if(data.flags & MeshVisualizerGL2D::Flag::ObjectId) { + /* To make this visually close to the multidraw test */ + shader + .setObjectId(6) + .setColorMapTransformation(0.5f/12, 1.0f/12); + } + #endif + + shader.draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + .setObjectId(6) + }}; + MeshVisualizerMaterialUniform materialUniformData[1]; + (*materialUniformData) + .setColor(0xffffcc_rgbf) + .setWireframeColor(0xcc0000_rgbf); + if(data.flags & MeshVisualizerGL2D::Flag::VertexId) + materialUniformData->setColorMapTransformation(0.5f/circleData.vertexCount(), 1.0f/circleData.vertexCount()); + else if(data.flags & MeshVisualizerGL2D::Flag::ObjectId) + materialUniformData->setColorMapTransformation(0.5f/12, 1.0f/12); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialUniformData}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + 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."); + + /* + First circle should be lower left, second lower right, third up center. + + - Wireframe all looking the same (the only instanced thing that can + differ is the transformation + - Vertex ID should all have the full color map range + - Object ID should be visually close to the multidraw case, except + that each circle is just a single color + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "MeshVisualizerTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +template void MeshVisualizerGLTest::renderInstanced3D() { + auto&& data = RenderInstancedData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + 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 + } + #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(((data.flags & MeshVisualizerGL3D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader)) || (data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection))) { + #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 & MeshVisualizerGL3D::Flag::ObjectId) && !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::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 sphereData = Primitives::uvSphereSolid(2, 4, Primitives::UVSphereFlag::Tangents); + /* For a GS-less wireframe we have to deindex the mesh */ + if(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) + sphereData = MeshTools::duplicate(sphereData); + GL::Mesh sphere = MeshTools::compile(sphereData); + + /* Three spheres, each in a different location. To test normal matrix + concatenation, everything is rotated 90° on Y, thus X is now -Z and Z is + now X. */ + struct { + Matrix4 transformation; + Matrix3x3 normal; + UnsignedInt objectId; + } instanceData[] { + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{-1.25f, -1.25f, 0.0f}))*Matrix4::rotationY(-45.0_degf)*Matrix4::rotationX(45.0_degf), + /* to test also per-instance normal matrix is applied properly -- + the first sphere should *not* have axis-aligned TBN directions */ + (Matrix4::rotationY(-45.0_degf)*Matrix4::rotationX(45.0_degf)).normalMatrix(), + /* 6 gets added to the uniform objectId, wrapping it around to 0, + making it visually close to the multidraw test */ + 6}, + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{ 1.25f, -1.25f, 0.0f})), + {}, + 10}, + {Matrix4::translation(Math::gather<'z', 'y', 'x'>(Vector3{ 0.0f, 1.0f, -1.0f})), + {}, + 14} + }; + + sphere + .addVertexBufferInstanced(GL::Buffer{instanceData}, 1, 0, + MeshVisualizerGL3D::TransformationMatrix{}, + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + MeshVisualizerGL3D::NormalMatrix{}, + #else + sizeof(Matrix3x3), + #endif + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGL3D::ObjectId{} + #else + 4 + #endif + ) + .setInstanceCount(3); + + MeshVisualizerGL3D shader{ + MeshVisualizerGL3D::Flag::InstancedTransformation|data.flags|flag}; + shader.setViewportSize(Vector2{RenderSize}); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & (MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::ObjectId)) + shader.bindColorMapTexture(_colorMapTexture); + #endif + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader + .setColor(0xffffcc_rgbf) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(90.0_degf)* + Matrix4::scaling(Vector3{0.4f})) + .setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::NormalDirection)) + shader + .setNormalMatrix(Matrix4::rotationY(90.0_degf).normalMatrix()) + .setLineLength(0.25f); + #endif + if(data.flags & MeshVisualizerGL3D::Flag::Wireframe) + shader.setWireframeColor(0xcc0000_rgbf); + #ifndef MAGNUM_TARGET_GLES2 + if(data.flags & MeshVisualizerGL3D::Flag::VertexId) + shader.setColorMapTransformation(0.5f/sphereData.vertexCount(), 1.0f/sphereData.vertexCount()); + else if(data.flags & MeshVisualizerGL3D::Flag::ObjectId) + /* To make this visually close to the multidraw test */ + shader + .setObjectId(6) + .setColorMapTransformation(0.5f/12, 1.0f/12); + #endif + + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(90.0_degf)* + Matrix4::scaling(Vector3{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + .setNormalMatrix(Matrix4::rotationY(90.0_degf).normalMatrix()) + .setObjectId(6) + }}; + MeshVisualizerMaterialUniform materialUniformData[1]; + (*materialUniformData) + .setColor(0xffffcc_rgbf) + .setWireframeColor(0xcc0000_rgbf) + .setLineLength(0.25f); + if(data.flags & MeshVisualizerGL3D::Flag::VertexId) + materialUniformData->setColorMapTransformation(0.5f/sphereData.vertexCount(), 1.0f/sphereData.vertexCount()); + else if(data.flags & MeshVisualizerGL3D::Flag::ObjectId) + materialUniformData->setColorMapTransformation(0.5f/12, 1.0f/12); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialUniformData}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + 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."); + + /* + First circle should be lower left, second lower right, third up center. + + - Wireframe all looking the same (the only instanced thing that can + differ is the transformation + - TBN should have the lower right with different orientation than the + other two + - Vertex ID should all have the full color map range + - Object ID should be visually close to the multidraw case, except + that each circle is just a single color + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "MeshVisualizerTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + #ifndef MAGNUM_TARGET_GLES2 void MeshVisualizerGLTest::renderMulti2D() { auto&& data = RenderMultiData2D[testCaseInstanceId()]; diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-instancedobjectid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-instancedobjectid2D.tga new file mode 100644 index 000000000..4a94c3ce8 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-instancedobjectid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-instancedobjectid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-instancedobjectid3D.tga new file mode 100644 index 000000000..ddda78170 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-instancedobjectid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-vertexid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-vertexid2D.tga new file mode 100644 index 000000000..d41278bd0 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-vertexid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-vertexid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-vertexid3D.tga new file mode 100644 index 000000000..023385744 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-vertexid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-nogeo2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-nogeo2D.tga new file mode 100644 index 000000000..63293ad2a Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-nogeo2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-nogeo3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-nogeo3D.tga new file mode 100644 index 000000000..68b1bd10a Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-nogeo3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-tbn3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-tbn3D.tga new file mode 100644 index 000000000..9300b6560 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-tbn3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe2D.tga new file mode 100644 index 000000000..e424efb0a Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe3D.tga new file mode 100644 index 000000000..1fd557ca3 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe3D.tga differ