From 0190e9d17898cad355c8afc760c961de28197890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 25 Jan 2022 17:46:09 +0100 Subject: [PATCH] Shaders: implement instancing in MeshVisualizerGL shaders. Mainly to have feature parity with Flat and Phong -- otherwise switching to draw a wireframe on an instanced mesh would be too annoying. Also, if we have multidraw there already, why not instancing as well. --- doc/changelog.dox | 3 + doc/snippets/MagnumShaders-gl.cpp | 40 ++ src/Magnum/Shaders/MeshVisualizer.vert | 45 +- src/Magnum/Shaders/MeshVisualizerGL.cpp | 24 + src/Magnum/Shaders/MeshVisualizerGL.h | 144 +++++- src/Magnum/Shaders/Test/CMakeLists.txt | 9 + .../Shaders/Test/MeshVisualizerGLTest.cpp | 441 +++++++++++++++++- .../instanced-instancedobjectid2D.tga | Bin 0 -> 1058 bytes .../instanced-instancedobjectid3D.tga | Bin 0 -> 963 bytes .../instanced-vertexid2D.tga | Bin 0 -> 6990 bytes .../instanced-vertexid3D.tga | Bin 0 -> 4289 bytes .../instanced-wireframe-nogeo2D.tga | Bin 0 -> 5754 bytes .../instanced-wireframe-nogeo3D.tga | Bin 0 -> 3700 bytes .../instanced-wireframe-tbn3D.tga | Bin 0 -> 4817 bytes .../instanced-wireframe2D.tga | Bin 0 -> 5348 bytes .../instanced-wireframe3D.tga | Bin 0 -> 3425 bytes 16 files changed, 695 insertions(+), 11 deletions(-) create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-instancedobjectid2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-instancedobjectid3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-vertexid2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-vertexid3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-nogeo2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-nogeo3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe-tbn3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instanced-wireframe3D.tga 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 0000000000000000000000000000000000000000..4a94c3ce8e8d26874f5b8ee51a36aaec8d095f69 GIT binary patch literal 1058 zcmbu7K}y3=6h;5oS!S-lap&Dcci|#BENxn9u~kYEV_HRAk85!Q;yF;^4PGH&;BoIg zy#Igl@=LiXf6uJU%5C|osw%4|_~L#teOKq-6Z4*`VS@@Y?<`oO!pvLh0DD$p<_&d( zJ*qHs#@6tLKKE(pGiR)Zcl5b;N1u5tThZII*7JMTnlrY95A?b3K%eZ9{!!2GtQ3t&G zN*(cP^gX$OtI@xZJGgqy`&~UfYV<96fU9GCTpRthb0d%M9DV%8=;KV@c)uO*sm2+` J*~Xbi{|iB^I%)s_ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ddda7817098531a86403933279ce542d198fabd2 GIT binary patch literal 963 zcma))Nm9a47zV@E9Xn6pn!T6c79PSC^N;{}A&?-G^9|Hva279M*_{d-%7s>a_4R9B zl9xY5(N%Q*Ye((qIyyC*&Hv_$y^Z_t7PTUuq{~u%kndWz1>MKxv^>DA=^<`OyLW`M z$GD8vPjOY_0&A1t{5`>?^n%QB!NyfKZueNCs#x`5-;m@%#BYu%G92k$2XOo8u29zL73V`IYox-3uAQ zx@R(mbx&jp>mEr0>paRF)*VO+>-Hprbvu&7x-D75I{(8;ShsNvR}S96Iwx>|OWN#f z_P1@l$7?pQ?Y#=v_8BYK_Ss9=_Dlh`J?jFteTNxr`))C8`_2>Cb`~Spc18o(c6L43 OcBUQJzWfY-t$zW|6dYRs literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d41278bd01d6a31b66598ff1dcbaba68f267ac16 GIT binary patch literal 6990 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 Mw2-Y}vi<-61E*~>n*aa+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..023385744eea09f3c881b66c61fb2fcb0679a8a9 GIT binary patch literal 4289 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..63293ad2a6e9cfbcbb1c88715a908ed8f434454d GIT binary patch literal 5754 zcmaKweT=`gPylHGAoSFHhbv^~PPlc=-Lv-yH5dPuR<~&J+P+7EwVFb&Obh zn6Ze_<=nt$ulVQ9x4-?!x+5Gd#Nm# zB0$U{DkvH}rT_o~7?1#?{Sf0pMn}i!EnoS@&Yv&Xzk2gW8&99bieKujhv&-QPn=n~ z@%Yak*f-&8L$!|07%C_grU($Thzgq0?udSQ;0G+=xWEzaFkHx3z$h0A17DrCv-g?9 zFT5+la~P%6`CJ6$b$d?rJbiH7q#gFMi)F|Z0b&->kzOfvE2J`@K;r;682vhUEg&hG z=VdP+L$S%N1QmEJUVFsc0*$deSD2b}7E#H}^UjMv1Es)FEFC41G6tNEd&n4yxJn2hw)Jcqld$7H zhEjHORvoI1Qr+@LkST%yREyj&#N3FrVC8`zV3m4aVCJaZ)K|df)mepO51{Hrj*#@+ z^Y5IRc->G&h$(`AQA!Cpv(J|-kAYj9gCQ;&j*E!fge{uSsM$LZAP7VbEBcRzb$9!` z{R9d-A6=#hGF#N}jLd9flrvl0*sV7=_%3n}qWd62Z*P`*Dqec|VDO=7rh|ep^Ew!# z6S25f@xG8z7J^qL1VL83v;cNZE3e9t3&xOl0PrWz$Zz|f zcAw-{0>3LzRr=y)RlUhI`l>K(?Y95)UO5~GKC3x>X; ztUvPcwD0W(O@-evMS!~20>jafAu=Nga6FGGPhXm$1ToOED%wp8W)}?r%`8nAiSpQj zt4IFLOGnSYXe-E`D^mohCpXCjDOZ*vUFIzrOTO_q1QWjM8tLCy3S@nk1r!JfO&<9s z-rG{|uYP>?hj;7+R((mCDFRGKc(<;UDQb1Hlj>;zDX0aeO7tq7Ce0ps5?eCkC{hg# zO0u8ze&X4||6J~Cfhd-kB51)+whfJ>(5pFy(0~j$S@+aNBOF^)1<$t{9xKgDrFKZP z(1+bgRVZf-onwk1f97S?KuNf5^;X5V!Pb8JgL%k96k8ghzy|{hskOqw~T`wqF1J-S@9Nxb7|WKBmcpM}#b)fS&mM zj!~iE_`-y3KmO%j4(^6yD&5+i9_*B56+|qsjFR;xzr{*XQ5m@QTf=uRd4J9J6aVnF zNr@#(<#z=U3oN5#F?lLjmWiGjKlRrSQI)#PblH{>nE^`Z%SNM(q`2qt13AmoVOwg6 zs%6oW!M=LsHQWC%d~(T)M{^K*OF0)*HHAPG1I$1WibgxMe{xUwuxZ9hBiEk80-h6w zvBlzvfaosz)EiH3P!weRiDpcsu;a&sUo)!g91sA38F6p;(VMRv&ps8pnsFm&GonJ` zFa{zZ3e~lb+B~-}Yw$aBhz8e;rM-2SVGM>2sK>f`>d^Chv);TT#WJ9n0xW^xTQm$X z3n)TzMK>_3S6{F58MTOF5X70SHhZl*R58GAS->cMdh8%4+IPg36=oA;T28eu`_eY* zgF5RT^d91G5mBPyTxruCi{7K?_%8(oW4clw?2vTRhY^N#hbjia-bZE9^}{di*Z1eZtH-L`7Hm?ryqu&)@w2 zK`AG?a#)BAj!843&mxS007=X|;+W43(xPJF^L=>|BOnl`pgCNWgPj8cpai#_QaSVf zeFOdrBBEYJ0(jJhlZl80HiqpnOw2I0YAdFyIMwmj?pXWQsb;K(ijl*@0JHF}zHU*T z?^|)u5+TxQs9Pxns#ebxD^=O@6rV!TvY}Q+OO()+KX1dUUG?>$Ieoe=iCBY;uED-T zETa@`{5`+{oj7%9*1Y|zUi+y3lse5cW)(y%u#A$OW!(e1MbzzHI(G28Gv8kH#}EE; zKp*WfD|kf65(*aG6U) F{|68+Z}tEH literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..68b1bd10a4fb7447a28c624fa6a834825a46e80f GIT binary patch literal 3700 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 V5lStv6cLN~b~h9b38w$s@-NNkKCS=& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e424efb0a564bb29716017b680e70f7db6e9ee32 GIT binary patch literal 5348 zcmZ{oX^2*36vyYib7xhD{MN^4AyLGvtf(Zx79=C1K%qs7iUeCIRJ0<^Wz5mk3N^LV z)NDZ`mn2lOgv^EfP~*NkrjCx9OXG|>_WM8QxzBswJFmlim~+o_&j0+M?L6ncB_-7* z$=`sI0VOw)bFuab`nXr$U4ut7yf*fqJ9_WreZF*vC{WYM`*hkgT4n#i2PQ0O z`l+FnuGc3XTYjKr^nz254cbrNT=gTOKusqm7-EhSeN$;uXf@sM+wt=IN9S%hzv05w zt=F#c7?{bw{(EKSx^shvH*~pkJJvJgfrtV%on~e5CIbT)kN{&nnN~-O`#!zzy*Vd; z*mG%1n}U2ui$Wy5#hb6ReZReV#Al7YpV)(kn+-%1sOe0+LNw^yGJ)-tr4Xc;21G;y*9m+*K>B-4r3B z@O^QHrLzw#_2yYtq*Mzu`#5)I%T}V2>~H9i*9~4wzLf#dN^TxD3eX9-zYbG7T1%!)-Aq zVh}Ce+1&MRC2%)^h$zsj&kx`;odxEC$sg^%}TEA~B~7#;|d7kt{B1I+NKcj0BvOpgxkT&nXM3#3*#~ z6-40*#~c9m7b|JA>FRSt6sYZV6t1S;V&DJ;+DcHJCVh8wqN<`?p(?Oj7}GF`$mA$; zf*rtFGDk#Vsf+$FAY=f50nBPPt+M+%;)dsf<)$-WvOj6qQla}gLPUYuz%azzDg=EU zx$$vT;?4bJH;MZiLqvhvuGHz+7WlR}Zcj>wPg<(f{5O`liTS%+y-G9$;zH#cC zUmbmv2Z2|V^nFEJKJOm zNj*3QCvEc>6&-YAgzXnJ%}432-Rs9KZd!F%7p(T09CR0zqN0Ottmb&@c=Y^JXeKQ^ zliy}?kYkLB4!SY&KJqKS_mSPJkG5t!#Yvtz66I-6OmV<(w#TS=YqCBS$f6ktZM0-~ z6=?{&2XON!LaMr@Tm9GDRL>X1Qlb@>Fss^Qp~V>pkUsaQ(V#aD|Iw;>mh%nm`Jn}o z2SHD2dW@mF9e@BRg`1ncO8HF_RMQ?bkJt`Q9&+|r;u-ZBuyRruq#|)guY6s-f z6mB?!p@S%_+^AW)6SOy~(?Et2R^Eo@su>)-zc8Y1y>=|yrCU!0ab?Zto{g7 z9PpdzF>)`BQo*O1atzj!`7c$HlFF%!FaMz_2bWV(*}%QE%i0{Xk+T|??rPqARijN0FGfWN z-F6n0wUzB_y7t=f;;4q%>(Bjm-v53VmLCX|bYXxQ*7IoY&gu8u{;`XiRvv0GV+3AN X(yu$m-B(->)@%2K?yK~jmX`kq$_!K{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1fd557ca3d6e3c4970a7a4156659fa56aac5935e GIT binary patch literal 3425 zcma);X>6566o%X0+YSH74UhfCL3uLQs(aGv__;nX}C_D5zP`zkg9d zQ9&(&t~77n{Qr*QV1wIqyvol8Ml!?m`Q`^*`(>ET^=xd_=8y?F3CX1$dtGO-kr4{& z*V@LJtM3h+lw+{#*xtkld#+9Dkn!2^$4bvtG;BUyZj?kHZj>%Re!Tbh%9gEDff&^U zgaSmo)ZyyjaoKB+mYyrG2gYX!bd*R-faGFL4QGZUh}<= zW)WzQA$lw71_0R$nd^9fNaE1gLnWR1WE{_{wQOqv#WTEZJEspElO2~-`cs(>wZvtc zzUrP2iM@kIgGM6y%mkI!~>=G^&;fz=X z`og0If5JF$8lL{6vSJUHoGsPyRxrHA*;55|Z@w#k1#$`HKx9h8<1@weCJ(z(Dg&5T zqYx|RcSdH#e&=Ie*@XF%n0x>Ny3Ela!Y))d9LufQcCKRHwt`W!Zg(G$Nvgwp4AKui z5ETC?%gRbw>Nz-b<-y`JCGsJ*i6y822>6uA+3Di?1AkYMv5%MKlNs{UzH9oRaoN*i z^5c$_e3SMFOL#`iNCH{)uaQ;IXn0@ker@P=R$4RP8Zen7@P@AM|&Ep z@@1ya7hD-L`}Tsw0M2X^wrrC+X+hq>^hcl-m?rUDxT6pisk(OLw}&JWU@De{ zZUWbi0;bN1>^CCo#NAqCztuMaA2vCsL(gmTw-?Y~J|)-G?#q?Xp(SnCl+)vs4CP9H zpru5)rsR&Uh9@c2OZC2b;tpJXpqP@v{<;%oWECAg8$r4W9jCc%?3{|AIH*v%!_N>! z>;vn12~Id(hENVi9=%mVt8n#>opWdZpH0kokOpzKUqe((nup`s8aMs{@<5 z*}&>bz}Uw+_}Q0J`O$PQ!^3j&!aP3T#vXn{*<8B6$m}y=OWV5{;YLI*jFgE1NVtm; zX#u!tFib2=ke!St{9*n`f3SdGF?2|*K!Q}6h=y313$eU{DQr!kf(YvLy@d+)Wgt`# zK~?uIt6+(NP(cLMHRM7C+a3rNL{PP@3l(fzAXE@Rb(y$O!JL1b{39TUpt|2&s9;+H zSp^YP?}7^zY;zz~5JB~VxlqCM-#Uk4u-gyU%+!`6tiNGGWOkA^^Yuo7%RV zI;qMe#B$2jWd30fzNwB1!>MD@9nOg;|W4bhU*Ui~(4wv~@{(ks1*bpN1~bXqNg7yl4Mw{SPj3UL^nk literal 0 HcmV?d00001