From 033e56ec23bd1a160a5af96dd8856ef92969daf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 24 Jan 2022 20:50:25 +0100 Subject: [PATCH] Shaders: add missing MeshVisualizerGL*D::setObjectId(). Originally the uniform wasn't present with the assumption that users could easily adjust color map offset to achieve the same effect. That was however unnecessarily annoying and error-prone in cases where it's essential to have the same object IDs from multiple draws have a matching color, and it was complicating multidraw workflows as the color map offset was not a part of per-draw data, but rather material data. --- doc/changelog.dox | 10 + src/Magnum/Shaders/MeshVisualizer.frag | 43 ++-- src/Magnum/Shaders/MeshVisualizer.geom | 7 +- src/Magnum/Shaders/MeshVisualizer.h | 76 ++++-- src/Magnum/Shaders/MeshVisualizer.vert | 15 +- src/Magnum/Shaders/MeshVisualizerGL.cpp | 70 ++++-- src/Magnum/Shaders/MeshVisualizerGL.h | 130 ++++++++-- src/Magnum/Shaders/Test/CMakeLists.txt | 4 + .../Shaders/Test/MeshVisualizerGLTest.cpp | 224 ++++++++++++++++-- .../Shaders/Test/MeshVisualizerGL_Test.cpp | 32 ++- .../defaults-objectid2D.tga | Bin 0 -> 960 bytes .../defaults-objectid3D.tga | Bin 0 -> 882 bytes .../instancedobjectid2D.tga | Bin 1718 -> 1684 bytes .../instancedobjectid3D.tga | Bin 2494 -> 2511 bytes .../MeshVisualizerTestFiles/objectid2D.tga | Bin 0 -> 946 bytes .../MeshVisualizerTestFiles/objectid3D.tga | Bin 0 -> 898 bytes .../wireframe-instancedobjectid2D.tga | Bin 7604 -> 8649 bytes .../wireframe-instancedobjectid3D.tga | Bin 6820 -> 7680 bytes .../wireframe-nogeo-instancedobjectid2D.tga | Bin 8353 -> 9354 bytes .../wireframe-nogeo-instancedobjectid3D.tga | Bin 7703 -> 8474 bytes 20 files changed, 502 insertions(+), 109 deletions(-) create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-objectid2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-objectid3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid3D.tga diff --git a/doc/changelog.dox b/doc/changelog.dox index edb2a8967..1b6e10446 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -416,6 +416,16 @@ See also: attributes and a @ref Shaders::PhongGL::Flag::Bitangent flag, implementing support for both four-component tangents (used by glTF, for example) and separate tangent and bitangent direction (used by Assimp). +- Added missing @ref Shaders::MeshVisualizerGL3D::setObjectId() "Shaders::MeshVisualizerGL*D::setObjectId()" + and a corresponding @ref Shaders::MeshVisualizerDrawUniform3D::objectId + "Shaders::MeshVisualizerDrawUniform*D::objectId" + member for UBO workflows. Originally the uniform wasn't present with the + assumption that users could easily adjust color map offset to achieve the + same effect. That was however unnecessarily annoying and error-prone in + cases where it's essential to have the same object IDs from multiple + draws have a matching color, and it was complicating multidraw workflows as + the color map offset was not a part of per-draw data, but rather material + data. @subsubsection changelog-latest-changes-trade Trade library diff --git a/src/Magnum/Shaders/MeshVisualizer.frag b/src/Magnum/Shaders/MeshVisualizer.frag index db16422a8..2cb67b5a0 100644 --- a/src/Magnum/Shaders/MeshVisualizer.frag +++ b/src/Magnum/Shaders/MeshVisualizer.frag @@ -43,7 +43,7 @@ /* Uniforms */ #ifndef UNIFORM_BUFFERS -#if (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) && !defined(TBN_DIRECTION) +#if (defined(WIREFRAME_RENDERING) || defined(OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) && !defined(TBN_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 1) #endif @@ -74,7 +74,7 @@ uniform lowp float wireframeWidth ; #elif defined(TBN_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 9) +layout(location = 10) #endif uniform lowp float lineWidth #ifndef GL_ES @@ -94,7 +94,7 @@ uniform lowp float smoothness ; #endif -#if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) +#if defined(OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 5) #endif @@ -107,6 +107,14 @@ uniform lowp vec2 colorMapOffsetScale #define colorMapScale colorMapOffsetScale.y #endif +#ifdef OBJECT_ID +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 6) +#endif +/* mediump is just 2^10, which might not be enough, this is 2^16 */ +uniform highp uint objectId; /* defaults to zero */ +#endif + /* Uniform buffers */ #else @@ -136,8 +144,9 @@ struct DrawUniform { #elif !defined(TWO_DIMENSIONS) #error #endif - highp uvec4 materialIdReservedReservedReservedReserved; - #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x + highp uvec4 materialIdReservedObjectIdReservedReserved; + #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x + #define draw_objectId materialIdReservedObjectIdReservedReserved.y }; layout(std140 @@ -175,7 +184,7 @@ layout(std140 /* Textures */ -#if defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) +#if defined(OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) #ifdef EXPLICIT_BINDING layout(binding = 4) #endif @@ -225,12 +234,15 @@ out lowp vec4 fragmentColor; void main() { #ifdef UNIFORM_BUFFERS + #ifdef OBJECT_ID + highp const uint objectId = draws[drawId].draw_objectId; + #endif #if MATERIAL_COUNT > 1 mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; #else #define materialId 0u #endif - #if (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) && !defined(TBN_DIRECTION) + #if (defined(WIREFRAME_RENDERING) || defined(OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) && !defined(TBN_DIRECTION) lowp const vec4 color = materials[materialId].color; lowp const vec4 wireframeColor = materials[materialId].wireframeColor; #endif @@ -242,7 +254,7 @@ void main() { #if defined(WIREFRAME_RENDERING) || defined(TBN_DIRECTION) lowp const float smoothness = materials[materialId].material_smoothness; #endif - #if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + #if defined(OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) lowp const float colorMapOffset = materials[materialId].material_colorMapOffset; lowp const float colorMapScale = materials[materialId].material_colorMapScale; #endif @@ -251,14 +263,17 @@ void main() { /* Map object/vertex/primitive ID to a color. Will be either combined with the wireframe background color (if wireframe is enabled), ignored (if rendering TBN direction) or used as-is if nothing else is enabled */ - #if defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + #if defined(OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) lowp vec4 faceColor = texture(colorMapTexture, vec2( /* Object/primitive IDs are constant across the whole primitive so we do the offset/scale mapping here */ - #if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + #if defined(OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) colorMapOffset + float( - #ifdef INSTANCED_OBJECT_ID - interpolatedInstanceObjectId + #ifdef OBJECT_ID + objectId + #ifdef INSTANCED_OBJECT_ID + + interpolatedInstanceObjectId + #endif #elif defined(PRIMITIVE_ID) gl_PrimitiveID #elif defined(PRIMITIVE_ID_FROM_VERTEX_ID) @@ -312,7 +327,7 @@ void main() { #else fragmentColor = backgroundColor; #endif - #if defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + #if defined(OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) fragmentColor *= faceColor; #endif @@ -362,7 +377,7 @@ void main() { #endif /* Object / Vertex / Primitive ID visualization using a colormap */ - #elif defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + #elif defined(OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) fragmentColor = color*faceColor; #else diff --git a/src/Magnum/Shaders/MeshVisualizer.geom b/src/Magnum/Shaders/MeshVisualizer.geom index ac662e2ad..e2a31d8f1 100644 --- a/src/Magnum/Shaders/MeshVisualizer.geom +++ b/src/Magnum/Shaders/MeshVisualizer.geom @@ -66,7 +66,7 @@ uniform lowp vec4 wireframeColor #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 9) +layout(location = 10) #endif uniform lowp float lineWidth #ifndef GL_ES @@ -108,8 +108,9 @@ struct DrawUniform { #elif !defined(TWO_DIMENSIONS) #error #endif - highp uvec4 materialIdReservedReservedReservedReserved; - #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x + highp uvec4 materialIdReservedObjectIdReservedReserved; + #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x + #define draw_objectId materialIdReservedObjectIdReservedReserved.y }; layout(std140 diff --git a/src/Magnum/Shaders/MeshVisualizer.h b/src/Magnum/Shaders/MeshVisualizer.h index 9b0a540c4..dc174dfc1 100644 --- a/src/Magnum/Shaders/MeshVisualizer.h +++ b/src/Magnum/Shaders/MeshVisualizer.h @@ -59,11 +59,12 @@ struct MeshVisualizerDrawUniform2D { _pad0{}, /* Otherwise it refuses to constexpr, on 3.8 at least */ #endif materialId{0} - #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - #ifndef CORRADE_TARGET_BIG_ENDIAN + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && !defined(CORRADE_TARGET_BIG_ENDIAN) , _pad0{} #endif - , _pad1{}, _pad2{}, _pad3{} + , objectId{0} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + , _pad1{}, _pad2{} #endif {} @@ -88,6 +89,15 @@ struct MeshVisualizerDrawUniform2D { return *this; } + /** + * @brief Set the @ref objectId field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform2D& setObjectId(UnsignedInt id) { + objectId = id; + return *this; + } + /** * @} */ @@ -108,7 +118,7 @@ struct MeshVisualizerDrawUniform2D { /* This field is an UnsignedInt in the shader and materialId is extracted as (value & 0xffff), so the order has to be different on BE */ #ifndef CORRADE_TARGET_BIG_ENDIAN - alignas(4) UnsignedShort materialId; + UnsignedShort materialId; /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT @@ -119,7 +129,7 @@ struct MeshVisualizerDrawUniform2D { :16; /* reserved for skinOffset */ #endif #else - alignas(4) UnsignedShort + UnsignedShort #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) _pad0 /* Otherwise it refuses to constexpr, on 3.8 at least */ #endif @@ -127,6 +137,21 @@ struct MeshVisualizerDrawUniform2D { UnsignedShort materialId; #endif + /** + * @brief Object ID + * + * Unlike @ref materialId, this index is used only for the object ID + * visualization, not to access any other uniform data. Default value + * is @cpp 0 @ce. + * + * Used only if @ref MeshVisualizerGL2D::Flag::ObjectId is enabled, ignored + * otherwise. If @ref MeshVisualizerGL2D::Flag::InstancedObjectId is + * enabled as well, this value is added to the ID coming from the + * @ref MeshVisualizerGL2D::ObjectId attribute. + * @see @ref MeshVisualizerGL2D::setObjectId() + */ + UnsignedInt objectId; + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT @@ -140,11 +165,6 @@ struct MeshVisualizerDrawUniform2D { _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ #endif :32; - Int - #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ - #endif - :32; #endif }; @@ -165,11 +185,12 @@ struct MeshVisualizerDrawUniform3D { , _pad0{} /* Otherwise it refuses to constexpr, on 3.8 at least */ #endif , materialId{0} - #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - #ifndef CORRADE_TARGET_BIG_ENDIAN + #if ((defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8)) && !defined(CORRADE_TARGET_BIG_ENDIAN) , _pad0{} #endif - , _pad1{}, _pad2{}, _pad3{} + , objectId{0} + #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) + , _pad1{}, _pad2{} #endif {} @@ -206,6 +227,15 @@ struct MeshVisualizerDrawUniform3D { return *this; } + /** + * @brief Set the @ref objectId field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform3D& setObjectId(UnsignedInt id) { + objectId = id; + return *this; + } + /** * @} */ @@ -252,6 +282,21 @@ struct MeshVisualizerDrawUniform3D { UnsignedShort materialId; #endif + /** + * @brief Object ID + * + * Unlike @ref materialId, this index is used only for the object ID + * visualization, not to access any other uniform data. Default value + * is @cpp 0 @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::ObjectId is enabled, ignored + * otherwise. If @ref MeshVisualizerGL3D::Flag::InstancedObjectId is + * enabled as well, this value is added to the ID coming from the + * @ref MeshVisualizerGL3D::ObjectId attribute. + * @see @ref MeshVisualizerGL3D::setObjectId() + */ + UnsignedInt objectId; + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK I MADE THOSE UNNAMED, YOU DUMB FOOL */ #ifndef DOXYGEN_GENERATING_OUTPUT @@ -265,11 +310,6 @@ struct MeshVisualizerDrawUniform3D { _pad2 /* Otherwise it refuses to constexpr, on 3.8 at least */ #endif :32; - Int - #if (defined(CORRADE_TARGET_CLANG) && __clang_major__ < 4) || (defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ < 8) - _pad3 /* Otherwise it refuses to constexpr, on 3.8 at least */ - #endif - :32; #endif }; diff --git a/src/Magnum/Shaders/MeshVisualizer.vert b/src/Magnum/Shaders/MeshVisualizer.vert index 91f2355e7..8e3a8732f 100644 --- a/src/Magnum/Shaders/MeshVisualizer.vert +++ b/src/Magnum/Shaders/MeshVisualizer.vert @@ -49,7 +49,7 @@ #ifndef UNIFORM_BUFFERS #ifdef TWO_DIMENSIONS #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 6) +layout(location = 7) #endif uniform highp mat3 transformationProjectionMatrix #ifndef GL_ES @@ -58,7 +58,7 @@ uniform highp mat3 transformationProjectionMatrix ; #elif defined(THREE_DIMENSIONS) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 6) +layout(location = 7) #endif uniform highp mat4 transformationMatrix #ifndef GL_ES @@ -66,7 +66,7 @@ uniform highp mat4 transformationMatrix #endif ; #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 7) +layout(location = 8) #endif uniform highp mat4 projectionMatrix #ifndef GL_ES @@ -92,7 +92,7 @@ uniform lowp vec2 colorMapOffsetScale #if defined(TANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 8) +layout(location = 9) #endif uniform highp mat3 normalMatrix #ifndef GL_ES @@ -101,7 +101,7 @@ uniform highp mat3 normalMatrix ; #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 10) +layout(location = 11) #endif uniform highp float lineLength #ifndef GL_ES @@ -166,8 +166,9 @@ struct DrawUniform { #elif !defined(TWO_DIMENSIONS) #error #endif - highp uvec4 materialIdReservedReservedReservedReserved; - #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x + highp uvec4 materialIdReservedObjectIdReservedReserved; + #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x + #define draw_objectId materialIdReservedObjectIdReservedReserved.y }; layout(std140 diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index 1fc40b8ec..9a61eee5d 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -85,12 +85,12 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags #ifndef MAGNUM_TARGET_GLES2 #ifndef CORRADE_NO_ASSERT Int countMutuallyExclusive = 0; - if(flags & FlagBase::InstancedObjectId) ++countMutuallyExclusive; + if(flags & FlagBase::ObjectId) ++countMutuallyExclusive; if(flags & FlagBase::VertexId) ++countMutuallyExclusive; if(flags & FlagBase::PrimitiveIdFromVertexId) ++countMutuallyExclusive; #endif CORRADE_ASSERT(countMutuallyExclusive <= 1, - "Shaders::MeshVisualizerGL: Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive", ); + "Shaders::MeshVisualizerGL: Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive", ); #endif #ifndef MAGNUM_TARGET_GLES @@ -161,7 +161,7 @@ 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" : "") + .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "") #endif @@ -185,7 +185,8 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra #endif frag.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 - .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags & FlagBase::ObjectId ? "#define OBJECT_ID\n" : "") + .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags & FlagBase::PrimitiveId ? (_flags >= FlagBase::PrimitiveIdFromVertexId ? @@ -208,13 +209,24 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra return version; } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGLBase& MeshVisualizerGLBase::setObjectId(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), + "Shaders::MeshVisualizerGL::setObjectId(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & FlagBase::ObjectId, + "Shaders::MeshVisualizerGL::setObjectId(): the shader was not created with object ID enabled", *this); + setUniform(_objectIdUniform, id); + return *this; +} +#endif + MeshVisualizerGLBase& MeshVisualizerGLBase::setColor(const Color4& color) { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), "Shaders::MeshVisualizerGL::setColor(): the shader was created with uniform buffers enabled", *this); #endif #ifndef MAGNUM_TARGET_GLES2 - CORRADE_ASSERT(_flags & (FlagBase::Wireframe|FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), + CORRADE_ASSERT(_flags & (FlagBase::Wireframe|FlagBase::ObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), "Shaders::MeshVisualizerGL::setColor(): the shader was not created with wireframe or object/vertex/primitive ID enabled", *this); #else CORRADE_ASSERT(_flags & FlagBase::Wireframe, @@ -250,7 +262,7 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeWidth(const Float width) MeshVisualizerGLBase& MeshVisualizerGLBase::setColorMapTransformation(const Float offset, const Float scale) { CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was created with uniform buffers enabled", *this); - CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), + CORRADE_ASSERT(_flags & (FlagBase::ObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was not created with object/vertex/primitive ID enabled", *this); setUniform(_colorMapOffsetScaleUniform, Vector2{offset, scale}); return *this; @@ -284,7 +296,7 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::bindMaterialBuffer(GL::Buffer& buffe #ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGLBase& MeshVisualizerGLBase::bindColorMapTexture(GL::Texture2D& texture) { - CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), + CORRADE_ASSERT(_flags & (FlagBase::ObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), "Shaders::MeshVisualizerGL::bindColorMapTexture(): the shader was not created with object/vertex/primitive ID enabled", *this); texture.bind(ColorMapTextureUnit); return *this; @@ -303,7 +315,7 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags #endif } { #ifndef MAGNUM_TARGET_GLES2 - CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), + CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizerGL2D: at least one visualization feature has to be enabled", ); #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), @@ -356,7 +368,7 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags geom = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Geometry); (*geom) .addSource("#define WIREFRAME_RENDERING\n#define MAX_VERTICES 3\n") - .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags & FlagBase::PrimitiveId ? (_flags >= FlagBase::PrimitiveIdFromVertexId ? @@ -433,7 +445,7 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); if(flags & (Flag::Wireframe #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + |Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId #endif )) _colorUniform = uniformLocation("color"); @@ -443,9 +455,11 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags _smoothnessUniform = uniformLocation("smoothness"); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + if(flags & (Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); } + if(flags & Flag::ObjectId) + _objectIdUniform = uniformLocation("objectId"); #endif } } @@ -455,7 +469,7 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags if(flags && !context.isExtensionSupported(version)) #endif { - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + if(flags & (Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); } #ifndef MAGNUM_TARGET_GLES2 @@ -480,7 +494,7 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags setTransformationProjectionMatrix(Matrix3{Math::IdentityInit}); if(flags & (Flag::Wireframe #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + |Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId #endif )) setColor(Color3(1.0f)); @@ -491,7 +505,7 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags setSmoothness(2.0f); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) + if(flags & (Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #endif } @@ -572,14 +586,14 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags #endif } { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection|Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), + CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", ); CORRADE_ASSERT(!(flags & Flag::NoGeometryShader && flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)), "Shaders::MeshVisualizerGL3D: geometry shader has to be enabled when rendering TBN direction", ); CORRADE_ASSERT(!(flags & Flag::BitangentDirection && flags & Flag::BitangentFromTangentDirection), "Shaders::MeshVisualizerGL3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive", ); #elif !defined(MAGNUM_TARGET_GLES2) - CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), + CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", ); #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), @@ -587,7 +601,7 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - CORRADE_ASSERT(!(flags & Flag::InstancedObjectId) || !(flags & Flag::BitangentDirection), + CORRADE_ASSERT(!(flags >= Flag::InstancedObjectId) || !(flags & Flag::BitangentDirection), "Shaders::MeshVisualizerGL3D: Bitangent attribute binding conflicts with the ObjectId attribute, use a Tangent4 attribute with instanced object ID rendering instead", ); #endif @@ -669,7 +683,7 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags (*geom) .addSource(Utility::formatString("#define MAX_VERTICES {}\n", maxVertices)) .addSource(flags & Flag::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") - .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags >= FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") .addSource(_flags & FlagBase::VertexId ? "#define VERTEX_ID\n" : "") .addSource(_flags & FlagBase::PrimitiveId ? (_flags >= FlagBase::PrimitiveIdFromVertexId ? @@ -765,7 +779,7 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags _projectionMatrixUniform = uniformLocation("projectionMatrix"); if(flags & (Flag::Wireframe #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + |Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId #endif )) _colorUniform = uniformLocation("color"); @@ -781,9 +795,11 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags _smoothnessUniform = uniformLocation("smoothness"); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + if(flags & (Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); } + if(flags & Flag::ObjectId) + _objectIdUniform = uniformLocation("objectId"); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { @@ -800,7 +816,7 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags if(flags && !context.isExtensionSupported(version)) #endif { - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + if(flags & (Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); } #ifndef MAGNUM_TARGET_GLES2 @@ -827,7 +843,7 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags setProjectionMatrix(Matrix4{Math::IdentityInit}); if(flags & (Flag::Wireframe #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + |Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId #endif )) setColor(Color3(1.0f)); @@ -844,7 +860,7 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags setSmoothness(2.0f); } #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) + if(flags & (Flag::ObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -996,6 +1012,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flag value) { _c(NoGeometryShader) _c(Wireframe) #ifndef MAGNUM_TARGET_GLES2 + _c(ObjectId) _c(InstancedObjectId) _c(VertexId) #ifndef MAGNUM_TARGET_WEBGL @@ -1029,6 +1046,7 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flag value) { _c(NormalDirection) #endif #ifndef MAGNUM_TARGET_GLES2 + _c(ObjectId) _c(InstancedObjectId) _c(VertexId) #ifndef MAGNUM_TARGET_WEBGL @@ -1054,7 +1072,8 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flags value) { there */ MeshVisualizerGL2D::Flag::NoGeometryShader, #ifndef MAGNUM_TARGET_GLES2 - MeshVisualizerGL2D::Flag::InstancedObjectId, + MeshVisualizerGL2D::Flag::InstancedObjectId, /* Superset of ObjectId */ + MeshVisualizerGL2D::Flag::ObjectId, MeshVisualizerGL2D::Flag::VertexId, MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL @@ -1081,7 +1100,8 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flags value) { MeshVisualizerGL3D::Flag::NormalDirection, #endif #ifndef MAGNUM_TARGET_GLES2 - MeshVisualizerGL3D::Flag::InstancedObjectId, + MeshVisualizerGL3D::Flag::InstancedObjectId, /* Superset of ObjectId */ + MeshVisualizerGL3D::Flag::ObjectId, 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 1b2dda3ed..8f9b8e228 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -50,7 +50,8 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr Wireframe = 1 << 0, NoGeometryShader = 1 << 1, #ifndef MAGNUM_TARGET_GLES2 - InstancedObjectId = 1 << 2, + ObjectId = 1 << 12, + InstancedObjectId = (1 << 2)|ObjectId, VertexId = 1 << 3, PrimitiveId = 1 << 4, PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId, @@ -72,6 +73,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs) const; + MeshVisualizerGLBase& setObjectId(UnsignedInt id); MeshVisualizerGLBase& setColor(const Color4& color); MeshVisualizerGLBase& setWireframeColor(const Color4& color); MeshVisualizerGLBase& setWireframeWidth(Float width); @@ -104,7 +106,8 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr _wireframeWidthUniform{3}, _smoothnessUniform{4}; #ifndef MAGNUM_TARGET_GLES2 - Int _colorMapOffsetScaleUniform{5}; + Int _colorMapOffsetScaleUniform{5}, + _objectIdUniform{6}; /* Used instead of all other uniforms except viewportSize when Flag::UniformBuffers is set, so it can alias them */ Int _drawOffsetUniform{1}; @@ -208,8 +211,37 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua NoGeometryShader = 1 << 1, #ifndef MAGNUM_TARGET_GLES2 - /** @copydoc MeshVisualizerGL3D::Flag::InstancedObjectId */ - InstancedObjectId = 1 << 2, + /** + * Visualize object ID set via @ref setObjectId() or + * @ref MeshVisualizerDrawUniform2D::objectId. Since the ID is + * uniform for the whole draw, this feature is mainly useful in + * multidraw scenarios or when combined with + * @ref Flag::InstancedObjectId. Mutually exclusive with + * @ref Flag::VertexId and @ref Flag::PrimitiveId. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Object ID input requires integer support in + * shaders, which is not available in OpenGL ES 2.0. + * @requires_webgl20 Object ID input requires integer support in + * shaders, which is not available in WebGL 1.0. + * @m_since_latest + */ + ObjectId = 1 << 12, + + /** + * Visualize instanced object ID. You need to provide the + * @ref ObjectId attribute in the mesh, which then gets summed with + * the ID coming from @ref setObjectId() or + * @ref MeshVisualizerDrawUniform2D::objectId. Implicitly enables + * @ref Flag::ObjectId. Mutually exclusive with @ref Flag::VertexId + * and @ref Flag::PrimitiveId. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Object ID input requires integer support in + * shaders, which is not available in OpenGL ES 2.0. + * @requires_webgl20 Object ID input requires integer support in + * shaders, which is not available in WebGL 1.0. + * @m_since{2020,06} + */ + InstancedObjectId = (1 << 2)|ObjectId, /** @copydoc MeshVisualizerGL3D::Flag::VertexId */ VertexId = 1 << 3, @@ -406,6 +438,28 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua */ MeshVisualizerGL2D& setViewportSize(const Vector2& size); + /** + * @brief Set object ID + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::ObjectId is enabled. Default is + * @cpp 0 @ce. If @ref Flag::InstancedObjectId is enabled as well, this + * value is added to the ID coming from the @ref ObjectId attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerDrawUniform2D::objectId and call + * @ref bindDrawBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Object ID input requires integer support in + * shaders, which is not available in OpenGL ES 2.0. + * @requires_webgl20 Object ID input requires integer support in + * shaders, which is not available in WebGL 1.0. + */ + MeshVisualizerGL2D& setObjectId(UnsignedInt id) { + return static_cast(Implementation::MeshVisualizerGLBase::setObjectId(id)); + } + /** * @brief Set base object color * @return Reference to self (for method chaining) @@ -633,7 +687,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua #endif private: - Int _transformationProjectionMatrixUniform{6}; + Int _transformationProjectionMatrixUniform{7}; }; /** @@ -960,17 +1014,36 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua #ifndef MAGNUM_TARGET_GLES2 /** - * Visualize instanced object ID. You need to provide the - * @ref ObjectId attribute in the mesh. Mutually exclusive with + * Visualize object ID set via @ref setObjectId() or + * @ref MeshVisualizerDrawUniform3D::objectId. Since the ID is + * uniform for the whole draw, this feature is mainly useful in + * multidraw scenarios or when combined with + * @ref Flag::InstancedObjectId. Mutually exclusive with * @ref Flag::VertexId and @ref Flag::PrimitiveId. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} - * @requires_gles30 Object ID output requires integer support in + * @requires_gles30 Object ID input requires integer support in * shaders, which is not available in OpenGL ES 2.0. - * @requires_webgl20 Object ID output requires integer support in + * @requires_webgl20 Object ID input requires integer support in + * shaders, which is not available in WebGL 1.0. + * @m_since_latest + */ + ObjectId = 1 << 12, + + /** + * Visualize instanced object ID. You need to provide the + * @ref ObjectId attribute in the mesh, which then gets summed with + * the ID coming from @ref setObjectId() or + * @ref MeshVisualizerDrawUniform3D::objectId. Implicitly enables + * @ref Flag::ObjectId. Mutually exclusive with @ref Flag::VertexId + * and @ref Flag::PrimitiveId. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Object ID input requires integer support in + * shaders, which is not available in OpenGL ES 2.0. + * @requires_webgl20 Object ID input requires integer support in * shaders, which is not available in WebGL 1.0. * @m_since{2020,06} */ - InstancedObjectId = 1 << 2, + InstancedObjectId = (1 << 2)|ObjectId, /** * Visualize vertex ID (@cpp gl_VertexID @ce). Useful for @@ -1339,6 +1412,28 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua */ MeshVisualizerGL3D& setViewportSize(const Vector2& size); + /** + * @brief Set object ID + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::ObjectId is enabled. Default is + * @cpp 0 @ce. If @ref Flag::InstancedObjectId is enabled as well, this + * value is added to the ID coming from the @ref ObjectId attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerDrawUniform3D::objectId and call + * @ref bindDrawBuffer() instead. + * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} + * @requires_gles30 Object ID input requires integer support in + * shaders, which is not available in OpenGL ES 2.0. + * @requires_webgl20 Object ID input requires integer support in + * shaders, which is not available in WebGL 1.0. + */ + MeshVisualizerGL3D& setObjectId(UnsignedInt id) { + return static_cast(Implementation::MeshVisualizerGLBase::setObjectId(id)); + } + /** * @brief Set base object color * @return Reference to self (for method chaining) @@ -1405,11 +1500,6 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::InstancedObjectId or @ref Flag::PrimitiveId / * @ref Flag::PrimitiveIdFromVertexId is enabled. * - * Note that this shader doesn't directly offer a - * @ref FlatGL::setObjectId() "setObjectId()" uniform that's used to - * offset the per-vertex / per-instance ID. Instead, you need to encode - * the base offset into the @p offset parameter. - * * Expects that @ref Flag::UniformBuffers is not set, in that case fill * @ref MeshVisualizerMaterialUniform::colorMapOffset and * @ref MeshVisualizerMaterialUniform::colorMapScale and call @@ -1685,12 +1775,12 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua #endif private: - Int _transformationMatrixUniform{6}, - _projectionMatrixUniform{7}; + Int _transformationMatrixUniform{7}, + _projectionMatrixUniform{8}; #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - Int _normalMatrixUniform{8}, - _lineWidthUniform{9}, - _lineLengthUniform{10}; + Int _normalMatrixUniform{9}, + _lineWidthUniform{10}, + _lineLengthUniform{11}; #endif }; diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index 90feea049..02e748404 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -199,6 +199,8 @@ if(BUILD_GL_TESTS) MeshVisualizerTestFiles/bitangents-from-tangents.tga MeshVisualizerTestFiles/defaults-instancedobjectid2D.tga MeshVisualizerTestFiles/defaults-instancedobjectid3D.tga + MeshVisualizerTestFiles/defaults-objectid2D.tga + MeshVisualizerTestFiles/defaults-objectid3D.tga MeshVisualizerTestFiles/defaults-primitiveid2D.tga MeshVisualizerTestFiles/defaults-primitiveid3D.tga MeshVisualizerTestFiles/defaults-tbn.tga @@ -208,6 +210,8 @@ if(BUILD_GL_TESTS) MeshVisualizerTestFiles/defaults-wireframe3D.tga MeshVisualizerTestFiles/instancedobjectid2D.tga MeshVisualizerTestFiles/instancedobjectid3D.tga + MeshVisualizerTestFiles/objectid2D.tga + MeshVisualizerTestFiles/objectid3D.tga MeshVisualizerTestFiles/primitiveid-tn.tga MeshVisualizerTestFiles/primitiveid2D.tga MeshVisualizerTestFiles/primitiveid3D.tga diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index 3417e73d4..743eea173 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -115,6 +115,8 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { void setWireframeNotEnabled2D(); void setWireframeNotEnabled3D(); #ifndef MAGNUM_TARGET_GLES2 + void setObjectIdNotEnabled2D(); + void setObjectIdNotEnabled3D(); void setColorMapNotEnabled2D(); void setColorMapNotEnabled3D(); #endif @@ -134,6 +136,8 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { template void renderDefaultsWireframe3D(); #endif #ifndef MAGNUM_TARGET_GLES2 + template void renderDefaultsObjectId2D(); + template void renderDefaultsObjectId3D(); template void renderDefaultsInstancedObjectId2D(); template void renderDefaultsInstancedObjectId3D(); template void renderDefaultsVertexId2D(); @@ -215,6 +219,7 @@ constexpr struct { #endif {"wireframe w/o GS", MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}, #ifndef MAGNUM_TARGET_GLES2 + {"object ID", MeshVisualizerGL2D::Flag::ObjectId}, {"instanced object ID", MeshVisualizerGL2D::Flag::InstancedObjectId}, {"vertex ID", MeshVisualizerGL2D::Flag::VertexId}, #ifndef MAGNUM_TARGET_WEBGL @@ -245,6 +250,7 @@ constexpr struct { {"wireframe", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe, 1, 1}, #endif {"wireframe w/o GS", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 1}, + {"object ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::ObjectId, 1, 1}, {"instanced object ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::InstancedObjectId, 1, 1}, {"vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::VertexId, 1, 1}, #ifndef MAGNUM_TARGET_WEBGL @@ -265,6 +271,7 @@ constexpr struct { #endif {"wireframe w/o GS", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}, #ifndef MAGNUM_TARGET_GLES2 + {"object ID", MeshVisualizerGL3D::Flag::ObjectId}, {"instanced object ID", MeshVisualizerGL3D::Flag::InstancedObjectId}, {"vertex ID", MeshVisualizerGL3D::Flag::VertexId}, #ifndef MAGNUM_TARGET_WEBGL @@ -284,7 +291,9 @@ constexpr struct { {"wireframe + instanced object ID + T/N direction", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection}, {"wireframe + vertex ID + T/B direction", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection}, /* InstancedObjectId|BitangentDirection is disallowed (checked in - ConstructInvalidData3D), but this should work */ + ConstructInvalidData3D), but both ObjectId alone and + BitangentFromTangentDirection should work */ + {"object ID + bitangent direction", MeshVisualizerGL3D::Flag::ObjectId|MeshVisualizerGL3D::Flag::BitangentDirection}, {"instanced object ID + bitangent from tangent direction", MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection}, #endif }; @@ -310,6 +319,7 @@ constexpr struct { {"wireframe", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe, 1, 1}, #endif {"wireframe w/o GS", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 1}, + {"object ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::ObjectId, 1, 1}, {"instanced object ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::InstancedObjectId, 1, 1}, {"vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::VertexId, 1, 1}, #ifndef MAGNUM_TARGET_WEBGL @@ -345,12 +355,15 @@ constexpr struct { #endif }, #ifndef MAGNUM_TARGET_GLES2 + {"both object and primitive ID", + MeshVisualizerGL2D::Flag::ObjectId|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, + ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"both instanced object and primitive ID", MeshVisualizerGL2D::Flag::InstancedObjectId|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, - ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, - {"both instanced object and vertex ID", - MeshVisualizerGL2D::Flag::InstancedObjectId|MeshVisualizerGL2D::Flag::VertexId, - ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"} + ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, + {"both object and vertex ID", + MeshVisualizerGL2D::Flag::ObjectId|MeshVisualizerGL2D::Flag::VertexId, + ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"} #endif }; @@ -382,12 +395,15 @@ constexpr struct { #endif }, #ifndef MAGNUM_TARGET_GLES2 + {"both object and primitive ID", + MeshVisualizerGL3D::Flag::ObjectId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, + ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"both instanced object and primitive ID", MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, - ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, + ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, {"both vertex and primitive ID", MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, - ": Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, + ": Flag::ObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive"}, #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) {"geometry shader disabled but needed", @@ -477,6 +493,10 @@ constexpr struct { const char* file2D; const char* file3D; } ObjectVertexPrimitiveIdData[] { + {"object ID", + MeshVisualizerGL2D::Flag::ObjectId, + MeshVisualizerGL3D::Flag::ObjectId, + "objectid2D.tga", "objectid3D.tga"}, {"instanced object ID", MeshVisualizerGL2D::Flag::InstancedObjectId, MeshVisualizerGL3D::Flag::InstancedObjectId, @@ -752,6 +772,8 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::setWireframeNotEnabled2D, &MeshVisualizerGLTest::setWireframeNotEnabled3D, #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::setObjectIdNotEnabled2D, + &MeshVisualizerGLTest::setObjectIdNotEnabled3D, &MeshVisualizerGLTest::setColorMapNotEnabled2D, &MeshVisualizerGLTest::setColorMapNotEnabled3D, #endif @@ -780,6 +802,22 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::renderTeardown); #endif + #ifndef MAGNUM_TARGET_GLES2 + /* MSVC needs explicit type due to default template args */ + addTests({ + &MeshVisualizerGLTest::renderDefaultsObjectId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsObjectId2D, + #endif + &MeshVisualizerGLTest::renderDefaultsObjectId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsObjectId3D, + #endif + }, + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #endif + #ifndef MAGNUM_TARGET_GLES2 /* MSVC needs explicit type due to default template args */ addInstancedTests({ @@ -1394,6 +1432,7 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D() { MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}; shader.setTransformationProjectionMatrix({}) /* setViewportSize() works on both UBOs and classic */ + .setObjectId({}) .setColor({}) .setWireframeColor({}) .setWireframeWidth({}) @@ -1401,6 +1440,7 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D() { .setSmoothness({}); CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL2D::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setObjectId(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setColor(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was created with uniform buffers enabled\n" @@ -1425,6 +1465,7 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D() { shader.setProjectionMatrix({}) .setTransformationMatrix({}) /* setViewportSize() works on both UBOs and classic */ + .setObjectId({}) .setColor({}) .setWireframeColor({}) .setWireframeWidth({}) @@ -1433,6 +1474,7 @@ void MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D() { CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL3D::setProjectionMatrix(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL3D::setTransformationMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setObjectId(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setColor(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was created with uniform buffers enabled\n" "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was created with uniform buffers enabled\n" @@ -1582,6 +1624,32 @@ void MeshVisualizerGLTest::setWireframeNotEnabled3D() { } #ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::setObjectIdNotEnabled2D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MeshVisualizerGL2D shader{NoCreate}; + + std::ostringstream out; + Error redirectError{&out}; + shader.setObjectId({}); + CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL::setObjectId(): the shader was not created with object ID enabled\n"); +} + +void MeshVisualizerGLTest::setObjectIdNotEnabled3D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + MeshVisualizerGL3D shader{NoCreate}; + + std::ostringstream out; + Error redirectError{&out}; + shader.setObjectId({}); + CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL::setObjectId(): the shader was not created with object ID enabled\n"); +} + void MeshVisualizerGLTest::setColorMapNotEnabled2D() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -1919,6 +1987,114 @@ template void MeshVisualizerGLTest::renderDefault #endif #ifndef MAGNUM_TARGET_GLES2 +template void MeshVisualizerGLTest::renderDefaultsObjectId2D() { + 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 + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(16)); + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::ObjectId|flag}; + shader.bindColorMapTexture(_colorMapTexture); + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader.draw(circle); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join(_testDir, "MeshVisualizerTestFiles/defaults-objectid2D.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +template void MeshVisualizerGLTest::renderDefaultsObjectId3D() { + 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 + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Mesh icosphere = MeshTools::compile(Primitives::icosphereSolid(0)); + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::InstancedObjectId|flag}; + shader.bindColorMapTexture(_colorMapTexture); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(icosphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(icosphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join(_testDir, "MeshVisualizerTestFiles/defaults-objectid3D.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + template void MeshVisualizerGLTest::renderDefaultsInstancedObjectId2D() { auto&& data = InstancedObjectIdDefaultsData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -2710,7 +2886,7 @@ template void MeshVisualizerGLTest::renderObjectV } #ifndef MAGNUM_TARGET_GLES - if((data.flags2D & MeshVisualizerGL2D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + if((data.flags2D & MeshVisualizerGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif @@ -2739,7 +2915,9 @@ template void MeshVisualizerGLTest::renderObjectV Trade::MeshData circleData = Primitives::circle2DSolid(16); - if(data.flags2D & MeshVisualizerGL2D::Flag::InstancedObjectId) { + /* Add the instanced Object ID data even if visualizing just uniform object + ID, to test the attribute isn't accidentally accessed always */ + if(data.flags2D & MeshVisualizerGL2D::Flag::ObjectId) { Containers::Array ids{16}; /* Each two faces share the same ID */ for(std::size_t i = 0; i != ids.size(); ++i) ids[i] = i/2; @@ -2777,14 +2955,19 @@ template void MeshVisualizerGLTest::renderObjectV /* OTOH the wireframe color should stay at full channels, not mixed */ if(data.flags2D & MeshVisualizerGL2D::Flag::Wireframe) shader.setWireframeColor(0xffffff_rgbf); + /* For object ID we set a base ID to verify the uniform and instanced + ID get summed. */ + if(data.flags2D & MeshVisualizerGL2D::Flag::ObjectId) + shader.setObjectId(8); /* For vertex ID we don't want any repeat/wraparound as that causes disruptions in the gradient and test failures. There's 17 vertices also. */ if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) shader.setColorMapTransformation(1.0f, -1.0f/17.0f); /* For object/primitive ID there's no gradient so a wraparound is okay. - This should cover the first half of the colormap, in reverse order; - for primitive ID the whole colormap due to the repeat wrapping */ + For the object ID this should cover the second half of the colormap + (due to the uniform object ID), in reverse order; for primitive ID + the whole colormap due to the repeat wrapping */ else shader.setColorMapTransformation(0.5f, -1.0f/16.0f); shader.draw(circle); @@ -2796,6 +2979,7 @@ template void MeshVisualizerGLTest::renderObjectV }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { MeshVisualizerDrawUniform2D{} + .setObjectId(8) }}; MeshVisualizerMaterialUniform materialUniformData[1]; materialUniformData->setColor(0xffff00_rgbf); @@ -2825,7 +3009,7 @@ template void MeshVisualizerGLTest::renderObjectV Utility::Directory::join({_testDir, "MeshVisualizerTestFiles", data.file2D}), /* AMD has slight off-by-one errors compared to Intel, SwiftShader a bit more */ - (DebugTools::CompareImageToFile{_manager, 4.0f, 0.141f})); + (DebugTools::CompareImageToFile{_manager, 4.67f, 0.141f})); } template void MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D() { @@ -2842,7 +3026,7 @@ template void MeshVisualizerGLTest::renderObjectV } #ifndef MAGNUM_TARGET_GLES - if((data.flags3D & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + if((data.flags3D & MeshVisualizerGL3D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); #endif @@ -2871,7 +3055,9 @@ template void MeshVisualizerGLTest::renderObjectV Trade::MeshData icosphereData = Primitives::icosphereSolid(1); - if(data.flags3D & MeshVisualizerGL3D::Flag::InstancedObjectId) { + /* Add the instanced Object ID data even if visualizing just uniform object + ID, to test the attribute isn't accidentally accessed always */ + if(data.flags3D & MeshVisualizerGL3D::Flag::ObjectId) { Containers::Array ids{80}; /* Each four faces share the same ID */ for(std::size_t i = 0; i != ids.size(); ++i) ids[i] = i/4; @@ -2908,14 +3094,19 @@ template void MeshVisualizerGLTest::renderObjectV /* OTOH the wireframe color should stay at full channels, not mixed */ if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) shader.setWireframeColor(0xffffff_rgbf); + /* For object ID we set a base ID to verify the uniform and instanced + ID get summed. */ + if(data.flags3D & MeshVisualizerGL3D::Flag::ObjectId) + shader.setObjectId(20); /* For vertex ID we don't want any repeat/wraparound as that causes disruptions in the gradient and test failures. There's 42 vertices also. */ if(data.flags3D & MeshVisualizerGL3D::Flag::VertexId) shader.setColorMapTransformation(1.0f, -1.0f/42.0f); /* For object/primitive ID there's no gradient so a wraparound is okay. - This should cover the first half of the colormap, in reverse order; - for primitive ID the whole colormap due to the repeat wrapping */ + For the object ID this should cover the second half of the colormap + (due to the uniform object ID), in reverse order; for primitive ID + the whole colormap due to the repeat wrapping */ else shader.setColorMapTransformation(0.5f, -1.0f/40.0f); shader.draw(icosphere); @@ -2935,6 +3126,7 @@ template void MeshVisualizerGLTest::renderObjectV }}; GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { MeshVisualizerDrawUniform3D{} + .setObjectId(20) }}; MeshVisualizerMaterialUniform materialUniformData[1]; materialUniformData->setColor(0xffff00_rgbf); diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp index 10e7ffc53..f13e7a11b 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGL_Test.cpp @@ -152,17 +152,37 @@ void MeshVisualizerGL_Test::debugFlags3D() { #ifndef MAGNUM_TARGET_GLES2 void MeshVisualizerGL_Test::debugFlagsSupersets2D() { + /* InstancedObjectId is a superset of ObjectId so only one should be + printed */ + { + std::ostringstream out; + Debug{&out} << (MeshVisualizerGL2D::Flag::InstancedObjectId|MeshVisualizerGL2D::Flag::ObjectId); + CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL2D::Flag::InstancedObjectId\n"); + } + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ - std::ostringstream out; - Debug{&out} << (MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::UniformBuffers); - CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL2D::Flag::MultiDraw\n"); + { + std::ostringstream out; + Debug{&out} << (MeshVisualizerGL2D::Flag::MultiDraw|MeshVisualizerGL2D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL2D::Flag::MultiDraw\n"); + } } void MeshVisualizerGL_Test::debugFlagsSupersets3D() { + /* InstancedObjectId is a superset of ObjectId so only one should be + printed */ + { + std::ostringstream out; + Debug{&out} << (MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::ObjectId); + CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL3D::Flag::InstancedObjectId\n"); + } + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ - std::ostringstream out; - Debug{&out} << (MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::UniformBuffers); - CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL3D::Flag::MultiDraw\n"); + { + std::ostringstream out; + Debug{&out} << (MeshVisualizerGL3D::Flag::MultiDraw|MeshVisualizerGL3D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::MeshVisualizerGL3D::Flag::MultiDraw\n"); + } } #endif diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-objectid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-objectid2D.tga new file mode 100644 index 0000000000000000000000000000000000000000..b8d999b785751e463672bcc4bc84a07714f81fb8 GIT binary patch literal 960 zcmbu8v1$TA6h$X(y0yw@q|Z<6FRf-ZDjF9h5}QR>Nf9i>LPSI)1Q7*)$#aW^OVA>$ za5-n5&VV!Th42{4PaNX#6pAQ{(wEw^n3H#0oRCjk9FrUuKgbjpM`VVJL-K`-8TrP= z0h!}spDb{(N0zwQB`aL)kTov4w^?tfi*IC$i|#GfJL+PS>~YaOW&J~4d?mlQ=-yy` zpf1+Q5f|@y!gnWrBmUPP*u&N{322q&g)?rY%9(%mCKdkaz1#D;-@LzwyT72nth;xD pcZj=ply{oDccyo&cd~bQ#{HC??-SvZ;uGYP=#~7k5V9~>81nr|pzU;8N~e6U zy40mt+N!F4tu6j;(Cteb)*hB7-jE&WNS)WrM|^4G71@c7H1U$`Oh@XxV7}l>6VJ)6 zbfk%AWH&le=PC0YUz&JA_Mjtm9y34jrHMymFFMl1L$Ws=sdMVPpYH3vKeKasU-x~l loYOwLpT3v-{;i$UIdMOoH~0OlIyda~zccUuNK60M`T&7a!%^>>;7>vPyjln$J?MHWe6e&B2lw=(wD~KZHKdC}p zmX37q$HPz*g)#5U{ChAD=A-$3cXy}UNMyeLjm(eVsxKs^Pw7=3NkL!GtM18`eoL>q zBRl#Xz3P_i>G$-i8->21S6!0>{efQfj&$@Lz3Pe#^aH)>l8p2tz3PG-UB58DzR_-6 zLi72LcGaN~o8&oJ;yPuI$GA+1@gCP-C(bi`h3k@Zi775yE4VJb#WVcB;ThiIy2adi zif?e;x`v{_Wh+wR8loDnaG6r!HLjuDI#2K&t|8Wt*SIVudt8sA!W&$sGh>G2JoMIOT<{oQ z;#belc!J0H#q$%s!V~=L`4gVvEBxeK&-jS1@f3f==Vw3S8NSAko)37AXZYxOk8kiC zA3X2y0^i`hbIs8fFYy9D;BRv`m@2%)ThI6Gzr`!OacS2c!THo{LZiO1K#49bGvo~Q->e$6qnhxCYXA> z!xMa7gO;3Uzc;@Hr?XBWU_@?^en{Qv0pNN<`rdGZuGU|+4c}Hxh zH`K~oVoSZHR^AX5bw#bbR#4Z}%1dHLy`xsXC0gp1T6sbA)IGKGoEWGFYULTRceOG1 z_o~;#!uQHo*OBuCU*ftXU1E&ORtj8~-rx!T*YE^y zaQ(#8d5o`d{d7%5hRc>jj%$iayuf9O0xxk*<;Ho0Z*fhrro6;uAyMJF6$M`7GDVFS zc#bzSZ}1$~t=u^e7~kT$#cRC7Wr_}8<0;$r!#uSyxqN;UzdN+IeIsAbo~B){m{Rd(er68=yJTg574Zf z=>~LtTFNC=UN4Oe1*sOBR;K;0Z;H1 zzMpxIr+9)7Gw<*K?rU&+%sF72`K}f!EIM(=&Qo ze1n&`%sy?SSK?cIgHKOk&Uq@l#0%&4X&${AukZ|)X$MFc-{3W#;?u6M!gqLsC(iBe zGI}k(!(&`#ccank@D`8oX_pE(Pmgza=-h62th3P@X!W#wT4K9DzTFL&!BpEl)S7a; z$J*~Hw|lVt*UC#mm##}xZufBe6P4RN-cE&b`wp;EsocIJ?363F?+|+{mD_iWT~f-g NgciP*w({xS^an=>0z&`* diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instancedobjectid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/instancedobjectid3D.tga index 90e05699ef0a042b127b88194a0fbb3024ef658c..8c4e19df27f669c826cfad3657f793209a14018a 100644 GIT binary patch literal 2511 zcmZveOOD${5QdwYop&><0A73Vqj;^`@F7x^sQ0{WB*p*+yvWY+37mbD19;&B2v9ab z00Tz8k8DK@?ZFh-s{dnkbyYXX5T1q8->dK{{1pDUyu2LRdwf;+>-V3zYNPP+Yk2n! z&k9Szi%&RTN4p6B{HER_IoJ{=g?R*cz*2w=xk8(xWoY{EqE?{I&}y_CP5&*Hbb*zZ z$R?z$bbXdf^&Dms}QN}9QFm`0kIDv^s5kpOptnwVp{%#l>=A>8nEU(*S6~m0er@yJtqya8{PtW+XOTvfm2AAD`%K3oJ%+7 zs>fND1Rs(+-{d@FF0b3S=a4j10U9wI@(5-s_I?o;<6;Q=MITe8}5YGpmxu*vnJ%M zo#s0gD)O>WSbrX$`-n->@f{x_s`bZwe$D4>4)_04ycdH*CcW9gT_Wc1# zq`?wgOg_a*BDiRiNVuXpfs}!)cnx9+T|>%M%@>)v9iF2v@mmacnp*d|Y-cGz8juCj z93_J3fc1eE%gglNBk}0C#>LiivEuA@FUIN{{ltj&ei|` literal 2494 zcmZveJ8m3D5QclG%o#H?*8vnsk)Vj{R4Ef4U{656**tf4_Pty_R)%dDFyO#xyyBxISWP{FZ3~vq_9`jL}>2Rp>j$mfIL= zlQfU{75EF7JI2Oy$%l+NzQOOc|HQS&VGjfi)L^MxeWo%hRkFcQ&R^kS10hSr@J93I@KWa#8W*>X<>pQ2d zFjKSjw#3qJ53%S01d825vPDOa+$_kIQMbv`}dyZJ=GW z2{$iklZ~%kOwL!$wZ%>RBJhCvuGj;Q0@r8JTcYME0l5==NL&{87Sdeo;`E5@Q;qfY ztSG?TJ+mXs7kGnz50f$Gvgz~Z)i`ZT@qz_+=U)cWs{X%_fy%<;Ww@Y@12`VxIRb5X z4&`b+-10CU^$$K?E?$O|3ebNBb=^a3!35+`=_*C7eq>1V& zcnVslj0ZIDC%fR)k8kS+ZidetI-C@;avkyPpvS~D+#FvC=c%K~%wYPR)E{xTPz$`i z7ZRR_Y1MPUJLDCzua~k%caq~=4`(+Z8<6liQiO-IT|x}*k4K6SSO0LfMPBz2r~vW| zv+wFPkGo=%2;6;KJ%zB=Er`W)85wsj34g^BWw$%11(GFk%PGg7OH1j-c{rQn@6ZeG zYD;X`fD7~)e!J-=R6Ep{=+gt;pJVL=p72~7hzW$dr{caw)v_pWk=?a!#hUvj$tJKv z^AUHnj2{VF9=QYeq8wRF+p?DsU)`E*>^~O*36O1LAKW8{*%QorX`Yf_si)w YtWD_ejQ_w&{?E|4{^Z}|Z!-V?56QRlegFUf diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid2D.tga new file mode 100644 index 0000000000000000000000000000000000000000..bd42fbbedea22ea2f953665310fa0fdee7e7bf77 GIT binary patch literal 946 zcmbu8OAA3!7)8&R(Vh8OCVr6j`xOxrsVf5+NRlK;k_`MMYY%SiiNTH9t#yub=-%)9 zLdb;VlMnfj3!f;8l$caQwjeG@9~b9jhKp0Oz{Lq!;^LUBaB)P|xHu#mTpW-sF80U{ z7rSJSiyd;n#Wp$OVvC$`(Y?v~%)Zzl7hH6&v%az~*2oPP-K(tc?28rhz{Ou)=DR0n z#I#=G&WpQZS}$_v&0X;yPy4@qKJD}R`CsqT_q`TgBX_T<*V^5C!+XZvd(3;!d(wN= edw56z@d&143Xx%>jc`N(eo literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid3D.tga new file mode 100644 index 0000000000000000000000000000000000000000..81654345a7acbb4de7d5d8d75d368348e8426238 GIT binary patch literal 898 zcma)*$xcE+6a{;YtGDyB*!V{XDvBbC0|tZy6$&hxbPm#j)&CmjL=lFF&Rw_kwn z`$8y$_$h{BSPXwzmW^{ptr8ZN+N3n_BeDhi_#t`V8aMZ#gPTq0;^q$YakBxZxLJpD z+}wgo+^oShZf?L0ZdTzQH`m}1H&b6_{gl`BXWUGEh4o8b*I#k70&i3Nox1%pd`$6A z>h_o6Yl4@kf9G}m2Y2r*|7DMTiS9E#Yi(Zld)+IO+~4hQwy($kGLL&M@qA`^Jlpil zy%+Don)m2^MfV=@npEcgExbuah#ww{suh&ZnH< j&aHFY!*@w%y^UM*eQ4so6CJ4G$7B!6c%K}9uU!8FvlhPz literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-instancedobjectid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-instancedobjectid2D.tga index 5ed8df360ea63f9bf3dbda21b193c495f2565a41..0a64e6270d0910229f95cad8e9f3a266abb81c52 100644 GIT binary patch literal 8649 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 DJ-9LB literal 7604 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 aXPPpn3%QdCTvCupYN}z_1m=- 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 literal 8353 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 E0c{%?qW}N^ literal 7703 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