diff --git a/doc/changelog.dox b/doc/changelog.dox index dac2dfc14..a393ef820 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -221,6 +221,8 @@ See also: - New @ref Shaders::MeshVisualizer2D for 2D mesh visualization - Tangent space visualization in @ref Shaders::MeshVisualizer3D +- Object and primitive ID visualization in @ref Shaders::MeshVisualizer2D + and @ref Shaders::MeshVisualizer3D - Texture coordinate transformation in @ref Shaders::DistanceFieldVector, @ref Shaders::Flat, @ref Shaders::Phong and @ref Shaders::Vector - Ability to render Per-instance / per-vertex object ID in @ref Shaders::Flat diff --git a/doc/generated/CMakeLists.txt b/doc/generated/CMakeLists.txt index 284395717..1eed805d5 100644 --- a/doc/generated/CMakeLists.txt +++ b/doc/generated/CMakeLists.txt @@ -60,6 +60,7 @@ target_link_libraries(easings add_executable(shaders shaders.cpp) target_link_libraries(shaders Magnum::Magnum + Magnum::DebugTools Magnum::MeshTools Magnum::Primitives Magnum::Shaders diff --git a/doc/generated/shaders.cpp b/doc/generated/shaders.cpp index 388e712df..94dbbd8d1 100644 --- a/doc/generated/shaders.cpp +++ b/doc/generated/shaders.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -79,7 +80,9 @@ struct ShaderVisualizer: Platform::WindowlessApplication { std::string phong(); std::string meshVisualizer2D(); + std::string meshVisualizer2DPrimitiveId(); std::string meshVisualizer3D(); + std::string meshVisualizer3DPrimitiveId(); std::string flat(); std::string vertexColor(); @@ -129,7 +132,9 @@ int ShaderVisualizer::exec() { for(auto fun: {&ShaderVisualizer::phong, &ShaderVisualizer::meshVisualizer2D, + &ShaderVisualizer::meshVisualizer2DPrimitiveId, &ShaderVisualizer::meshVisualizer3D, + &ShaderVisualizer::meshVisualizer3DPrimitiveId, &ShaderVisualizer::flat, &ShaderVisualizer::vertexColor, &ShaderVisualizer::vector, @@ -185,6 +190,30 @@ std::string ShaderVisualizer::meshVisualizer2D() { return "meshvisualizer2d.png"; } +std::string ShaderVisualizer::meshVisualizer2DPrimitiveId() { + const Matrix3 projection = + Matrix3::projection(Vector2{3.0f})* + Matrix3::rotation(13.7_degf); + + const auto map = DebugTools::ColorMap::turbo(); + const Vector2i size{Int(map.size()), 1}; + GL::Texture2D colorMapTexture; + colorMapTexture + .setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::Repeat) + .setStorage(1, GL::TextureFormat::SRGB8Alpha8, size) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGB8Srgb, size, map}); + + Shaders::MeshVisualizer2D{Shaders::MeshVisualizer2D::Flag::PrimitiveId} + .setTransformationProjectionMatrix(projection) + .setColorMapTransformation(1.0f/255.0f, 1.0f/8.0f) + .bindColorMapTexture(colorMapTexture) + .draw(MeshTools::compile(Primitives::circle2DSolid(8))); + + return "meshvisualizer2d-primitiveid.png"; +} + std::string ShaderVisualizer::meshVisualizer3D() { const Matrix4 transformation = Transformation* Matrix4::rotationZ(13.7_degf)* @@ -208,6 +237,31 @@ std::string ShaderVisualizer::meshVisualizer3D() { return "meshvisualizer3d.png"; } +std::string ShaderVisualizer::meshVisualizer3DPrimitiveId() { + const Matrix4 transformation = Transformation* + Matrix4::rotationZ(13.7_degf)* + Matrix4::rotationX(-12.6_degf); + + const auto map = DebugTools::ColorMap::turbo(); + const Vector2i size{Int(map.size()), 1}; + GL::Texture2D colorMapTexture; + colorMapTexture + .setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::Repeat) + .setStorage(1, GL::TextureFormat::SRGB8Alpha8, size) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGB8Srgb, size, map}); + + Shaders::MeshVisualizer3D{Shaders::MeshVisualizer3D::Flag::PrimitiveId} + .setTransformationMatrix(transformation) + .setProjectionMatrix(Projection) + .setColorMapTransformation(1.0f/255.0f, 1.0f/32.0f) + .bindColorMapTexture(colorMapTexture) + .draw(MeshTools::compile(Primitives::uvSphereSolid(4, 8))); + + return "meshvisualizer3d-primitiveid.png"; +} + std::string ShaderVisualizer::flat() { Shaders::Flat3D{} .setColor(BaseColor) diff --git a/doc/shaders-meshvisualizer2d-primitiveid.png b/doc/shaders-meshvisualizer2d-primitiveid.png new file mode 100644 index 000000000..3d3ec25b6 Binary files /dev/null and b/doc/shaders-meshvisualizer2d-primitiveid.png differ diff --git a/doc/shaders-meshvisualizer3d-primitiveid.png b/doc/shaders-meshvisualizer3d-primitiveid.png new file mode 100644 index 000000000..45c35f685 Binary files /dev/null and b/doc/shaders-meshvisualizer3d-primitiveid.png differ diff --git a/doc/snippets/MagnumShaders.cpp b/doc/snippets/MagnumShaders.cpp index 7ab1a1e4e..b6174b29d 100644 --- a/doc/snippets/MagnumShaders.cpp +++ b/doc/snippets/MagnumShaders.cpp @@ -27,6 +27,9 @@ #include #include +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/DebugTools/ColorMap.h" #include "Magnum/GL/Buffer.h" #include "Magnum/GL/DefaultFramebuffer.h" #include "Magnum/GL/Framebuffer.h" @@ -34,9 +37,11 @@ #include "Magnum/GL/Renderbuffer.h" #include "Magnum/GL/RenderbufferFormat.h" #include "Magnum/GL/Texture.h" +#include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/Shaders/DistanceFieldVector.h" #include "Magnum/Shaders/Flat.h" @@ -358,6 +363,35 @@ shader.setColor(0x2f83cc_rgbf) .draw(mesh); /* [MeshVisualizer-usage-no-geom2] */ } + +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Containers::ArrayView objectIds; +Matrix4 transformationMatrix, projectionMatrix; +/* [MeshVisualizer-usage-object-id] */ +const auto map = DebugTools::ColorMap::turbo(); +const Vector2i size{Int(map.size()), 1}; + +GL::Texture2D colorMapTexture; +colorMapTexture + .setMinificationFilter(SamplerFilter::Linear) + .setMagnificationFilter(SamplerFilter::Linear) + .setWrapping(SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::RGBA8, size) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGB8Srgb, size, map}); + +Shaders::MeshVisualizer3D shader{ + Shaders::MeshVisualizer3D::Flag::InstancedObjectId}; +shader.setColorMapTransformation(0.0f, 1.0f/Math::max(objectIds)) + .setTransformationMatrix(transformationMatrix) + .setProjectionMatrix(projectionMatrix) + .bindColorMapTexture(colorMapTexture) + .draw(mesh); +/* [MeshVisualizer-usage-object-id] */ +} +#endif + #if !defined(__GNUC__) || defined(__clang__) || __GNUC__*100 + __GNUC_MINOR__ >= 500 { /* [Phong-usage-colored1] */ diff --git a/src/Magnum/Shaders/MeshVisualizer.cpp b/src/Magnum/Shaders/MeshVisualizer.cpp index cb433ba96..7ac9a5794 100644 --- a/src/Magnum/Shaders/MeshVisualizer.cpp +++ b/src/Magnum/Shaders/MeshVisualizer.cpp @@ -37,11 +37,19 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" #include "Magnum/GL/Shader.h" +#include "Magnum/GL/Texture.h" #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { +namespace { + enum: Int { + /* First four taken by Phong (A/D/S/N) */ + ColorMapTextureUnit = 4 + }; +} + namespace Implementation { MeshVisualizerBase::MeshVisualizerBase(FlagsBase flags): _flags{flags} { @@ -59,6 +67,16 @@ MeshVisualizerBase::MeshVisualizerBase(FlagsBase flags): _flags{flags} { MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::OES::standard_derivatives); #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(_flags & FlagBase::PrimitiveId && !(_flags >= FlagBase::PrimitiveIdFromVertexId)) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_VERSION_SUPPORTED(GL::Version::GL320); + #else + MAGNUM_ASSERT_GL_VERSION_SUPPORTED(GL::Version::GLES320); + #endif + } + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShaders")) @@ -69,10 +87,12 @@ MeshVisualizerBase::MeshVisualizerBase(FlagsBase flags): _flags{flags} { GL::Version MeshVisualizerBase::setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs) const { #ifndef MAGNUM_TARGET_GLES const GL::Version version = GL::Context::current().supportedVersion({GL::Version::GL320, GL::Version::GL310, GL::Version::GL300, GL::Version::GL210}); - CORRADE_INTERNAL_ASSERT(_flags & FlagBase::NoGeometryShader || version >= GL::Version::GL320); + /* Extended in MeshVisualizer3D for TBN visualization */ + CORRADE_INTERNAL_ASSERT(!(_flags & FlagBase::Wireframe) || _flags & FlagBase::NoGeometryShader || version >= GL::Version::GL320); #elif !defined(MAGNUM_TARGET_WEBGL) const GL::Version version = GL::Context::current().supportedVersion({GL::Version::GLES310, GL::Version::GLES300, GL::Version::GLES200}); - CORRADE_INTERNAL_ASSERT(_flags & FlagBase::NoGeometryShader || version >= GL::Version::GLES310); + /* Extended in MeshVisualizer3D for TBN visualization */ + CORRADE_INTERNAL_ASSERT(!(_flags & FlagBase::Wireframe) || _flags & FlagBase::NoGeometryShader || version >= GL::Version::GLES310); #else const GL::Version version = GL::Context::current().supportedVersion({GL::Version::GLES300, GL::Version::GLES200}); #endif @@ -82,6 +102,10 @@ GL::Version MeshVisualizerBase::setupShaders(GL::Shader& vert, GL::Shader& frag, vert.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") .addSource(_flags & FlagBase::NoGeometryShader ? "#define NO_GEOMETRY_SHADER\n" : "") + #ifndef MAGNUM_TARGET_GLES2 + .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : "") + #endif #ifdef MAGNUM_TARGET_WEBGL .addSource("#define SUBSCRIPTING_WORKAROUND\n") #elif defined(MAGNUM_TARGET_GLES2) @@ -90,14 +114,27 @@ GL::Version MeshVisualizerBase::setupShaders(GL::Shader& vert, GL::Shader& frag, #endif ; frag.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") - .addSource(_flags & FlagBase::NoGeometryShader ? "#define NO_GEOMETRY_SHADER\n" : ""); + .addSource(_flags & FlagBase::NoGeometryShader ? "#define NO_GEOMETRY_SHADER\n" : "") + #ifndef MAGNUM_TARGET_GLES2 + .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") + .addSource(_flags & FlagBase::PrimitiveId ? + (_flags >= FlagBase::PrimitiveIdFromVertexId ? + "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : + "#define PRIMITIVE_ID\n") : "") + #endif + ; return version; } MeshVisualizerBase& MeshVisualizerBase::setColor(const Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(_flags & (FlagBase::Wireframe|FlagBase::InstancedObjectId|FlagBase::PrimitiveId), + "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/primitive ID enabled", *this); + #else CORRADE_ASSERT(_flags & FlagBase::Wireframe, "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe enabled", *this); + #endif setUniform(_colorUniform, color); return *this; } @@ -116,11 +153,37 @@ MeshVisualizerBase& MeshVisualizerBase::setWireframeWidth(const Float width) { return *this; } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerBase& MeshVisualizerBase::setColorMapTransformation(const Float offset, const Float scale) { + CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::PrimitiveId), + "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/primitive ID enabled", *this); + setUniform(_colorMapOffsetScaleUniform, Vector2{offset, scale}); + return *this; } -MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisualizerBase{Implementation::MeshVisualizerBase::FlagBase(UnsignedByte(flags))} { +MeshVisualizerBase& MeshVisualizerBase::bindColorMapTexture(GL::Texture2D& texture) { + CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::PrimitiveId), + "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/primitive ID enabled", *this); + texture.bind(ColorMapTextureUnit); + return *this; +} +#endif + +} + +MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisualizerBase{Implementation::MeshVisualizerBase::FlagBase(UnsignedShort(flags))} { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), + "Shaders::MeshVisualizer2D: at least one visualization feature has to be enabled", ); + #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), "Shaders::MeshVisualizer2D: at least Flag::Wireframe has to be enabled", ); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags & Flag::InstancedObjectId) || !(flags & Flag::PrimitiveIdFromVertexId), + "Shaders::MeshVisualizer2D: Flag::InstancedObjectId and Flag::PrimitiveId are mutually exclusive", ); + #endif Utility::Resource rs{"MagnumShaders"}; GL::Shader vert{NoCreate}; @@ -139,6 +202,11 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua 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::PrimitiveId ? + (_flags >= FlagBase::PrimitiveIdFromVertexId ? + "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : + "#define PRIMITIVE_ID\n") : "") .addSource(rs.get("MeshVisualizer.geom")); } #else @@ -163,7 +231,10 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua #endif { bindAttributeLocation(Position::Location, "position"); - + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::InstancedObjectId) + bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + #endif #if !defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_TARGET_GLES2) #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isVersionSupported(GL::Version::GL310)) @@ -182,26 +253,46 @@ MeshVisualizer2D::MeshVisualizer2D(const Flags flags): Implementation::MeshVisua #endif { _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::Wireframe) { + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId + #endif + )) _colorUniform = uniformLocation("color"); + if(flags & Flag::Wireframe) { _wireframeColorUniform = uniformLocation("wireframeColor"); _wireframeWidthUniform = uniformLocation("wireframeWidth"); _smoothnessUniform = uniformLocation("smoothness"); if(!(flags & Flag::NoGeometryShader)) _viewportSizeUniform = uniformLocation("viewportSize"); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags & (Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId)) { + _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); + setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES setTransformationProjectionMatrix({}); - if(flags & Flag::Wireframe) { + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId + #endif + )) setColor(Color3(1.0f)); + if(flags & Flag::Wireframe) { /* Viewport size is zero by default */ setWireframeColor(Color3{0.0f}); setWireframeWidth(1.0f); setSmoothness(2.0f); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags & (Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId)) + setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); + #endif #endif } @@ -227,24 +318,39 @@ MeshVisualizer2D& MeshVisualizer2D::setSmoothness(const Float smoothness) { return *this; } -MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisualizerBase{Implementation::MeshVisualizerBase::FlagBase(UnsignedByte(flags))} { +MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisualizerBase{Implementation::MeshVisualizerBase::FlagBase(UnsignedShort(flags))} { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection) & ~Flag::NoGeometryShader), + CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection|Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizer3D: at least one visualization feature has to be enabled", ); CORRADE_ASSERT(!(flags & Flag::NoGeometryShader && flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)), "Shaders::MeshVisualizer3D: geometry shader has to be enabled when rendering TBN direction", ); CORRADE_ASSERT(!(flags & Flag::BitangentDirection && flags & Flag::BitangentFromTangentDirection), "Shaders::MeshVisualizer3D: Flag::BitangentDirection and Flag::BitangentFromTangentDirection are mutually exclusive", ); + #elif !defined(MAGNUM_TARGET_GLES2) + CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), + "Shaders::MeshVisualizer3D: at least one visualization feature has to be enabled", ); #else CORRADE_ASSERT(flags & (Flag::Wireframe & ~Flag::NoGeometryShader), "Shaders::MeshVisualizer3D: at least Flag::Wireframe has to be enabled", ); #endif + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags & Flag::InstancedObjectId) || !(flags & Flag::PrimitiveIdFromVertexId), + "Shaders::MeshVisualizer3D: Flag::InstancedObjectId and Flag::PrimitiveId are mutually exclusive", ); + #endif + Utility::Resource rs{"MagnumShaders"}; GL::Shader vert{NoCreate}; GL::Shader frag{NoCreate}; const GL::Version version = setupShaders(vert, frag, rs); + /* Expands the check done for wireframe in MeshVisualizerBase with TBN */ + #ifndef MAGNUM_TARGET_GLES + CORRADE_INTERNAL_ASSERT(!(flags & (Flag::NormalDirection|Flag::TangentDirection|Flag::BitangentDirection|Flag::BitangentFromTangentDirection)) || version >= GL::Version::GL320); + #elif !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + CORRADE_INTERNAL_ASSERT(!(flags & (Flag::NormalDirection|Flag::TangentDirection|Flag::BitangentDirection|Flag::BitangentFromTangentDirection)) || version >= GL::Version::GLES310); + #endif + vert.addSource("#define THREE_DIMENSIONS\n") #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) .addSource(flags & Flag::TangentDirection ? "#define TANGENT_DIRECTION\n" : "") @@ -275,6 +381,11 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua (*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::PrimitiveId ? + (_flags >= FlagBase::PrimitiveIdFromVertexId ? + "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : + "#define PRIMITIVE_ID\n") : "") .addSource(flags & Flag::TangentDirection ? "#define TANGENT_DIRECTION\n" : "") .addSource(flags & (Flag::BitangentDirection|Flag::BitangentFromTangentDirection) ? "#define BITANGENT_DIRECTION\n" : "") .addSource(flags & Flag::NormalDirection ? "#define NORMAL_DIRECTION\n" : "") @@ -302,7 +413,10 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua #endif { bindAttributeLocation(Position::Location, "position"); - + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::InstancedObjectId) + bindAttributeLocation(ObjectId::Location, "instanceObjectId"); + #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & Flag::TangentDirection || flags & Flag::BitangentFromTangentDirection) @@ -333,8 +447,13 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua { _transformationMatrixUniform = uniformLocation("transformationMatrix"); _projectionMatrixUniform = uniformLocation("projectionMatrix"); - if(flags & Flag::Wireframe) { + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId + #endif + )) _colorUniform = uniformLocation("color"); + if(flags & Flag::Wireframe) { _wireframeColorUniform = uniformLocation("wireframeColor"); _wireframeWidthUniform = uniformLocation("wireframeWidth"); } @@ -347,6 +466,12 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua if(!(flags & Flag::NoGeometryShader)) _viewportSizeUniform = uniformLocation("viewportSize"); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags & (Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId)) { + _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); + setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); + } + #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { _normalMatrixUniform = uniformLocation("normalMatrix"); @@ -360,8 +485,13 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua #ifdef MAGNUM_TARGET_GLES setTransformationMatrix({}); setProjectionMatrix({}); - if(flags & Flag::Wireframe) { + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId + #endif + )) setColor(Color3(1.0f)); + if(flags & Flag::Wireframe) { /* Viewport size is zero by default */ setWireframeColor(Color3{0.0f}); setWireframeWidth(1.0f); @@ -373,6 +503,10 @@ MeshVisualizer3D::MeshVisualizer3D(const Flags flags): Implementation::MeshVisua )) { setSmoothness(2.0f); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags & (Flag::InstancedObjectId|Flag::PrimitiveIdFromVertexId)) + setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); + #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { setNormalMatrix({}); @@ -454,6 +588,13 @@ Debug& operator<<(Debug& debug, const MeshVisualizer2D::Flag value) { #define _c(v) case MeshVisualizer2D::Flag::v: return debug << "::" #v; _c(NoGeometryShader) _c(Wireframe) + #ifndef MAGNUM_TARGET_GLES2 + _c(InstancedObjectId) + #ifndef MAGNUM_TARGET_WEBGL + _c(PrimitiveId) + #endif + _c(PrimitiveIdFromVertexId) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -475,6 +616,13 @@ Debug& operator<<(Debug& debug, const MeshVisualizer3D::Flag value) { _c(BitangentDirection) _c(NormalDirection) #endif + #ifndef MAGNUM_TARGET_GLES2 + _c(InstancedObjectId) + #ifndef MAGNUM_TARGET_WEBGL + _c(PrimitiveId) + #endif + _c(PrimitiveIdFromVertexId) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -486,7 +634,14 @@ Debug& operator<<(Debug& debug, const MeshVisualizer2D::Flags value) { return Containers::enumSetDebugOutput(debug, value, "Shaders::MeshVisualizer2D::Flags{}", { MeshVisualizer2D::Flag::Wireframe, /* Wireframe contains this on ES2 so it's not reported there */ - MeshVisualizer2D::Flag::NoGeometryShader + MeshVisualizer2D::Flag::NoGeometryShader, + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizer2D::Flag::InstancedObjectId, + MeshVisualizer2D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ + #ifndef MAGNUM_TARGET_WEBGL + MeshVisualizer2D::Flag::PrimitiveId + #endif + #endif }); } @@ -494,7 +649,20 @@ Debug& operator<<(Debug& debug, const MeshVisualizer3D::Flags value) { return Containers::enumSetDebugOutput(debug, value, "Shaders::MeshVisualizer3D::Flags{}", { MeshVisualizer3D::Flag::Wireframe, /* Wireframe contains this on ES2 so it's not reported there */ - MeshVisualizer3D::Flag::NoGeometryShader + MeshVisualizer3D::Flag::NoGeometryShader, + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + MeshVisualizer3D::Flag::TangentDirection, + MeshVisualizer3D::Flag::BitangentFromTangentDirection, + MeshVisualizer3D::Flag::BitangentDirection, + MeshVisualizer3D::Flag::NormalDirection, + #endif + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizer3D::Flag::InstancedObjectId, + MeshVisualizer3D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ + #ifndef MAGNUM_TARGET_WEBGL + MeshVisualizer3D::Flag::PrimitiveId + #endif + #endif }); } diff --git a/src/Magnum/Shaders/MeshVisualizer.frag b/src/Magnum/Shaders/MeshVisualizer.frag index 7ca72e3af..433878b9d 100644 --- a/src/Magnum/Shaders/MeshVisualizer.frag +++ b/src/Magnum/Shaders/MeshVisualizer.frag @@ -44,7 +44,7 @@ #extension GL_NV_shader_noperspective_interpolation: require #endif -#if defined(WIREFRAME_RENDERING) && !defined(TBN_DIRECTION) +#if (defined(WIREFRAME_RENDERING) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) || defined(INSTANCED_OBJECT_ID)) && !defined(TBN_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 1) #endif @@ -75,7 +75,7 @@ uniform lowp float wireframeWidth ; #elif defined(TBN_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 8) +layout(location = 9) #endif uniform lowp float lineWidth #ifndef GL_ES @@ -93,7 +93,27 @@ uniform lowp float smoothness = 2.0 #endif ; +#endif + +#if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) +#ifdef EXPLICIT_TEXTURE_LAYER +layout(binding = 4) +#endif +uniform lowp sampler2D colorMapTexture; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 6) +#endif +uniform lowp vec2 colorMapOffsetScale + #ifndef GL_ES + = vec2(1.0/512.0, 1.0/256.0) + #endif + ; +#define colorMapOffset colorMapOffsetScale.x +#define colorMapScale colorMapOffsetScale.y +#endif +#if defined(WIREFRAME_RENDERING) || defined(TBN_DIRECTION) #ifndef NO_GEOMETRY_SHADER #if !defined(GL_ES) || defined(GL_NV_shader_noperspective_interpolation) noperspective @@ -104,6 +124,13 @@ in lowp vec3 barycentric; #endif #endif +#ifdef PRIMITIVE_ID_FROM_VERTEX_ID +flat in highp uint interpolatedPrimitiveId; +#endif +#ifdef INSTANCED_OBJECT_ID +flat in highp uint interpolatedInstanceObjectId; +#endif + #ifdef TBN_DIRECTION in lowp vec4 backgroundColor; in lowp vec4 lineColor; @@ -117,6 +144,23 @@ out lowp vec4 fragmentColor; #endif void main() { + /* Map object/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(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + lowp vec4 faceColor = texture(colorMapTexture, vec2(colorMapOffset + float( + #ifdef INSTANCED_OBJECT_ID + interpolatedInstanceObjectId + #elif defined(PRIMITIVE_ID) + gl_PrimitiveID + #elif defined(PRIMITIVE_ID_FROM_VERTEX_ID) + interpolatedPrimitiveId + #else + #error mosra messed up + #endif + )*colorMapScale, 0.0)); + #endif + /* 1. For wireframe the line is on the triangle edges, thus dist = 0 at vertices and dist = w (= wireframeWidth) at center of smoothed edge. 2. For antialiased TBN (drawn alone) the line is in the center of the @@ -137,6 +181,24 @@ void main() { 0 -S -w 0 w S -w 0 w */ #if defined(WIREFRAME_RENDERING) || defined(TBN_DIRECTION) + + /* Fill with background color first: + + 1. For wireframe alone the color is supplied directly from the color + uniform + 2. For TBN alone the backgroundColor is supplied from the GS + + If primitive/object ID is enabled, it multiplies the color. + */ + #if defined(WIREFRAME_RENDERING) && !defined(TBN_DIRECTION) + fragmentColor = color; + #else + fragmentColor = backgroundColor; + #endif + #if defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + fragmentColor *= faceColor; + #endif + #ifndef NO_GEOMETRY_SHADER /* Distance to nearest side. If signed distance is involved when rendering TBN alone, we need to find an absolute distance */ @@ -146,7 +208,7 @@ void main() { lowp const float nearest = min(min(dist.x, dist.y), dist.z); #endif - /* Smooth step between two colors based on distance. + /* Mix in the line color: 1. For wireframe alone the width and colors are supplied directly from wireframeWidth, wireframeColor, color uniforms @@ -158,10 +220,11 @@ void main() { */ fragmentColor = mix( #if defined(WIREFRAME_RENDERING) && !defined(TBN_DIRECTION) - wireframeColor, color, + wireframeColor, #else - lineColor, backgroundColor, + lineColor, #endif + fragmentColor, #ifdef WIREFRAME_RENDERING smoothstep(wireframeWidth - smoothness, wireframeWidth + smoothness, nearest) @@ -178,10 +241,14 @@ void main() { const lowp vec3 d = fwidth(barycentric); const lowp vec3 factor = smoothstep(vec3(0.0), d*1.5, barycentric); const lowp float nearest = min(min(factor.x, factor.y), factor.z); - fragmentColor = mix(wireframeColor, color, nearest); + fragmentColor = mix(wireframeColor, fragmentColor, nearest); #endif + /* Object / Primitive ID visualization using a colormap */ + #elif defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) + fragmentColor = color*faceColor; + #else - #error neither wireframe or TBN direction is enabled, huh? + #error no visualization enabled, huh? #endif } diff --git a/src/Magnum/Shaders/MeshVisualizer.geom b/src/Magnum/Shaders/MeshVisualizer.geom index c5b9659bb..912bd5a5e 100644 --- a/src/Magnum/Shaders/MeshVisualizer.geom +++ b/src/Magnum/Shaders/MeshVisualizer.geom @@ -41,7 +41,7 @@ uniform lowp vec2 viewportSize; /* defaults to zero */ layout(triangles) in; -#if (defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION)) && defined(WIREFRAME_RENDERING) +#if (defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION)) && (defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID)) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 1) #endif @@ -63,7 +63,7 @@ uniform lowp vec4 wireframeColor #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 8) +layout(location = 9) #endif uniform lowp float lineWidth #ifndef GL_ES @@ -99,6 +99,15 @@ noperspective #endif out lowp vec3 dist; +#ifdef PRIMITIVE_ID_FROM_VERTEX_ID +flat in highp uint interpolatedVsPrimitiveId[]; +flat out highp uint interpolatedPrimitiveId; +#endif +#ifdef INSTANCED_OBJECT_ID +flat in highp uint interpolatedVsInstanceObjectId[]; +flat out highp uint interpolatedInstanceObjectId; +#endif + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) out lowp vec4 backgroundColor; out lowp vec4 lineColor; @@ -124,7 +133,7 @@ void emitQuad(vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointSc and to 0 otherwise. See the fragment shader for details. */ vec2 direction = normalize(endpointScreen - positionScreen); - #ifdef WIREFRAME_RENDERING + #if defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) float edgeDistance = 0.0; vec2 halfSide = lineWidth*vec2(-direction.y, direction.x); #else @@ -157,6 +166,20 @@ void emitQuad(vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointSc #endif void main() { + /* Passthrough for unchanged variables */ + #ifdef PRIMITIVE_ID_FROM_VERTEX_ID + interpolatedPrimitiveId = interpolatedVsPrimitiveId[0]; + #elif defined(PRIMITIVE_ID) + /* This has to be done explicitly, otherwise the fragment input is + undefined. Interestingly enough this worked well on Mesa / Intel with + the GS emitting always just 3 vertices, but not anymore when it emits + also the TBN direction. */ + gl_PrimitiveID = gl_PrimitiveIDIn; + #endif + #ifdef INSTANCED_OBJECT_ID + interpolatedInstanceObjectId = interpolatedVsInstanceObjectId[0]; + #endif + /* Screen position of each vertex */ vec2 p[3]; #ifdef TANGENT_DIRECTION @@ -181,7 +204,7 @@ void main() { #endif } - #ifdef WIREFRAME_RENDERING + #if defined(WIREFRAME_RENDERING) || defined(INSTANCED_OBJECT_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) /* Vector of each triangle side */ const vec2 v[3] = vec2[3]( p[2]-p[1], @@ -192,10 +215,15 @@ void main() { /* Compute area using perp-dot product */ const float area = abs(dot(vec2(-v[1].y, v[1].x), v[2])); - /* Add distance to opposite side to each vertex */ + /* If wireframe is enabled, add distance to opposite side to each vertex. + Otherwise make all distances the same to avoid any lines being drawn. */ for(int i = 0; i != 3; ++i) { dist = vec3(0.0, 0.0, 0.0); + #ifdef WIREFRAME_RENDERING dist[i] = area/length(v[i]); + #else + dist = vec3(area/length(v[i])); + #endif #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) backgroundColor = color; lineColor = wireframeColor; diff --git a/src/Magnum/Shaders/MeshVisualizer.h b/src/Magnum/Shaders/MeshVisualizer.h index 32816213f..d6a280a24 100644 --- a/src/Magnum/Shaders/MeshVisualizer.h +++ b/src/Magnum/Shaders/MeshVisualizer.h @@ -42,13 +42,18 @@ namespace Implementation { class MAGNUM_SHADERS_EXPORT MeshVisualizerBase: public GL::AbstractShaderProgram { protected: - enum class FlagBase: UnsignedByte { + enum class FlagBase: UnsignedShort { #ifndef MAGNUM_TARGET_GLES2 Wireframe = 1 << 0, #else Wireframe = (1 << 0) | (1 << 1), #endif - NoGeometryShader = 1 << 1 + NoGeometryShader = 1 << 1, + #ifndef MAGNUM_TARGET_GLES2 + InstancedObjectId = 1 << 2, + PrimitiveId = 1 << 3, + PrimitiveIdFromVertexId = (1 << 4)|PrimitiveId + #endif }; typedef Containers::EnumSet FlagsBase; @@ -62,6 +67,8 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerBase: public GL::AbstractShaderProgram MeshVisualizerBase& setColor(const Color4& color); MeshVisualizerBase& setWireframeColor(const Color4& color); MeshVisualizerBase& setWireframeWidth(Float width); + MeshVisualizerBase& setColorMapTransformation(Float offset, Float scale); + MeshVisualizerBase& bindColorMapTexture(GL::Texture2D& texture); /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES @@ -77,6 +84,9 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerBase: public GL::AbstractShaderProgram _wireframeWidthUniform{3}, _smoothnessUniform{4}, _viewportSizeUniform{5}; + #ifndef MAGNUM_TARGET_GLES2 + Int _colorMapOffsetScaleUniform{6}; + #endif }; } @@ -85,12 +95,24 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerBase: public GL::AbstractShaderProgram @brief 2D mesh visualization shader @m_since_latest -Uses the geometry shader to visualize wireframe of 3D meshes. You need to -provide the @ref Position attribute in your triangle mesh. Use -@ref setTransformationProjectionMatrix(), @ref setColor() and others to +Visualizes wireframe, per-vertex/per-instance object ID or primitive ID of 2D +meshes. You need to provide the @ref Position attribute in your triangle mesh. +Use @ref setTransformationProjectionMatrix(), @ref setColor() and others to configure the shader. +@m_class{m-row} + +@parblock + +@m_div{m-col-m-4 m-col-t-6 m-push-m-2 m-nopadt m-nopadx} @image html shaders-meshvisualizer2d.png width=256px +@m_enddiv + +@m_div{m-col-m-4 m-col-t-6 m-push-m-2 m-nopadt m-nopadx} +@image html shaders-meshvisualizer2d-primitiveid.png width=256px +@m_enddiv + +@endparblock The shader expects that you enable wireframe visualization by passing an appropriate @ref Flag to the constructor --- there's no default behavior with @@ -114,6 +136,21 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali */ typedef GL::Attribute<4, Float> VertexIndex; + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief (Instanced) object ID + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::UnsignedInt. + * Used only if @ref Flag::InstancedObjectId is set. + * @requires_gles30 Object ID input requires integer attributes, which + * are not available in OpenGL ES 2.0. + * @requires_webgl20 Object ID input requires integer attributes, which + * are not available in WebGL 1.0. + */ + typedef Generic3D::ObjectId ObjectId; + #endif + enum: UnsignedInt { /** * Color shader output. @ref shaders-generic "Generic output", @@ -128,7 +165,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali * * @see @ref Flags, @ref MeshVisualizer2D() */ - enum class Flag: UnsignedByte { + enum class Flag: UnsignedShort { /** * Visualize wireframe. On OpenGL ES 2.0 this also enables * @ref Flag::NoGeometryShader. @@ -145,7 +182,24 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali * attribute in the mesh. In OpenGL ES 2.0 enabled alongside * @ref Flag::Wireframe. */ - NoGeometryShader = 1 << 1 + NoGeometryShader = 1 << 1, + + #ifndef MAGNUM_TARGET_GLES2 + /** @copydoc MeshVisualizer3D::Flag::InstancedObjectId */ + InstancedObjectId = 1 << 2, + + #ifndef MAGNUM_TARGET_WEBGL + /** @copydoc MeshVisualizer3D::Flag::PrimitiveId */ + PrimitiveId = 1 << 3, + #endif + + /** @copydoc MeshVisualizer3D::Flag::PrimitiveIdFromVertexId */ + #ifndef MAGNUM_TARGET_WEBGL + PrimitiveIdFromVertexId = (1 << 4)|PrimitiveId + #else + PrimitiveIdFromVertexId = (1 << 4)|(1 << 3) + #endif + #endif }; /** @brief Flags */ @@ -212,8 +266,11 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali * @brief Set base object color * @return Reference to self (for method chaining) * - * Initial value is @cpp 0xffffffff_rgbaf @ce. Expects that - * @ref Flag::Wireframe is enabled. + * Initial value is @cpp 0xffffffff_rgbaf @ce. Expects that either + * @ref Flag::Wireframe or @ref Flag::InstancedObjectId / + * @ref Flag::PrimitiveId is enabled. In case of the latter, the color + * is multiplied with the color map coming from + * @ref bindColorMapTexture(). */ MeshVisualizer2D& setColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerBase::setColor(color)); @@ -242,6 +299,18 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali return static_cast(Implementation::MeshVisualizerBase::setWireframeWidth(width)); } + #ifndef MAGNUM_TARGET_GLES2 + /** @copydoc MeshVisualizer3D::setColorMapTransformation() */ + MeshVisualizer2D& setColorMapTransformation(Float offset, Float scale) { + return static_cast(Implementation::MeshVisualizerBase::setColorMapTransformation(offset, scale)); + } + + /** @copydoc MeshVisualizer3D::bindColorMapTexture() */ + MeshVisualizer2D& bindColorMapTexture(GL::Texture2D& texture) { + return static_cast(Implementation::MeshVisualizerBase::bindColorMapTexture(texture)); + } + #endif + /** * @brief Set line smoothness * @return Reference to self (for method chaining) @@ -259,16 +328,32 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer2D: public Implementation::MeshVisuali /** @brief 3D mesh visualization shader -Uses the geometry shader to visualize wireframe or tangent space of 3D meshes. -You need to provide the @ref Position attribute in your triangle mesh at the -very least. Use @ref setTransformationProjectionMatrix(), @ref setColor() and -others to configure the shader. +Visualizes wireframe, per-vertex/per-instance object ID, primitive ID or +tangent space of 3D meshes. You need to provide the @ref Position attribute in +your triangle mesh at the very least. Use @ref setTransformationProjectionMatrix(), +@ref setColor() and others to configure the shader. + +@m_class{m-row} +@parblock + +@m_div{m-col-m-4 m-col-t-6 m-push-m-2 m-text-center m-nopadt m-nopadx} @image html shaders-meshvisualizer3d.png width=256px +@ref Shaders-MeshVisualizer-wireframe, \n +@ref Shaders-MeshVisualizer-tbn +@m_enddiv + +@m_div{m-col-m-4 m-col-t-6 m-push-m-2 m-text-center m-nopadt m-nopadx} +@image html shaders-meshvisualizer3d-primitiveid.png width=256px +@ref Shaders-MeshVisualizer-object-id +@m_enddiv -The shader expects that you enable either wireframe visualization or tangent -space visualization by passing an appropriate @ref Flag to the constructor --- -there's no default behavior with nothing enabled. +@endparblock + +The shader expects that you enable wireframe visualization, tangent space +visualization or object/primitive ID visualization by passing an appropriate +@ref Flag to the constructor --- there's no default behavior with nothing +enabled. @section Shaders-MeshVisualizer-wireframe Wireframe visualization @@ -355,6 +440,37 @@ Rendering setup: @snippet MagnumShaders.cpp MeshVisualizer-usage-tbn2 +@section Shaders-MeshVisualizer-object-id Object and primitive ID visualization + +If the mesh contains a per-vertex (or instanced) @ref ObjectId, it can be +visualized by enabling @ref Flag::InstancedObjectId. For the actual +visualization you need to provide a color map using @ref bindColorMapTexture() +and use @ref setColorMapTransformation() to map given range of discrete IDs to +the @f$ [0, 1] @f$ texture range. Various colormap presets are in the +@ref DebugTools::ColorMap namespace. Example usage: + +@snippet MagnumShaders.cpp MeshVisualizer-usage-object-id + +If you enable @ref Flag::PrimitiveId instead, the shader will use the color map +to visualize the order in which primitives are drawn. That's useful for example +to see how well is the mesh optimized for a post-transform vertex cache. This +by default relies on the @glsl gl_PrimitiveID @ce GLSL builtin; with +@ref Flag::PrimitiveIdFromVertexId it's emulated using @glsl gl_VertexID @ce, +expecting you to draw a non-indexed triangle mesh. You can use +@ref MeshTools::duplicate() (and potentially @ref MeshTools::generateIndices()) +to conveniently convert the mesh to a non-indexed @ref MeshPrimitive::Triangles. + +@requires_gl32 The `gl_PrimitiveID` shader variable is not available on OpenGL + 3.1 and lower. +@requires_gl30 The `gl_VertexID` shader variable is not available on OpenGL + 2.1. +@requires_gles32 The `gl_PrimitiveID` shader variable is not available on + OpenGL ES 3.1 and lower. +@requires_gles30 The `gl_VertexID` shader variable is not available on OpenGL + ES 2.0. +@requires_gles `gl_PrimitiveID` is not available in WebGL. +@requires_webgl20 `gl_VertexID` is not available in WebGL 1.0. + @see @ref shaders, @ref MeshVisualizer2D @todo Understand and add support wireframe width/smoothness without GS */ @@ -427,6 +543,21 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali */ typedef GL::Attribute<4, Float> VertexIndex; + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief (Instanced) object ID + * @m_since_latest + * + * @ref shaders-generic "Generic attribute", @ref Magnum::UnsignedInt. + * Used only if @ref Flag::InstancedObjectId is set. + * @requires_gles30 Object ID input requires integer attributes, which + * are not available in OpenGL ES 2.0. + * @requires_webgl20 Object ID input requires integer attributes, which + * are not available in WebGL 1.0. + */ + typedef Generic3D::ObjectId ObjectId; + #endif + enum: UnsignedInt { /** * Color shader output. @ref shaders-generic "Generic output", @@ -441,7 +572,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * * @see @ref Flags, @ref MeshVisualizer() */ - enum class Flag: UnsignedByte { + enum class Flag: UnsignedShort { /** * Visualize wireframe. On OpenGL ES 2.0 this also enables * @ref Flag::NoGeometryShader. @@ -465,6 +596,54 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali */ NoGeometryShader = 1 << 1, + #ifndef MAGNUM_TARGET_GLES2 + /** + * Visualize instanced object ID. You need to provide the + * @ref ObjectId attribute in the mesh. Mutually exclusive with + * @ref Flag::PrimitiveId. + * @requires_gles30 Object ID input requires integer attributes, + * which are not available in OpenGL ES 2.0 or WebGL 1.0. + * @m_since_latest + */ + InstancedObjectId = 1 << 2, + + #ifndef MAGNUM_TARGET_WEBGL + /** + * Visualize primitive ID (@cpp gl_PrimitiveID @ce). Mutually + * exclusive with @ref Flag::InstancedObjectId. See also + * @ref Flag::PrimitiveIdFromVertexId. + * @requires_gl32 The `gl_PrimitiveID` shader variable is not + * available on OpenGL 3.1 and lower. + * @requires_gles30 Not defined in OpenGL ES 2.0. + * @requires_gles32 The `gl_PrimitiveID` shader variable is not + * available on OpenGL ES 3.1 and lower. + * @requires_gles `gl_PrimitiveID` is not available in WebGL. + * @m_since_latest + */ + PrimitiveId = 1 << 3, + #endif + + /** + * Visualize primitive ID on a non-indexed triangle mesh using + * @cpp gl_VertexID/3 @ce. Implicitly enables + * @ref Flag::PrimitiveId, mutually exclusive with + * @ref Flag::InstancedObjectId. Usable on OpenGL < 3.2, + * OpenGL ES < 3.2 and WebGL where @cpp gl_PrimitiveID @ce is not + * available. + * @requires_gl30 The `gl_VertexID` shader variable is not + * available on OpenGL 2.1. + * @requires_gles30 The `gl_VertexID` shader variable is not + * available on OpenGL ES 2.0. + * @requires_webgl20 `gl_VertexID` is not available in WebGL 1.0. + * @m_since_latest + */ + #ifndef MAGNUM_TARGET_WEBGL + PrimitiveIdFromVertexId = (1 << 4)|PrimitiveId, + #else + PrimitiveIdFromVertexId = (1 << 4)|(1 << 3), + #endif + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) /** * Visualize tangent direction with red lines pointing out of @@ -479,7 +658,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles Geometry shaders are not available in WebGL. * @m_since_latest */ - TangentDirection = 1 << 2, + TangentDirection = 1 << 5, /** * Visualize bitangent direction with green lines pointing out of @@ -496,7 +675,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles Geometry shaders are not available in WebGL. * @m_since_latest */ - BitangentFromTangentDirection = 1 << 3, + BitangentFromTangentDirection = 1 << 6, /** * Visualize bitangent direction with green lines pointing out of @@ -513,7 +692,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles Geometry shaders are not available in WebGL. * @m_since_latest */ - BitangentDirection = 1 << 4, + BitangentDirection = 1 << 7, /** * Visualize normal direction with blue lines pointing out of @@ -527,7 +706,7 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @requires_gles Geometry shaders are not available in WebGL. * @m_since_latest */ - NormalDirection = 1 << 5 + NormalDirection = 1 << 8 #endif }; @@ -650,8 +829,11 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali * @brief Set base object color * @return Reference to self (for method chaining) * - * Initial value is @cpp 0xffffffff_rgbaf @ce. Expects that - * @ref Flag::Wireframe is enabled. + * Initial value is @cpp 0xffffffff_rgbaf @ce. Expects that either + * @ref Flag::Wireframe or @ref Flag::InstancedObjectId / + * @ref Flag::PrimitiveId is enabled. In case of the latter, the color + * is multiplied with the color map coming from + * @ref bindColorMapTexture(). */ MeshVisualizer3D& setColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerBase::setColor(color)); @@ -680,6 +862,62 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali return static_cast(Implementation::MeshVisualizerBase::setWireframeWidth(width)); } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Set color map transformation + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Offset and scale applied to the input value coming either from the + * @ref ObjectId attribute or @glsl gl_PrimitiveID @ce, resulting value + * is then used to fetch a color from a color map bound with + * @ref bindColorMapTexture(). Initial value is @cpp 1.0f/512.0f @ce + * and @cpp 1.0/256.0f @ce, meaning that for a 256-entry colormap the + * first 256 values get an exact color from it and the next values will + * be either clamped to last color or repeated depending on the color + * map texture wrapping mode. Expects that either + * @ref Flag::InstancedObjectId or @ref Flag::PrimitiveId / + * @ref Flag::PrimitiveIdFromVertexId is enabled. + * + * Note that this shader doesn't directly offer a + * @ref Flat::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. + * @requires_gles30 Object ID visualization requires integer attributes + * while primitive ID visualization requires the `gl_VertexID` / + * `gl_PrimitiveID` builtins, neither of which is available in + * OpenGL ES 2.0. + * @requires_webgl20 Object ID visualization requires integer + * attributes while primitive ID visualization requires at least + * the `gl_VertexID` builtin, neither of which is available in + * WebGL 1. + */ + MeshVisualizer3D& setColorMapTransformation(Float offset, Float scale) { + return static_cast(Implementation::MeshVisualizerBase::setColorMapTransformation(offset, scale)); + } + + /** + * @brief Bind a color map texture + * @return Reference to self (for method chaining) + * @m_since_latest + * + * See also @ref setColorMapTransformation(). Expects that either + * @ref Flag::InstancedObjectId or @ref Flag::PrimitiveId / + * @ref Flag::PrimitiveIdFromVertexId is enabled. + * @requires_gles30 Object ID visualization requires integer attributes + * while primitive ID visualization requires the `gl_VertexID` / + * `gl_PrimitiveID` builtins, neither of which is available in + * OpenGL ES 2.0. + * @requires_webgl20 Object ID visualization requires integer + * attributes while primitive ID visualization requires at least + * the `gl_VertexID` builtin, neither of which is available in + * WebGL 1. + */ + MeshVisualizer3D& bindColorMapTexture(GL::Texture2D& texture) { + return static_cast(Implementation::MeshVisualizerBase::bindColorMapTexture(texture)); + } + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) /** * @brief Set line width @@ -734,11 +972,11 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizer3D: public Implementation::MeshVisuali private: Int _transformationMatrixUniform{0}, - _projectionMatrixUniform{6}; + _projectionMatrixUniform{7}; #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - Int _normalMatrixUniform{7}, - _lineWidthUniform{8}, - _lineLengthUniform{9}; + Int _normalMatrixUniform{8}, + _lineWidthUniform{9}, + _lineLengthUniform{10}; #endif }; diff --git a/src/Magnum/Shaders/MeshVisualizer.vert b/src/Magnum/Shaders/MeshVisualizer.vert index 63352b9d1..11108f30f 100644 --- a/src/Magnum/Shaders/MeshVisualizer.vert +++ b/src/Magnum/Shaders/MeshVisualizer.vert @@ -47,7 +47,7 @@ uniform highp mat4 transformationMatrix #endif ; #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 6) +layout(location = 7) #endif uniform highp mat4 projectionMatrix #ifndef GL_ES @@ -60,7 +60,7 @@ uniform highp mat4 projectionMatrix #if defined(TANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 7) +layout(location = 8) #endif uniform highp mat3 normalMatrix #ifndef GL_ES @@ -69,7 +69,7 @@ uniform highp mat3 normalMatrix ; #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 9) +layout(location = 10) #endif uniform highp float lineLength #ifndef GL_ES @@ -122,6 +122,27 @@ in lowp float vertexIndex; out vec3 barycentric; #endif +#ifdef INSTANCED_OBJECT_ID +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = OBJECT_ID_ATTRIBUTE_LOCATION) +#endif +in highp uint instanceObjectId; + +#if defined(NO_GEOMETRY_SHADER) || (!defined(WIREFRAME_RENDERING) && !defined(TANGENT_DIRECTION) && !defined(BITANGENT_DIRECTION) && !defined(BITANGENT_FROM_TANGENT_DIRECTION) && !defined(NORMAL_DIRECTION)) +flat out highp uint interpolatedInstanceObjectId; +#else +flat out highp uint interpolatedVsInstanceObjectId; +#endif +#endif + +#ifdef PRIMITIVE_ID_FROM_VERTEX_ID +#if defined(NO_GEOMETRY_SHADER) || (!defined(WIREFRAME_RENDERING) && !defined(TANGENT_DIRECTION) && !defined(BITANGENT_DIRECTION) && !defined(BITANGENT_FROM_TANGENT_DIRECTION) && !defined(NORMAL_DIRECTION)) +flat out highp uint interpolatedPrimitiveId; +#else +flat out highp uint interpolatedVsPrimitiveId; +#endif +#endif + #ifdef TANGENT_DIRECTION out highp vec4 tangentEndpoint; #endif @@ -171,6 +192,22 @@ void main() { #else barycentric[gl_VertexID % 3] = 1.0; #endif + #endif + #ifdef INSTANCED_OBJECT_ID + #if defined(NO_GEOMETRY_SHADER) || (!defined(WIREFRAME_RENDERING) && !defined(TANGENT_DIRECTION) && !defined(BITANGENT_DIRECTION) && !defined(BITANGENT_FROM_TANGENT_DIRECTION) && !defined(NORMAL_DIRECTION)) + interpolatedInstanceObjectId + #else + interpolatedVsInstanceObjectId + #endif + = instanceObjectId; + #endif + #ifdef PRIMITIVE_ID_FROM_VERTEX_ID + #if defined(NO_GEOMETRY_SHADER) || (!defined(WIREFRAME_RENDERING) && !defined(TANGENT_DIRECTION) && !defined(BITANGENT_DIRECTION) && !defined(BITANGENT_FROM_TANGENT_DIRECTION) && !defined(NORMAL_DIRECTION)) + interpolatedPrimitiveId + #else + interpolatedVsPrimitiveId + #endif + = uint(gl_VertexID/3); #endif } diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index cf13c938e..53a1d5a76 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -152,15 +152,26 @@ if(BUILD_GL_TESTS) MagnumOpenGLTester FILES MeshVisualizerTestFiles/bitangents-from-tangents.tga + MeshVisualizerTestFiles/defaults-objectid2D.tga + MeshVisualizerTestFiles/defaults-objectid3D.tga + MeshVisualizerTestFiles/defaults-primitiveid2D.tga + MeshVisualizerTestFiles/defaults-primitiveid3D.tga MeshVisualizerTestFiles/defaults-tbn.tga MeshVisualizerTestFiles/defaults-wireframe2D.tga MeshVisualizerTestFiles/defaults-wireframe3D.tga + MeshVisualizerTestFiles/objectid2D.tga + MeshVisualizerTestFiles/objectid3D.tga + MeshVisualizerTestFiles/primitiveid-tn.tga + MeshVisualizerTestFiles/primitiveid2D.tga + MeshVisualizerTestFiles/primitiveid3D.tga MeshVisualizerTestFiles/tbn-wide.tga MeshVisualizerTestFiles/tbn.tga MeshVisualizerTestFiles/wireframe-nogeo2D.tga MeshVisualizerTestFiles/wireframe-nogeo3D.tga + MeshVisualizerTestFiles/wireframe-nogeo-objectid2D.tga + MeshVisualizerTestFiles/wireframe-nogeo-objectid3D.tga MeshVisualizerTestFiles/wireframe-perspective.tga - MeshVisualizerTestFiles/wireframe-tn.tga + MeshVisualizerTestFiles/wireframe-primitiveid-tn.tga MeshVisualizerTestFiles/wireframe-tn-smooth.tga MeshVisualizerTestFiles/wireframe-wide2D.tga MeshVisualizerTestFiles/wireframe-wide3D.tga diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index f475fd75d..4a173cce1 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -32,6 +32,7 @@ #include #include +#include "Magnum/DebugTools/ColorMap.h" #include "Magnum/DebugTools/CompareImage.h" #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" @@ -40,12 +41,15 @@ #include "Magnum/GL/Mesh.h" #include "Magnum/GL/Renderbuffer.h" #include "Magnum/GL/RenderbufferFormat.h" +#include "Magnum/GL/Texture.h" +#include "Magnum/GL/TextureFormat.h" #include "Magnum/Image.h" #include "Magnum/ImageView.h" #include "Magnum/PixelFormat.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/MeshTools/Combine.h" #include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/GenerateIndices.h" @@ -84,6 +88,10 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { void setWireframeNotEnabled2D(); void setWireframeNotEnabled3D(); + #ifndef MAGNUM_TARGET_GLES2 + void setColorMapNotEnabled2D(); + void setColorMapNotEnabled3D(); + #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void setTangentBitangentNormalNotEnabled3D(); #endif @@ -94,10 +102,22 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void renderDefaultsWireframe2D(); void renderDefaultsWireframe3D(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + void renderDefaultsObjectId2D(); + void renderDefaultsObjectId3D(); + void renderDefaultsPrimitiveId2D(); + void renderDefaultsPrimitiveId3D(); + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void renderDefaultsTangentBitangentNormal(); #endif void renderWireframe2D(); void renderWireframe3D(); + #ifndef MAGNUM_TARGET_GLES2 + void renderObjectPrimitiveId2D(); + void renderObjectPrimitiveId3D(); + #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void renderWireframe3DPerspective(); void renderTangentBitangentNormal(); @@ -109,10 +129,10 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { GL::Renderbuffer _color{NoCreate}, _depth{NoCreate}; + GL::Framebuffer _framebuffer{NoCreate}; #ifndef MAGNUM_TARGET_GLES2 - GL::Renderbuffer _objectId{NoCreate}; + GL::Texture2D _colorMapTexture; #endif - GL::Framebuffer _framebuffer{NoCreate}; }; /* @@ -122,8 +142,8 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { - Mesa AMD - SwiftShader ES2/ES3 - ARM Mali (Huawei P10) ES2/ES3 (except TBN visualization) - - WebGL 1 / 2 (on Mesa Intel) - - iPhone 6 w/ iOS 12.4 + - WebGL 1 / 2 (on Mesa Intel) (except primitive/object ID) + - iPhone 6 w/ iOS 12.4 (except primitive/object ID) */ using namespace Math::Literals; @@ -133,13 +153,27 @@ constexpr struct { MeshVisualizer2D::Flags flags; } ConstructData2D[] { {"wireframe w/o GS", MeshVisualizer2D::Flag::Wireframe|MeshVisualizer2D::Flag::NoGeometryShader}, + #ifndef MAGNUM_TARGET_GLES2 + {"object ID", MeshVisualizer2D::Flag::InstancedObjectId}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizer2D::Flag::PrimitiveId}, + #endif + {"primitive ID from vertex ID", MeshVisualizer2D::Flag::PrimitiveIdFromVertexId} + #endif }; constexpr struct { const char* name; MeshVisualizer3D::Flags flags; } ConstructData3D[] { - {"wireframe w/o GS", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::NoGeometryShader} + {"wireframe w/o GS", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::NoGeometryShader}, + #ifndef MAGNUM_TARGET_GLES2 + {"object ID", MeshVisualizer3D::Flag::InstancedObjectId}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizer3D::Flag::InstancedObjectId}, + #endif + {"primitive ID from vertex ID", MeshVisualizer3D::Flag::PrimitiveIdFromVertexId} + #endif }; #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -154,7 +188,8 @@ constexpr struct { {"normal direction", MeshVisualizer3D::Flag::NormalDirection}, {"tbn direction", MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::BitangentDirection|MeshVisualizer3D::Flag::NormalDirection}, {"tbn direction with bitangent from tangent", MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::BitangentFromTangentDirection|MeshVisualizer3D::Flag::NormalDirection}, - {"wireframe + t/n direction", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::NormalDirection} + {"wireframe + t/n direction", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::NormalDirection}, + {"wireframe + object id + t/n direction", MeshVisualizer3D::Flag::Wireframe|MeshVisualizer3D::Flag::InstancedObjectId|MeshVisualizer3D::Flag::TangentDirection|MeshVisualizer3D::Flag::NormalDirection} }; #endif @@ -165,7 +200,17 @@ constexpr struct { } ConstructInvalidData2D[] { {"no feature enabled", MeshVisualizer2D::Flag::NoGeometryShader, /* not a feature flag */ - "at least Flag::Wireframe has to be enabled"} + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + "at least one visualization feature has to be enabled" + #else + "at least Flag::Wireframe has to be enabled" + #endif + }, + #ifndef MAGNUM_TARGET_GLES2 + {"both object and primitive id", + MeshVisualizer2D::Flag::InstancedObjectId|MeshVisualizer2D::Flag::PrimitiveIdFromVertexId, + "Flag::InstancedObjectId and Flag::PrimitiveId are mutually exclusive"} + #endif }; constexpr struct { @@ -180,8 +225,26 @@ constexpr struct { #else "at least Flag::Wireframe has to be enabled" #endif - } + }, + #ifndef MAGNUM_TARGET_GLES2 + {"both object and primitive id", + MeshVisualizer3D::Flag::InstancedObjectId|MeshVisualizer3D::Flag::PrimitiveIdFromVertexId, + "Flag::InstancedObjectId and Flag::PrimitiveId are mutually exclusive"} + #endif +}; + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + SamplerFilter filter; + SamplerWrapping wrapping; +} ObjectIdDefaultsData[] { + {"nearest, clamp", SamplerFilter::Nearest, SamplerWrapping::ClampToEdge}, + {"nearest, repeat", SamplerFilter::Nearest, SamplerWrapping::Repeat}, + {"linear, clamp", SamplerFilter::Linear, SamplerWrapping::ClampToEdge}, + {"linear, repeat", SamplerFilter::Linear, SamplerWrapping::Repeat} }; +#endif constexpr struct { const char* name; @@ -223,6 +286,41 @@ constexpr struct { 3.0f, 1.0f, "wireframe-wide3D.tga", "wireframe-nogeo3D.tga"} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizer2D::Flags flags2D; + MeshVisualizer3D::Flags flags3D; + const char* file2D; + const char* file3D; +} ObjectPrimitiveIdData[] { + {"object ID", + MeshVisualizer2D::Flag::InstancedObjectId, + MeshVisualizer3D::Flag::InstancedObjectId, + "objectid2D.tga", "objectid3D.tga"}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", + MeshVisualizer2D::Flag::PrimitiveId, + MeshVisualizer3D::Flag::PrimitiveId, + "primitiveid2D.tga", "primitiveid3D.tga"}, + #endif + {"primitive ID from vertex ID", + MeshVisualizer2D::Flag::PrimitiveIdFromVertexId, + MeshVisualizer3D::Flag::PrimitiveIdFromVertexId, + "primitiveid2D.tga", "primitiveid3D.tga"}, + {"wireframe + object ID", + MeshVisualizer2D::Flag::InstancedObjectId|MeshVisualizer2D::Flag::Wireframe, + MeshVisualizer3D::Flag::InstancedObjectId|MeshVisualizer3D::Flag::Wireframe, + "wireframe-objectid2D.tga", "wireframe-objectid3D.tga"}, + {"wireframe + object ID, no geometry shader", + MeshVisualizer2D::Flag::InstancedObjectId|MeshVisualizer2D::Flag::Wireframe| + MeshVisualizer2D::Flag::NoGeometryShader, + MeshVisualizer3D::Flag::InstancedObjectId|MeshVisualizer3D::Flag::Wireframe| + MeshVisualizer3D::Flag::NoGeometryShader, + "wireframe-nogeo-objectid2D.tga", "wireframe-nogeo-objectid3D.tga"} +}; +#endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) constexpr struct { const char* name; @@ -258,22 +356,29 @@ constexpr struct { {"only bitangent from tangent", MeshVisualizer3D::Flag::BitangentFromTangentDirection, {}, false, 2.0f, 1.0f, 0.6f, 1.0f, "bitangents-from-tangents.tga"}, - {"wireframe + tangents + normals, single pass", + {"wireframe + primitive ID + tangents + normals, single pass", MeshVisualizer3D::Flag::Wireframe| + MeshVisualizer3D::Flag::PrimitiveId| MeshVisualizer3D::Flag::TangentDirection| MeshVisualizer3D::Flag::NormalDirection, {}, - false, 2.0f, 1.0f, 0.6f, 1.0f, "wireframe-tn.tga"}, - {"wireframe, rendering all, but only tangents + normals present", + false, 2.0f, 1.0f, 0.6f, 1.0f, "wireframe-primitiveid-tn.tga"}, + {"wireframe + primitive ID, rendering all, but only tangents + normals present", MeshVisualizer3D::Flag::Wireframe| + MeshVisualizer3D::Flag::PrimitiveId| MeshVisualizer3D::Flag::TangentDirection| MeshVisualizer3D::Flag::BitangentDirection| MeshVisualizer3D::Flag::NormalDirection, {}, - true, 2.0f, 1.0f, 0.6f, 1.0f, "wireframe-tn.tga"}, + true, 2.0f, 1.0f, 0.6f, 1.0f, "wireframe-primitiveid-tn.tga"}, {"wireframe + tangents + normals, two passes", MeshVisualizer3D::Flag::TangentDirection| MeshVisualizer3D::Flag::NormalDirection, MeshVisualizer3D::Flag::Wireframe, false, 2.0f, 1.0f, 0.6f, 1.0f, "wireframe-tn-smooth.tga"}, + {"primitive ID + tangents + normals", + MeshVisualizer3D::Flag::PrimitiveId| + MeshVisualizer3D::Flag::TangentDirection| + MeshVisualizer3D::Flag::NormalDirection, {}, + false, 2.0f, 1.0f, 0.6f, 1.0f, "primitiveid-tn.tga"} }; #endif @@ -307,6 +412,10 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::setWireframeNotEnabled2D, &MeshVisualizerGLTest::setWireframeNotEnabled3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::setColorMapNotEnabled2D, + &MeshVisualizerGLTest::setColorMapNotEnabled3D, + #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) &MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D, #endif @@ -314,8 +423,27 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) addTests({&MeshVisualizerGLTest::renderDefaultsWireframe2D, - &MeshVisualizerGLTest::renderDefaultsWireframe3D, - &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal}, + &MeshVisualizerGLTest::renderDefaultsWireframe3D}, + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::renderDefaultsObjectId2D, + &MeshVisualizerGLTest::renderDefaultsObjectId3D}, + Containers::arraySize(ObjectIdDefaultsData), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + addTests({ + &MeshVisualizerGLTest::renderDefaultsPrimitiveId2D, + &MeshVisualizerGLTest::renderDefaultsPrimitiveId3D, + #ifndef MAGNUM_TARGET_WEBGL + &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal + #endif + }, &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif @@ -330,6 +458,14 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::renderObjectPrimitiveId2D, + &MeshVisualizerGLTest::renderObjectPrimitiveId3D}, + Containers::arraySize(ObjectPrimitiveIdData), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) addTests({&MeshVisualizerGLTest::renderWireframe3DPerspective}, &MeshVisualizerGLTest::renderSetup, @@ -363,12 +499,36 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { { _testDir = SHADERS_TEST_DIR; } + + /* Set up a color map texture for use by object / primitive ID tests */ + #ifndef MAGNUM_TARGET_GLES2 + { + const auto map = DebugTools::ColorMap::turbo(); + const Vector2i size{Int(map.size()), 1}; + _colorMapTexture + .setMinificationFilter(SamplerFilter::Linear) + .setMagnificationFilter(SamplerFilter::Linear) + .setWrapping(SamplerWrapping::Repeat) + .setStorage(1, GL::TextureFormat::RGB8, size) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGB8Srgb, size, map}); + } + #endif } void MeshVisualizerGLTest::construct2D() { auto&& data = ConstructData2D[testCaseInstanceId()]; setTestCaseDescription(data.name); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags & MeshVisualizer2D::Flag::PrimitiveId && !(data.flags >= MeshVisualizer2D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + #endif + MeshVisualizer2D shader{data.flags}; CORRADE_COMPARE(shader.flags(), data.flags); CORRADE_VERIFY(shader.id()); @@ -386,6 +546,16 @@ void MeshVisualizerGLTest::construct3D() { auto&& data = ConstructData3D[testCaseInstanceId()]; setTestCaseDescription(data.name); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags & MeshVisualizer3D::Flag::PrimitiveId && !(data.flags >= MeshVisualizer3D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + #endif + MeshVisualizer3D shader{data.flags}; CORRADE_COMPARE(shader.flags(), data.flags); CORRADE_VERIFY(shader.id()); @@ -554,13 +724,23 @@ void MeshVisualizerGLTest::setWireframeNotEnabled2D() { is just wireframe in case of 2D), so fake it with a NoCreate */ MeshVisualizer2D shader{NoCreate}; shader - .setColor({}) + .setColor({}); + + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/primitive ID enabled\n"); + #else + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe enabled\n"); + #endif + + out.str({}); + shader .setWireframeColor({}) .setWireframeWidth({}) .setSmoothness({}); CORRADE_COMPARE(out.str(), - "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe enabled\n" "Shaders::MeshVisualizer::setWireframeColor(): the shader was not created with wireframe enabled\n" "Shaders::MeshVisualizer::setWireframeWidth(): the shader was not created with wireframe enabled\n" "Shaders::MeshVisualizer2D::setSmoothness(): the shader was not created with wireframe enabled\n"); @@ -575,18 +755,58 @@ void MeshVisualizerGLTest::setWireframeNotEnabled3D() { with a NoCreate */ MeshVisualizer3D shader{NoCreate}; shader - .setColor({}) + .setColor({}); + + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe or object/primitive ID enabled\n"); + #else + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe enabled\n"); + #endif + + out.str({}); + shader .setWireframeColor({}) .setWireframeWidth({}) .setSmoothness({}); CORRADE_COMPARE(out.str(), - "Shaders::MeshVisualizer::setColor(): the shader was not created with wireframe enabled\n" "Shaders::MeshVisualizer::setWireframeColor(): the shader was not created with wireframe enabled\n" "Shaders::MeshVisualizer::setWireframeWidth(): the shader was not created with wireframe enabled\n" "Shaders::MeshVisualizer3D::setSmoothness(): the shader was not created with wireframe or TBN direction enabled\n"); } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::setColorMapNotEnabled2D() { + std::ostringstream out; + Error redirectError{&out}; + + GL::Texture2D texture; + MeshVisualizer2D shader{NoCreate}; + shader.setColorMapTransformation({}, {}) + .bindColorMapTexture(texture); + + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/primitive ID enabled\n" + "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/primitive ID enabled\n"); +} + +void MeshVisualizerGLTest::setColorMapNotEnabled3D() { + std::ostringstream out; + Error redirectError{&out}; + + GL::Texture2D texture; + MeshVisualizer3D shader{NoCreate}; + shader.setColorMapTransformation({}, {}) + .bindColorMapTexture(texture); + + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizer::setColorMapTransformation(): the shader was not created with object/primitive ID enabled\n" + "Shaders::MeshVisualizer::bindColorMapTexture(): the shader was not created with object/primitive ID enabled\n"); +} +#endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D() { #ifndef MAGNUM_TARGET_GLES @@ -746,7 +966,177 @@ void MeshVisualizerGLTest::renderDefaultsWireframe3D() { /* AMD has off-by-one errors on edges compared to Intel */ (DebugTools::CompareImageToFile{_manager, 1.0f, 0.06f})); } +#endif + +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::renderDefaultsObjectId2D() { + auto&& data = ObjectIdDefaultsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Configure a texture with preset filtering and wrapping. The goal here is + that the default config should be filtering/wrapping-independent for the + first 256 items */ + const auto map = DebugTools::ColorMap::turbo(); + const Vector2i size{Int(map.size()), 1}; + GL::Texture2D colorMapTexture; + colorMapTexture + .setMinificationFilter(data.filter) + .setMagnificationFilter(data.filter) + .setWrapping(data.wrapping) + .setStorage(1, GL::TextureFormat::RGB8, size) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGB8Srgb, size, map}); + + /* Generate per-face IDs going from 0 to 240 to cover the whole range */ + Containers::Array ids{16}; + for(std::size_t i = 0; i != ids.size(); ++i) ids[i] = i*16; + GL::Mesh circle = MeshTools::compile(MeshTools::combineFaceAttributes( + MeshTools::generateIndices(Primitives::circle2DSolid(16)), { + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, + Containers::arrayView(ids)} + })); + + MeshVisualizer2D{MeshVisualizer2D::Flag::InstancedObjectId} + .bindColorMapTexture(colorMapTexture) + .draw(circle); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImageImporter 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"), + /* SwiftShader has a few rounding errors on edges */ + (DebugTools::CompareImageToFile{_manager, 150.67f, 0.45f})); +} + +void MeshVisualizerGLTest::renderDefaultsObjectId3D() { + auto&& data = ObjectIdDefaultsData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Configure a texture with preset filtering and wrapping. The goal here is + that the default config should be filtering/wrapping-independent for the + first 256 items */ + const auto map = DebugTools::ColorMap::turbo(); + const Vector2i size{Int(map.size()), 1}; + GL::Texture2D colorMapTexture; + colorMapTexture + .setMinificationFilter(data.filter) + .setMagnificationFilter(data.filter) + .setWrapping(data.wrapping) + .setStorage(1, GL::TextureFormat::RGB8, size) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGB8Srgb, size, map}); + + /* Generate per-face IDs going from 0 to 228 to cover the whole range */ + Containers::Array ids{20}; + for(std::size_t i = 0; i != ids.size(); ++i) ids[i] = i*12; + GL::Mesh icosphere = MeshTools::compile(MeshTools::combineFaceAttributes( + Primitives::icosphereSolid(0), { + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, + Containers::arrayView(ids)} + })); + + MeshVisualizer3D{MeshVisualizer3D::Flag::InstancedObjectId} + .bindColorMapTexture(colorMapTexture) + .draw(icosphere); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImageImporter 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"), + /* SwiftShader has a few rounding errors on edges */ + (DebugTools::CompareImageToFile{_manager, 150.67f, 0.165f})); +} +void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); + + MeshVisualizer2D::Flags flags; + #ifdef MAGNUM_TARGET_WEBGL + flags = MeshVisualizer2D::Flag::PrimitiveIdFromVertexId; + #else + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isVersionSupported(GL::Version::GL320)) + #else + if(!GL::Context::current().isVersionSupported(GL::Version::GLES320)) + #endif + { + Debug{} << "Using primitive ID from vertex ID"; + flags = MeshVisualizer2D::Flag::PrimitiveIdFromVertexId; + } + else flags = MeshVisualizer2D::Flag::PrimitiveId; + #endif + + Trade::MeshData circleData = Primitives::circle2DSolid(16); + if(flags >= MeshVisualizer2D::Flag::PrimitiveIdFromVertexId) + circleData = MeshTools::duplicate(MeshTools::generateIndices(circleData)); + + MeshVisualizer2D{flags} + .bindColorMapTexture(_colorMapTexture) + .draw(MeshTools::compile(circleData)); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + 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-primitiveid2D.tga"), + /* SwiftShader has a few rounding errors on edges */ + (DebugTools::CompareImageToFile{_manager, 76.67f, 0.23f})); +} + +void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); + + MeshVisualizer2D::Flags flags; + #ifdef MAGNUM_TARGET_WEBGL + flags = MeshVisualizer2D::Flag::PrimitiveIdFromVertexId; + #else + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isVersionSupported(GL::Version::GL320)) + #else + if(!GL::Context::current().isVersionSupported(GL::Version::GLES320)) + #endif + { + Debug{} << "Using primitive ID from vertex ID"; + flags = MeshVisualizer2D::Flag::PrimitiveIdFromVertexId; + } + else flags = MeshVisualizer2D::Flag::PrimitiveId; + #endif + + Trade::MeshData icosphereData = Primitives::icosphereSolid(0); + if(flags >= MeshVisualizer2D::Flag::PrimitiveIdFromVertexId) + icosphereData = MeshTools::duplicate(icosphereData); + + MeshVisualizer2D{flags} + .bindColorMapTexture(_colorMapTexture) + .draw(MeshTools::compile(icosphereData)); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + 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-primitiveid3D.tga"), + /* SwiftShader has a few rounding errors on edges */ + (DebugTools::CompareImageToFile{_manager, 88.34f, 0.071f})); +} +#endif + +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) @@ -974,6 +1364,178 @@ void MeshVisualizerGLTest::renderWireframe3D() { } } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::renderObjectPrimitiveId2D() { + auto&& data = ObjectPrimitiveIdData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags2D & MeshVisualizer2D::Flag::PrimitiveId && !(data.flags2D >= MeshVisualizer2D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + + #ifndef MAGNUM_TARGET_GLES + if(!(data.flags2D & MeshVisualizer2D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() + std::string(" is not supported")); + #else + if(!(data.flags2D & MeshVisualizer2D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() + std::string(" is not supported")); + #endif + #endif + + Trade::MeshData circleData = Primitives::circle2DSolid(16); + + if(data.flags2D & MeshVisualizer2D::Flag::InstancedObjectId) { + 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; + circleData = MeshTools::combineFaceAttributes( + MeshTools::generateIndices(circleData), { + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, + Containers::arrayView(ids)} + }); + } + + /* Duplicate the data if using primitive ID from vertex ID or if geometry + shader is disabled */ + if(data.flags2D >= MeshVisualizer2D::Flag::PrimitiveIdFromVertexId) + circleData = MeshTools::generateIndices(circleData); + if(data.flags2D >= MeshVisualizer2D::Flag::PrimitiveIdFromVertexId || + data.flags2D & MeshVisualizer2D::Flag::NoGeometryShader) + circleData = MeshTools::duplicate(circleData); + + GL::Mesh circle = MeshTools::compile(circleData); + + MeshVisualizer2D shader{data.flags2D}; + shader + /* Remove blue so it's clear the (wireframe) background and mapped ID + colors got mixed */ + .setColor(0xffff00_rgbf) + /* Shouldn't assert (nor warn) when wireframe is not enabled */ + .setViewportSize({80, 80}) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + /* Should cover the first half of the colormap, in reverse order; for + primitive ID the whole colormap due to the repeat wrapping */ + .setColorMapTransformation(0.5f, -1.0f/16.0f) + .bindColorMapTexture(_colorMapTexture); + + /* OTOH the wireframe color should stay at full channels, not mixed */ + if(data.flags3D & MeshVisualizer3D::Flag::Wireframe) + shader.setWireframeColor(0xffffff_rgbf); + + shader.draw(circle); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImageImporter 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", data.file2D}), + /* AMD has slight off-by-one errors compared to Intel, SwiftShader a + bit more */ + (DebugTools::CompareImageToFile{_manager, 4.0f, 0.141f})); +} + +void MeshVisualizerGLTest::renderObjectPrimitiveId3D() { + auto&& data = ObjectPrimitiveIdData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags3D & MeshVisualizer3D::Flag::PrimitiveId && !(data.flags3D >= MeshVisualizer3D::Flag::PrimitiveIdFromVertexId) && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL320) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES320) + #endif + ) CORRADE_SKIP("gl_PrimitiveID not supported."); + + #ifndef MAGNUM_TARGET_GLES + if(!(data.flags3D & MeshVisualizer3D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() + std::string(" is not supported")); + #else + if(!(data.flags3D & MeshVisualizer3D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() + std::string(" is not supported")); + #endif + #endif + + Trade::MeshData icosphereData = Primitives::icosphereSolid(1); + + if(data.flags3D & MeshVisualizer3D::Flag::InstancedObjectId) { + 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; + icosphereData = MeshTools::combineFaceAttributes( + icosphereData, { + Trade::MeshAttributeData{Trade::MeshAttribute::ObjectId, + Containers::arrayView(ids)} + }); + } + + /* Duplicate the data if using primitive ID from vertex ID or if geometry + shader is disabled */ + if(data.flags3D >= MeshVisualizer3D::Flag::PrimitiveIdFromVertexId || + data.flags3D & MeshVisualizer3D::Flag::NoGeometryShader) + icosphereData = MeshTools::duplicate(icosphereData); + + GL::Mesh circle = MeshTools::compile(icosphereData); + + MeshVisualizer3D shader{data.flags3D}; + shader + /* Remove blue so it's clear the wireframe background and mapped ID + colors got mixed */ + .setColor(0xffff00_rgbf) + /* Shouldn't assert (nor warn) when wireframe is not enabled */ + .setViewportSize({80, 80}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + /* Should cover the first half of the colormap, in reverse order; for + primitive ID the whole colormap due to the repeat wrapping */ + .setColorMapTransformation(0.5f, -1.0f/40.0f) + .bindColorMapTexture(_colorMapTexture); + + /* OTOH the wireframe color should stay at full channels, not mixed */ + if(data.flags2D & MeshVisualizer2D::Flag::Wireframe) + shader.setWireframeColor(0xffffff_rgbf); + + shader.draw(circle); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); + + /* Release build has 1 pixel slightly off. Huh. AMD has additional + off-by-one errors compared to Intel. If + GL_NV_shader_noperspective_interpolation is not supported, the artifacts + are bigger when wireframe is enabled. */ + Float maxThreshold = 1.0f, meanThreshold = 0.026f; + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(data.flags3D & MeshVisualizer3D::Flag::Wireframe && !GL::Context::current().isExtensionSupported()) { + /* SwiftShader has a bit more rounding errors */ + maxThreshold = 238.0f; + meanThreshold = 1.957f; + } + #endif + 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.file3D}), + (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); +} +#endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void MeshVisualizerGLTest::renderWireframe3DPerspective() { #ifndef MAGNUM_TARGET_GLES @@ -1131,6 +1693,9 @@ void MeshVisualizerGLTest::renderTangentBitangentNormal() { if(data.flags & MeshVisualizer3D::Flag::Wireframe) shader .setColor(0xffff99_rgbf) .setWireframeColor(0x9999ff_rgbf); + if(data.flags & MeshVisualizer3D::Flag::PrimitiveId) shader + .bindColorMapTexture(_colorMapTexture) + .setColorMapTransformation(1.0f/512.0f, 0.5f); shader.draw(mesh); @@ -1141,7 +1706,7 @@ void MeshVisualizerGLTest::renderTangentBitangentNormal() { are bigger. */ Float maxThreshold = 1.334f, meanThreshold = 0.008f; #ifdef MAGNUM_TARGET_GLES - if(!(data.flags & MeshVisualizer3D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) { + if(!GL::Context::current().isExtensionSupported()) { maxThreshold = 39.0f; meanThreshold = 1.207f; } 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 000000000..e0569ef45 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-objectid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-objectid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-objectid3D.tga new file mode 100644 index 000000000..e31b47372 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-objectid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-primitiveid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-primitiveid2D.tga new file mode 100644 index 000000000..e6b8b91c2 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-primitiveid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-primitiveid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-primitiveid3D.tga new file mode 100644 index 000000000..013604f44 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/defaults-primitiveid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid2D.tga new file mode 100644 index 000000000..6e6a7b4af Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid3D.tga new file mode 100644 index 000000000..90e05699e Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/objectid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid-tn.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid-tn.tga new file mode 100644 index 000000000..fddb6b1b4 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid-tn.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid2D.tga new file mode 100644 index 000000000..045063650 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid3D.tga new file mode 100644 index 000000000..ef0ea9525 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/primitiveid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-nogeo-objectid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-nogeo-objectid2D.tga new file mode 100644 index 000000000..4d0b06899 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-nogeo-objectid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-nogeo-objectid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-nogeo-objectid3D.tga new file mode 100644 index 000000000..32fa6390f Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-nogeo-objectid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-objectid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-objectid2D.tga new file mode 100644 index 000000000..5ed8df360 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-objectid2D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-objectid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-objectid3D.tga new file mode 100644 index 000000000..44a7028c9 Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-objectid3D.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-primitiveid-tn.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-primitiveid-tn.tga new file mode 100644 index 000000000..b2ea974ad Binary files /dev/null and b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-primitiveid-tn.tga differ diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn.tga deleted file mode 100644 index 2aa934dd4..000000000 Binary files a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/wireframe-tn.tga and /dev/null differ