From ef9da0ec96274a43eea966e1232101f440b2ae62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 20 May 2021 16:47:30 +0200 Subject: [PATCH] Shaders: add UBO support to all shaders. --- doc/changelog.dox | 2 + package/archlinux/PKGBUILD | 1 + package/archlinux/PKGBUILD-coverage | 1 + package/archlinux/PKGBUILD-release | 1 + src/Magnum/Shaders/CMakeLists.txt | 17 +- src/Magnum/Shaders/DistanceFieldVector.frag | 56 + src/Magnum/Shaders/DistanceFieldVector.h | 223 +- src/Magnum/Shaders/DistanceFieldVectorGL.cpp | 223 +- src/Magnum/Shaders/DistanceFieldVectorGL.h | 244 +- src/Magnum/Shaders/Flat.frag | 47 + src/Magnum/Shaders/Flat.h | 132 +- src/Magnum/Shaders/Flat.vert | 65 + src/Magnum/Shaders/FlatGL.cpp | 197 +- src/Magnum/Shaders/FlatGL.h | 208 +- src/Magnum/Shaders/Generic.h | 430 +++- src/Magnum/Shaders/MeshVisualizer.frag | 79 + src/Magnum/Shaders/MeshVisualizer.geom | 107 +- src/Magnum/Shaders/MeshVisualizer.h | 416 +++- src/Magnum/Shaders/MeshVisualizer.vert | 108 + src/Magnum/Shaders/MeshVisualizerGL.cpp | 532 ++++- src/Magnum/Shaders/MeshVisualizerGL.h | 479 +++- src/Magnum/Shaders/Phong.frag | 137 +- src/Magnum/Shaders/Phong.h | 449 +++- src/Magnum/Shaders/Phong.vert | 119 +- src/Magnum/Shaders/PhongGL.cpp | 377 +++- src/Magnum/Shaders/PhongGL.h | 357 ++- src/Magnum/Shaders/Test/CMakeLists.txt | 45 +- .../Test/DistanceFieldVectorGLTest.cpp | 959 +++++++- .../Shaders/Test/DistanceFieldVectorTest.cpp | 195 ++ src/Magnum/Shaders/Test/FlatGLTest.cpp | 1675 ++++++++++++-- src/Magnum/Shaders/Test/FlatGL_Test.cpp | 4 +- src/Magnum/Shaders/Test/FlatTest.cpp | 124 ++ .../FlatTestFiles/multidraw-textured2D.tga | Bin 0 -> 2501 bytes .../FlatTestFiles/multidraw-textured3D.tga | Bin 0 -> 2477 bytes .../Test/FlatTestFiles/multidraw2D.tga | Bin 0 -> 1026 bytes .../Test/FlatTestFiles/multidraw3D.tga | Bin 0 -> 1010 bytes src/Magnum/Shaders/Test/GenericTest.cpp | 408 ++++ .../Shaders/Test/MeshVisualizerGLTest.cpp | 1959 +++++++++++++++-- .../Shaders/Test/MeshVisualizerTest.cpp | 302 +++ .../multidraw-vertexid2D.tga | Bin 0 -> 6639 bytes .../multidraw-vertexid3D.tga | Bin 0 -> 5818 bytes .../multidraw-wireframe-nogeo2D.tga | Bin 0 -> 4485 bytes .../multidraw-wireframe-nogeo3D.tga | Bin 0 -> 4183 bytes .../multidraw-wireframe-tbn3D.tga | Bin 0 -> 5096 bytes .../multidraw-wireframe2D.tga | Bin 0 -> 4494 bytes .../multidraw-wireframe3D.tga | Bin 0 -> 4221 bytes src/Magnum/Shaders/Test/PhongGLTest.cpp | 1807 +++++++++++++-- src/Magnum/Shaders/Test/PhongTest.cpp | 322 +++ .../PhongTestFiles/multidraw-textured.tga | Bin 0 -> 5524 bytes .../Shaders/Test/PhongTestFiles/multidraw.tga | Bin 0 -> 5274 bytes src/Magnum/Shaders/Test/VectorGLTest.cpp | 863 +++++++- src/Magnum/Shaders/Test/VectorTest.cpp | 118 + .../multidraw2D-distancefield.tga | Bin 0 -> 3404 bytes .../Test/VectorTestFiles/multidraw2D.tga | Bin 0 -> 3239 bytes .../multidraw3D-distancefield.tga | Bin 0 -> 3030 bytes .../Test/VectorTestFiles/multidraw3D.tga | Bin 0 -> 2597 bytes src/Magnum/Shaders/Test/VertexColorGLTest.cpp | 650 +++++- .../Shaders/Test/VertexColorGL_Test.cpp | 34 +- .../Test/VertexColorTestFiles/multidraw2D.tga | Bin 0 -> 6852 bytes .../Test/VertexColorTestFiles/multidraw3D.tga | Bin 0 -> 4828 bytes src/Magnum/Shaders/Vector.frag | 37 + src/Magnum/Shaders/Vector.h | 100 +- src/Magnum/Shaders/Vector.vert | 65 + src/Magnum/Shaders/VectorGL.cpp | 186 +- src/Magnum/Shaders/VectorGL.h | 194 +- src/Magnum/Shaders/VertexColor.vert | 46 + src/Magnum/Shaders/VertexColorGL.cpp | 140 +- src/Magnum/Shaders/VertexColorGL.h | 184 +- 68 files changed, 14367 insertions(+), 1057 deletions(-) create mode 100644 src/Magnum/Shaders/Test/DistanceFieldVectorTest.cpp create mode 100644 src/Magnum/Shaders/Test/FlatTest.cpp create mode 100644 src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured2D.tga create mode 100644 src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured3D.tga create mode 100644 src/Magnum/Shaders/Test/FlatTestFiles/multidraw2D.tga create mode 100644 src/Magnum/Shaders/Test/FlatTestFiles/multidraw3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTest.cpp create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe2D.tga create mode 100644 src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe3D.tga create mode 100644 src/Magnum/Shaders/Test/PhongTest.cpp create mode 100644 src/Magnum/Shaders/Test/PhongTestFiles/multidraw-textured.tga create mode 100644 src/Magnum/Shaders/Test/PhongTestFiles/multidraw.tga create mode 100644 src/Magnum/Shaders/Test/VectorTest.cpp create mode 100644 src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D-distancefield.tga create mode 100644 src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D.tga create mode 100644 src/Magnum/Shaders/Test/VectorTestFiles/multidraw3D-distancefield.tga create mode 100644 src/Magnum/Shaders/Test/VectorTestFiles/multidraw3D.tga create mode 100644 src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw2D.tga create mode 100644 src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw3D.tga diff --git a/doc/changelog.dox b/doc/changelog.dox index 11b5cf82a..549229cff 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -147,6 +147,8 @@ See also: @subsubsection changelog-latest-new-shaders Shaders library +- All builtin shaders now have opt-in support for uniform buffers on desktop, + OpenGL ES 3.0+ and WebGL 2.0 - Added @ref Shaders::PhongGL::setNormalTextureScale(), consuming the recently added @ref Trade::MaterialAttribute::NormalTextureScale material attribute diff --git a/package/archlinux/PKGBUILD b/package/archlinux/PKGBUILD index 7dba2f6c7..a277dd9dd 100644 --- a/package/archlinux/PKGBUILD +++ b/package/archlinux/PKGBUILD @@ -72,6 +72,7 @@ check() { MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access" ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_uniform_buffer_object" ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" ctest --output-on-failure -j5 -R GLTest # Run all Vulkan tests with SwiftShader as well diff --git a/package/archlinux/PKGBUILD-coverage b/package/archlinux/PKGBUILD-coverage index 89d33001c..1dafdf4d6 100644 --- a/package/archlinux/PKGBUILD-coverage +++ b/package/archlinux/PKGBUILD-coverage @@ -73,6 +73,7 @@ check() { MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access GL_ARB_robustness GL_ARB_multi_bind" ctest --output-on-failure -j5 -R GLTest || true MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" ctest --output-on-failure -j5 -R GLTest || true MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" ctest --output-on-failure -j5 -R GLTest || true + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_uniform_buffer_object" ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" ctest --output-on-failure -j5 -R GLTest || true # Run all Vulkan tests with SwiftShader as well diff --git a/package/archlinux/PKGBUILD-release b/package/archlinux/PKGBUILD-release index 7c6364886..bda38eda3 100644 --- a/package/archlinux/PKGBUILD-release +++ b/package/archlinux/PKGBUILD-release @@ -110,6 +110,7 @@ check() { MAGNUM_DISABLE_EXTENSIONS="GL_ARB_direct_state_access" ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_ARB_get_texture_sub_image" ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_ARB_vertex_array_object" ctest --output-on-failure -j5 -R GLTest + MAGNUM_DISABLE_EXTENSIONS="GL_ARB_uniform_buffer_object" ctest --output-on-failure -j5 -R GLTest MAGNUM_DISABLE_EXTENSIONS="GL_KHR_debug" ctest --output-on-failure -j5 -R GLTest # Run all Vulkan tests with SwiftShader as well diff --git a/src/Magnum/Shaders/CMakeLists.txt b/src/Magnum/Shaders/CMakeLists.txt index e48a91475..08551065a 100644 --- a/src/Magnum/Shaders/CMakeLists.txt +++ b/src/Magnum/Shaders/CMakeLists.txt @@ -31,8 +31,6 @@ corrade_add_resource(MagnumShaders_RESOURCES_GL resources-gl.conf) set_target_properties(MagnumShaders_RESOURCES_GL-dependencies PROPERTIES FOLDER "Magnum/Shaders") set(MagnumShaders_SRCS - VertexColorGL.cpp - ${MagnumShaders_RESOURCES_GL}) set(MagnumShaders_GracefulAssert_SRCS @@ -40,15 +38,22 @@ set(MagnumShaders_GracefulAssert_SRCS FlatGL.cpp MeshVisualizerGL.cpp PhongGL.cpp - VectorGL.cpp) + VectorGL.cpp + VertexColorGL.cpp) set(MagnumShaders_HEADERS + DistanceFieldVector.h DistanceFieldVectorGL.h + Flat.h FlatGL.h + Generic.h GenericGL.h + MeshVisualizer.h MeshVisualizerGL.h + Phong.h PhongGL.h Shaders.h + Vector.h VectorGL.h VertexColorGL.h @@ -56,12 +61,6 @@ set(MagnumShaders_HEADERS if(MAGNUM_BUILD_DEPRECATED) list(APPEND MagnumShaders_HEADERS - DistanceFieldVector.h - Flat.h - Generic.h - MeshVisualizer.h - Phong.h - Vector.h VertexColor.h) endif() diff --git a/src/Magnum/Shaders/DistanceFieldVector.frag b/src/Magnum/Shaders/DistanceFieldVector.frag index 2f45fb27e..4f292e38f 100644 --- a/src/Magnum/Shaders/DistanceFieldVector.frag +++ b/src/Magnum/Shaders/DistanceFieldVector.frag @@ -29,8 +29,13 @@ #define texture texture2D #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 2) #endif @@ -63,6 +68,49 @@ uniform lowp float smoothness #endif ; +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +struct DrawUniform { + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +struct MaterialUniform { + lowp vec4 color; + lowp vec4 reserved; + lowp vec4 outlineColor; + lowp vec4 outlineRangeSmoothnessReserved; + #define material_outlineRange outlineRangeSmoothnessReserved.xy + #define material_smoothness outlineRangeSmoothnessReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Textures */ #ifdef EXPLICIT_BINDING @@ -84,6 +132,14 @@ out lowp vec4 fragmentColor; #endif void main() { + #ifdef UNIFORM_BUFFERS + mediump const uint materialId = draws[drawOffset].draw_materialIdReserved & 0xffffu; + lowp const float smoothness = materials[materialId].material_smoothness; + lowp const vec4 color = materials[materialId].color; + lowp const vec4 outlineColor = materials[materialId].outlineColor; + lowp const vec2 outlineRange = materials[materialId].material_outlineRange; + #endif + lowp float intensity = texture(vectorTexture, interpolatedTextureCoordinates).r; /* Fill color */ diff --git a/src/Magnum/Shaders/DistanceFieldVector.h b/src/Magnum/Shaders/DistanceFieldVector.h index c3b15183c..dd5d665a8 100644 --- a/src/Magnum/Shaders/DistanceFieldVector.h +++ b/src/Magnum/Shaders/DistanceFieldVector.h @@ -25,26 +25,227 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::DistanceFieldVector, alias @ref Magnum::Shaders::DistanceFieldVector2D, @ref Magnum::Shaders::DistanceFieldVector3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/DistanceFieldVectorGL.h, - * the @ref Magnum::Shaders::DistanceFieldVectorGL "DistanceFieldVectorGL" - * class and related typedefs instead. + * @brief Struct @ref Magnum::Shaders::DistanceFieldVectorDrawUniform, @ref Magnum::Shaders::DistanceFieldVectorMaterialUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/DistanceFieldVectorGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/DistanceFieldVectorGL.h, the DistanceFieldVectorGL class and related typedefs instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for distance field vector shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D / +@ref TransformationProjectionUniform3D contains parameters that are specific to +each draw call. Texture transformation, if needed, is supplied separately in a +@ref TextureTransformationUniform; material-related properties are expected to +be shared among multiple draw calls and thus are provided in a separate +@ref DistanceFieldVectorMaterialUniform structure, referenced by +@ref materialId. +@see @ref DistanceFieldVectorGL::bindDrawBuffer() +*/ +struct DistanceFieldVectorDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit DistanceFieldVectorDrawUniform(DefaultInitT = DefaultInit) noexcept: materialId{0} {} + + /** @brief Construct without initializing the contents */ + explicit DistanceFieldVectorDrawUniform(NoInitT) noexcept {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorDrawUniform& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @} + */ + + /** @var materialId + * @brief Material ID + * + * References a particular material from a + * @ref DistanceFieldVectorMaterialUniform array. Useful when an UBO with + * more than one material is supplied or in a multi-draw scenario. Should + * be less than the material count passed to the + * @ref DistanceFieldVectorGL::DistanceFieldVectorGL(Flags, UnsignedInt, UnsignedInt) + * constructor. Default value is @cpp 0 @ce, meaning the first material + * gets used. + */ + + /* 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 + 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 + UnsignedShort:16; /* reserved for skinOffset */ + #endif + #else + UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; /* reserved for objectId */ + Int:32; + Int:32; + #endif +}; + +/** +@brief Material uniform for distance field vector shaders +@m_since_latest + +Describes material properties referenced from +@ref DistanceFieldVectorDrawUniform::materialId. +@see @ref DistanceFieldVectorGL::bindMaterialBuffer() +*/ +struct DistanceFieldVectorMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit DistanceFieldVectorMaterialUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, outlineColor{0.0f, 0.0f, 0.0f, 0.0f}, outlineStart{0.5f}, outlineEnd{1.0f}, smoothness{0.04f} {} + + /** @brief Construct without initializing the contents */ + explicit DistanceFieldVectorMaterialUniform(NoInitT) noexcept: color{NoInit}, outlineColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorMaterialUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref outlineColor field + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorMaterialUniform& setOutlineColor(const Color4& color) { + outlineColor = color; + return *this; + } + + /** + * @brief Set the @ref outlineStart and @ref outlineEnd fields + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorMaterialUniform& setOutlineRange(Float start, Float end) { + outlineStart = start; + outlineEnd = end; + return *this; + } + + /** + * @brief Set the @ref smoothness field + * @return Reference to self (for method chaining) + */ + DistanceFieldVectorMaterialUniform& setSmoothness(Float smoothness) { + this->smoothness = smoothness; + return *this; + } + + /** + * @} + */ + + /** + * @brief Fill color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * @see @ref DistanceFieldVectorGL::setColor() + */ + Color4 color; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; /* reserved for backgroundColor */ + Int:32; + Int:32; + Int:32; + #endif + + /** + * @brief Outline color + * + * Default value is @cpp 0x00000000_rgbaf @ce and the outline is not drawn + * --- see @ref outlineStart and @ref outlineEnd for more information. + * @see @ref DistanceFieldVectorGL::setColor() + */ + Color4 outlineColor; + + /** + * @brief Outline start + * + * Describe where fill ends and possible outline starts. Default value is + * @cpp 0.5f @ce, larger values will make the vector art look thinner, + * smaller will make it look thicker. + * @see @ref DistanceFieldVectorGL::setOutlineRange() + */ + Float outlineStart; + + /** + * @brief Outline end + * + * Describe where outline ends. If set to a value larger than + * @ref outlineStart, the outline is not drawn. Initial value is + * @cpp 1.0f @ce. + * @see @ref DistanceFieldVectorGL::setOutlineRange() + */ + Float outlineEnd; + + /** + * @brief Smoothness radius + * + * Larger values will make edges look less aliased (but blurry), smaller + * values will make them look more crisp (but possibly aliased). Initial + * value is @cpp 0.04f @ce. + * @see @ref DistanceFieldVectorGL::setSmoothness() + */ + Float smoothness; + + /* warning: Member __pad4__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief DistanceFieldVectorGL * @m_deprecated_since_latest Use @ref DistanceFieldVectorGL instead. */ @@ -61,10 +262,8 @@ typedef CORRADE_DEPRECATED("use DistanceFieldVectorGL2D instead") DistanceFieldV * @m_deprecated_since_latest Use @ref DistanceFieldVectorGL3D instead. */ typedef CORRADE_DEPRECATED("use DistanceFieldVectorGL3D instead") DistanceFieldVectorGL3D DistanceFieldVector3D; +#endif }} -#else -#error use Magnum/Shaders/DistanceFieldVectorGL.h, the DistanceFieldVectorGL class and related typedefs instead -#endif #endif diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp index 022195230..4ded3988b 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.cpp +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.cpp @@ -37,15 +37,55 @@ #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { namespace { enum: Int { TextureUnit = 6 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from other shaders which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1, + DrawBufferBinding = 2, + TextureTransformationBufferBinding = 3, + MaterialBufferBinding = 4 + }; + #endif } -template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags): _flags{flags} { +template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _materialCount{materialCount}, + _drawCount{drawCount} + #endif +{ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::DistanceFieldVectorGL: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::DistanceFieldVectorGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -65,9 +105,27 @@ template DistanceFieldVectorGL::DistanceFiel GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); vert.addSource(flags & Flag::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") - .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n") - .addSource(rs.get("generic.glsl")) + .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + } + #endif + vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("Vector.vert")); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define MATERIAL_COUNT {}\n" + "#define DRAW_COUNT {}\n", + materialCount, + drawCount)); + } + #endif frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("DistanceFieldVector.frag")); @@ -92,13 +150,20 @@ template DistanceFieldVectorGL::DistanceFiel if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) - _textureMatrixUniform = uniformLocation("textureMatrix"); - _colorUniform = uniformLocation("color"); - _outlineColorUniform = uniformLocation("outlineColor"); - _outlineRangeUniform = uniformLocation("outlineRange"); - _smoothnessUniform = uniformLocation("smoothness"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + if(flags & Flag::TextureTransformation) + _textureMatrixUniform = uniformLocation("textureMatrix"); + _colorUniform = uniformLocation("color"); + _outlineColorUniform = uniformLocation("outlineColor"); + _outlineRangeUniform = uniformLocation("outlineRange"); + _smoothnessUniform = uniformLocation("smoothness"); + } } #ifndef MAGNUM_TARGET_GLES @@ -106,25 +171,54 @@ template DistanceFieldVectorGL::DistanceFiel #endif { setUniform(uniformLocation("vectorTexture"), TextureUnit); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + if(flags & Flag::TextureTransformation) + setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) - setTextureMatrix(Matrix3{Math::IdentityInit}); - setColor(Color4{1.0f}); /* Outline color is zero by default */ - setOutlineRange(0.5f, 1.0f); - setSmoothness(0.04f); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + if(flags & Flag::TextureTransformation) + setTextureMatrix(Matrix3{Math::IdentityInit}); + setColor(Color4{1.0f}); + /* Outline color is zero by default */ + setOutlineRange(0.5f, 1.0f); + setSmoothness(0.04f); + } #endif } +#ifndef MAGNUM_TARGET_GLES2 +template DistanceFieldVectorGL::DistanceFieldVectorGL(const Flags flags): DistanceFieldVectorGL{flags, 1, 1} {} +#endif + template DistanceFieldVectorGL& DistanceFieldVectorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setTextureMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::TextureTransformation, "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was not created with texture transformation enabled", *this); setUniform(_textureMatrixUniform, matrix); @@ -132,25 +226,112 @@ template DistanceFieldVectorGL& DistanceFiel } template DistanceFieldVectorGL& DistanceFieldVectorGL::setColor(const Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setColor(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_colorUniform, color); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setOutlineColor(const Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setOutlineColor(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_outlineColorUniform, color); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setOutlineRange(Float start, Float end) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setOutlineRange(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_outlineRangeUniform, Vector2(start, end)); return *this; } template DistanceFieldVectorGL& DistanceFieldVectorGL::setSmoothness(Float value) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::DistanceFieldVectorGL::setSmoothness(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_smoothnessUniform, value); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template DistanceFieldVectorGL& DistanceFieldVectorGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::DistanceFieldVectorGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + setUniform(_drawOffsetUniform, offset); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindTextureTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindTextureTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +template DistanceFieldVectorGL& DistanceFieldVectorGL::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} +#endif + template DistanceFieldVectorGL& DistanceFieldVectorGL::bindVectorTexture(GL::Texture2D& texture) { texture.bind(TextureUnit); return *this; @@ -168,6 +349,9 @@ Debug& operator<<(Debug& debug, const DistanceFieldVectorGLFlag value) { /* LCOV_EXCL_START */ #define _c(v) case DistanceFieldVectorGLFlag::v: return debug << "::" #v; _c(TextureTransformation) + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -177,8 +361,11 @@ Debug& operator<<(Debug& debug, const DistanceFieldVectorGLFlag value) { Debug& operator<<(Debug& debug, const DistanceFieldVectorGLFlags value) { return Containers::enumSetDebugOutput(debug, value, "Shaders::DistanceFieldVectorGL::Flags{}", { - DistanceFieldVectorGLFlag::TextureTransformation - }); + DistanceFieldVectorGLFlag::TextureTransformation, + #ifndef MAGNUM_TARGET_GLES2 + DistanceFieldVectorGLFlag::UniformBuffers + #endif + }); } } diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index 538cb8961..604c6106b 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -39,7 +39,10 @@ namespace Magnum { namespace Shaders { namespace Implementation { enum class DistanceFieldVectorGLFlag: UnsignedByte { - TextureTransformation = 1 << 0 + TextureTransformation = 1 << 0, + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 1 + #endif }; typedef Containers::EnumSet DistanceFieldVectorGLFlags; } @@ -122,7 +125,23 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @see @ref setTextureMatrix() * @m_since{2020,06} */ - TextureTransformation = 1 << 0 + TextureTransformation = 1 << 0, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer(), @ref bindTextureTransformationBuffer(), + * and @ref bindMaterialBuffer() instead of direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 1 + #endif }; /** @@ -142,9 +161,50 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector /** * @brief Constructor * @param flags Flags + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref DistanceFieldVectorGL(Flags, UnsignedInt, UnsignedInt) with + * @p materialCount and @p drawCount set to @cpp 1 @ce. */ explicit DistanceFieldVectorGL(Flags flags = {}); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param materialCount Size of a @ref DistanceFieldVectorMaterialUniform + * buffer bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref TransformationProjectionUniform3D / + * @ref DistanceFieldVectorDrawUniform / + * @ref TextureTransformationUniform buffer bound with + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() + * and @ref bindTextureTransformationBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials are specified via + * @ref DistanceFieldVectorDrawUniform::materialId. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref DistanceFieldVectorGL(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit DistanceFieldVectorGL(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -177,8 +237,40 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector */ Flags flags() const { return _flags; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the + * @ref DistanceFieldVectorMaterialUniform uniform buffer. Has use only + * if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, + * @ref DistanceFieldVectorDrawUniform and + * @ref TextureTransformationUniform uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -186,6 +278,11 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @return Reference to self (for method chaining) * * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ DistanceFieldVectorGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); @@ -197,6 +294,11 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * Expects that the shader was created with * @ref Flag::TextureTransformation enabled. Initial value is an * identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset and call + * @ref bindTextureTransformationBuffer() instead. */ DistanceFieldVectorGL& setTextureMatrix(const Matrix3& matrix); @@ -205,6 +307,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @return Reference to self (for method chaining) * * Initial value is @cpp 0xffffffff_rgbaf @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref DistanceFieldVectorMaterialUniform::color and call + * @ref bindMaterialBuffer() instead. * @see @ref setOutlineColor() */ DistanceFieldVectorGL& setColor(const Color4& color); @@ -215,6 +321,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * * Initial value is @cpp 0x00000000_rgbaf @ce and the outline is not * drawn --- see @ref setOutlineRange() for more information. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref DistanceFieldVectorMaterialUniform::outlineColor and call + * @ref bindMaterialBuffer() instead. * @see @ref setOutlineRange(), @ref setColor() */ DistanceFieldVectorGL& setOutlineColor(const Color4& color); @@ -231,6 +341,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * larger than @p start, the outline is not drawn. Initial value is * @cpp 1.0f @ce. * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref DistanceFieldVectorMaterialUniform::outlineStart and + * @ref DistanceFieldVectorMaterialUniform::outlineEnd and call + * @ref bindMaterialBuffer() instead. * @see @ref setOutlineColor() */ DistanceFieldVectorGL& setOutlineRange(Float start, Float end); @@ -242,6 +356,10 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * Larger values will make edges look less aliased (but blurry), * smaller values will make them look more crisp (but possibly * aliased). Initial value is @cpp 0.04f @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref DistanceFieldVectorMaterialUniform::smoothness and call + * @ref bindMaterialBuffer() instead. */ DistanceFieldVectorGL& setSmoothness(Float value); @@ -249,6 +367,120 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, + * @ref DistanceFieldVectorDrawUniform and + * @ref TextureTransformationUniform buffers bound with + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() and + * @ref bindTextureTransformationBuffer() should be used for current + * draw. Expects that @ref Flag::UniformBuffers is set and @p offset is + * less than @ref drawCount(). Initial value is @cpp 0 @ce. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D. At the very least you need + * to call also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + DistanceFieldVectorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref DistanceFieldVectorDrawUniform. At the very least you need to + * call also @ref bindTransformationProjectionBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + DistanceFieldVectorGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a texture transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that both @ref Flag::UniformBuffers and + * @ref Flag::TextureTransformation is set. The buffer is expected to + * contain @ref drawCount() instances of + * @ref TextureTransformationUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& bindTextureTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + DistanceFieldVectorGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref DistanceFieldVectorMaterialUniform. At the very least you need + * to call also @ref bindTransformationProjectionBuffer() and + * @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + DistanceFieldVectorGL& bindMaterialBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + DistanceFieldVectorGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ @@ -278,12 +510,20 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector #endif Flags _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _materialCount{}, _drawCount{}; + #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, _colorUniform{2}, _outlineColorUniform{3}, _outlineRangeUniform{4}, _smoothnessUniform{5}; + #ifndef MAGNUM_TARGET_GLES2 + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; + #endif }; /** diff --git a/src/Magnum/Shaders/Flat.frag b/src/Magnum/Shaders/Flat.frag index 995b2bacb..613f84b05 100644 --- a/src/Magnum/Shaders/Flat.frag +++ b/src/Magnum/Shaders/Flat.frag @@ -27,14 +27,23 @@ #extension GL_EXT_gpu_shader4: require #endif +#if defined(UNIFORM_BUFFERS) && defined(ALPHA_MASK) && !defined(GL_ES) +#extension GL_ARB_shader_bit_encoding: require +#endif + #ifndef NEW_GLSL #define fragmentColor gl_FragColor #define texture texture2D #define in varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 2) #endif @@ -63,6 +72,34 @@ layout(location = 4) uniform highp uint objectId; /* defaults to zero */ #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +struct DrawUniform { + lowp vec4 color; + highp uvec4 objectIdReservedAlphaMaskReserved; + #define draw_objectId objectIdReservedAlphaMaskReserved.x + #define draw_alphaMask objectIdReservedAlphaMaskReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; +#endif + /* Textures */ #ifdef TEXTURED @@ -103,6 +140,16 @@ out highp uint fragmentObjectId; #endif void main() { + #ifdef UNIFORM_BUFFERS + lowp const vec4 color = draws[drawOffset].color; + #ifdef OBJECT_ID + highp const uint objectId = draws[drawOffset].draw_objectId; + #endif + #ifdef ALPHA_MASK + lowp const float alphaMask = uintBitsToFloat(draws[drawOffset].draw_alphaMask); + #endif + #endif + fragmentColor = #ifdef TEXTURED texture(textureData, interpolatedTextureCoordinates)* diff --git a/src/Magnum/Shaders/Flat.h b/src/Magnum/Shaders/Flat.h index 70311e465..f11ea91e1 100644 --- a/src/Magnum/Shaders/Flat.h +++ b/src/Magnum/Shaders/Flat.h @@ -25,26 +25,136 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::Flat, alias @ref Magnum::Shaders::Flat2D, @ref Magnum::Shaders::Flat3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/FlatGL.h, the - * @ref Magnum::Shaders::FlatGL "FlatGL" class and - * related typedefs instead. + * @brief Struct @ref Magnum::Shaders::FlatDrawUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/FlatGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/FlatGL.h, the FlatGL class and related typedefs instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for flat shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D / +@ref TransformationProjectionUniform3D contains parameters that are specific to +each draw call. Texture transformation, if needed, is supplied separately in a +@ref TextureTransformationUniform. +@see @ref FlatGL::bindDrawBuffer() +*/ +struct FlatDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit FlatDrawUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, objectId{0}, alphaMask{0.5f} {} + + /** @brief Construct without initializing the contents */ + explicit FlatDrawUniform(NoInitT) noexcept: color{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref objectId field + * @return Reference to self (for method chaining) + */ + FlatDrawUniform& setObjectId(UnsignedInt id) { + objectId = id; + return *this; + } + + /** + * @brief Set the @ref alphaMask field + * @return Reference to self (for method chaining) + */ + FlatDrawUniform& setAlphaMask(Float alphaMask) { + this->alphaMask = alphaMask; + return *this; + } + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + FlatDrawUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @} + */ + + /** + * @brief Color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * + * If @ref FlatGL::Flag::VertexColor is enabled, the color is multiplied + * with a color coming from the @ref FlatGL::Color3 / @ref FlatGL::Color4 + * attribute. + * @see @ref FlatGL::setColor() + */ + Color4 color; + + /** + * @brief Object ID + * + * Used only for the object ID framebuffer output, not to access any other + * uniform data. Default value is @cpp 0 @ce. + * + * Used only if @ref FlatGL::Flag::ObjectId is enabled, ignored otherwise. + * If @ref FlatGL::Flag::InstancedObjectId is enabled as well, this value + * is added to the ID coming from the @ref FlatGL::ObjectId attribute. + * @see @ref FlatGL::setObjectId() + */ + UnsignedInt objectId; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + /* This field is an UnsignedInt in the shader and skinOffset is extracted + as (value >> 16), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort:16; + UnsignedShort:16; /* reserved for skinOffset */ + #else + UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; + #endif + #endif + + /** + * @brief Alpha mask value + * + * Fragments with alpha values smaller than the mask value will be + * discarded. Default value is @cpp 0.5f @ce. + * + * Used only if @ref FlatGL::Flag::AlphaMask is enabled, ignored otherwise. + * @see @ref FlatGL::setAlphaMask() + */ + Float alphaMask; + + /* warning: Member __pad2__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief FlatGL * @m_deprecated_since_latest Use @ref FlatGL instead. */ @@ -61,10 +171,8 @@ typedef CORRADE_DEPRECATED("use FlatGL2D instead") FlatGL2D Flat2D; * @m_deprecated_since_latest Use @ref FlatGL3D instead. */ typedef CORRADE_DEPRECATED("use FlatGL3D instead") FlatGL3D Flat3D; +#endif }} -#else -#error use Magnum/Shaders/FlatGL.h, the FlatGL class and related typedefs instead -#endif #endif diff --git a/src/Magnum/Shaders/Flat.vert b/src/Magnum/Shaders/Flat.vert index 1c4305fc6..93fa99e1b 100644 --- a/src/Magnum/Shaders/Flat.vert +++ b/src/Magnum/Shaders/Flat.vert @@ -32,8 +32,13 @@ #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif @@ -64,6 +69,51 @@ uniform mediump mat3 textureMatrix ; #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + highp + #ifdef TWO_DIMENSIONS + mat3 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrices[DRAW_COUNT]; +}; + +#ifdef TEXTURE_TRANSFORMATION +struct TextureTransformationUniform { + highp vec4 rotationScaling; + highp vec4 offsetReservedReserved; + #define textureTransformation_offset offsetReservedReserved.xy +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 3 + #endif +) uniform TextureTransformation { + TextureTransformationUniform textureTransformations[DRAW_COUNT]; +}; +#endif +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -133,6 +183,21 @@ flat out highp uint interpolatedInstanceObjectId; #endif void main() { + #ifdef UNIFORM_BUFFERS + highp const + #ifdef TWO_DIMENSIONS + mat3 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrix = transformationProjectionMatrices[drawOffset]; + #ifdef TEXTURE_TRANSFORMATION + mediump const mat3 textureMatrix = mat3(textureTransformations[drawOffset].rotationScaling.xy, 0.0, textureTransformations[drawOffset].rotationScaling.zw, 0.0, textureTransformations[drawOffset].textureTransformation_offset, 1.0); + #endif + #endif + #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix* #ifdef INSTANCED_TRANSFORMATION diff --git a/src/Magnum/Shaders/FlatGL.cpp b/src/Magnum/Shaders/FlatGL.cpp index cb054eaf9..5887f781c 100644 --- a/src/Magnum/Shaders/FlatGL.cpp +++ b/src/Magnum/Shaders/FlatGL.cpp @@ -39,16 +39,52 @@ #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Buffer.h" +#endif + namespace Magnum { namespace Shaders { namespace { enum: Int { TextureUnit = 0 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from other shaders which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1, + DrawBufferBinding = 2, + TextureTransformationBufferBinding = 3 + }; + #endif } -template FlatGL::FlatGL(const Flags flags): _flags(flags) { +template FlatGL::FlatGL(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _drawCount{drawCount} + #endif +{ CORRADE_ASSERT(!(flags & Flag::TextureTransformation) || (flags & Flag::Textured), "Shaders::FlatGL: texture transformation enabled but the shader is not textured", ); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::FlatGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -75,8 +111,16 @@ template FlatGL::FlatGL(const Flags flags): .addSource(flags >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif .addSource(flags & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") - .addSource(flags >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : "") - .addSource(rs.get("generic.glsl")) + .addSource(flags >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + } + #endif + vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("Flat.vert")); frag.addSource(flags & Flag::Textured ? "#define TEXTURED\n" : "") .addSource(flags & Flag::AlphaMask ? "#define ALPHA_MASK\n" : "") @@ -85,7 +129,16 @@ template FlatGL::FlatGL(const Flags flags): .addSource(flags & Flag::ObjectId ? "#define OBJECT_ID\n" : "") .addSource(flags >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif - .addSource(rs.get("generic.glsl")) + ; + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + } + #endif + frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("Flat.frag")); CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); @@ -125,14 +178,21 @@ template FlatGL::FlatGL(const Flags flags): if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) - _textureMatrixUniform = uniformLocation("textureMatrix"); - _colorUniform = uniformLocation("color"); - if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + if(flags >= Flag::UniformBuffers) { + _drawOffsetUniform = uniformLocation("drawOffset"); + } else #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + if(flags & Flag::TextureTransformation) + _textureMatrixUniform = uniformLocation("textureMatrix"); + _colorUniform = uniformLocation("color"); + if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + #endif + } } #ifndef MAGNUM_TARGET_GLES @@ -140,25 +200,52 @@ template FlatGL::FlatGL(const Flags flags): #endif { if(flags & Flag::Textured) setUniform(uniformLocation("textureData"), TextureUnit); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + if(flags & Flag::TextureTransformation) + setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) - setTextureMatrix(Matrix3{Math::IdentityInit}); - setColor(Magnum::Color4{1.0f}); - if(flags & Flag::AlphaMask) setAlphaMask(0.5f); - /* Object ID is zero by default */ + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + if(flags & Flag::TextureTransformation) + setTextureMatrix(Matrix3{Math::IdentityInit}); + setColor(Magnum::Color4{1.0f}); + if(flags & Flag::AlphaMask) setAlphaMask(0.5f); + /* Object ID is zero by default */ + } #endif } +#ifndef MAGNUM_TARGET_GLES2 +template FlatGL::FlatGL(const Flags flags): FlatGL{flags, 1} {} +#endif + template FlatGL& FlatGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } template FlatGL& FlatGL::setTextureMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setTextureMatrix(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::TextureTransformation, "Shaders::FlatGL::setTextureMatrix(): the shader was not created with texture transformation enabled", *this); setUniform(_textureMatrixUniform, matrix); @@ -166,11 +253,19 @@ template FlatGL& FlatGL::setText } template FlatGL& FlatGL::setColor(const Magnum::Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setColor(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_colorUniform, color); return *this; } template FlatGL& FlatGL::setAlphaMask(Float mask) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setAlphaMask(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::AlphaMask, "Shaders::FlatGL::setAlphaMask(): the shader was not created with alpha mask enabled", *this); setUniform(_alphaMaskUniform, mask); @@ -179,6 +274,8 @@ template FlatGL& FlatGL::setAlph #ifndef MAGNUM_TARGET_GLES2 template FlatGL& FlatGL::setObjectId(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::FlatGL::setObjectId(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(_flags & Flag::ObjectId, "Shaders::FlatGL::setObjectId(): the shader was not created with object ID enabled", *this); setUniform(_objectIdUniform, id); @@ -186,6 +283,63 @@ template FlatGL& FlatGL::setObje } #endif +#ifndef MAGNUM_TARGET_GLES2 +template FlatGL& FlatGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::FlatGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + setUniform(_drawOffsetUniform, offset); + return *this; +} + +template FlatGL& FlatGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +template FlatGL& FlatGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +template FlatGL& FlatGL::bindTextureTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding); + return *this; +} + +template FlatGL& FlatGL::bindTextureTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); + return *this; +} +#endif + template FlatGL& FlatGL::bindTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & Flag::Textured, "Shaders::FlatGL::bindTexture(): the shader was not created with texturing enabled", *this); @@ -214,11 +368,14 @@ Debug& operator<<(Debug& debug, const FlatGLFlag value) { #endif _c(InstancedTransformation) _c(InstancedTextureOffset) + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + #endif #undef _c /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; } Debug& operator<<(Debug& debug, const FlatGLFlags value) { @@ -232,7 +389,11 @@ Debug& operator<<(Debug& debug, const FlatGLFlags value) { FlatGLFlag::InstancedObjectId, /* Superset of ObjectId */ FlatGLFlag::ObjectId, #endif - FlatGLFlag::InstancedTransformation}); + FlatGLFlag::InstancedTransformation, + #ifndef MAGNUM_TARGET_GLES2 + FlatGLFlag::UniformBuffers + #endif + }); } } diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index 24db89982..d052ca5e7 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -38,7 +38,7 @@ namespace Magnum { namespace Shaders { namespace Implementation { - enum class FlatGLFlag: UnsignedByte { + enum class FlatGLFlag: UnsignedShort { Textured = 1 << 0, AlphaMask = 1 << 1, VertexColor = 1 << 2, @@ -48,7 +48,10 @@ namespace Implementation { InstancedObjectId = (1 << 5)|ObjectId, #endif InstancedTransformation = 1 << 6, - InstancedTextureOffset = (1 << 7)|TextureTransformation + InstancedTextureOffset = (1 << 7)|TextureTransformation, + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 8 + #endif }; typedef Containers::EnumSet FlatGLFlags; } @@ -269,7 +272,7 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * * @see @ref Flags, @ref flags() */ - enum class Flag: UnsignedByte { + enum class Flag: UnsignedShort { /** * Multiply color with a texture. * @see @ref setColor(), @ref bindTexture() @@ -319,9 +322,10 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** * Instanced object ID. Retrieves a per-instance / per-vertex * object ID from the @ref ObjectId attribute, outputting a sum of - * the per-vertex ID and ID coming from @ref setObjectId(). - * Implicitly enables @ref Flag::ObjectId. See - * @ref Shaders-FlatGL-object-id for more information. + * the per-vertex ID and ID coming from @ref setObjectId() or + * @ref FlatDrawUniform::objectId. Implicitly enables + * @ref Flag::ObjectId. See @ref Shaders-FlatGL-object-id for more + * information. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL @@ -335,8 +339,10 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * Instanced transformation. Retrieves a per-instance * transformation matrix from the @ref TransformationMatrix * attribute and uses it together with the matrix coming from - * @ref setTransformationProjectionMatrix() (first the - * per-instance, then the uniform matrix). See + * @ref setTransformationProjectionMatrix() or + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix + * / @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * (first the per-instance, then the uniform matrix). See * @ref Shaders-FlatGL-instancing for more information. * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} * @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, @@ -351,7 +357,9 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** * Instanced texture offset. Retrieves a per-instance offset vector * from the @ref TextureOffset attribute and uses it together with - * the matrix coming from @ref setTextureMatrix() (first the + * the matrix coming from @ref setTextureMatrix() or + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset (first the * per-instance vector, then the uniform matrix). Instanced texture * scaling and rotation is not supported at the moment, you can * specify that only via the uniform @ref setTextureMatrix(). @@ -365,7 +373,23 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * in WebGL 1.0. * @m_since{2020,06} */ - InstancedTextureOffset = (1 << 7)|TextureTransformation + InstancedTextureOffset = (1 << 7)|TextureTransformation, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * instead of direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 8 + #endif }; /** @@ -384,9 +408,37 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** * @brief Constructor * @param flags Flags + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref FlatGL(Flags, UnsignedInt) with @p drawCount set to @cpp 1 @ce. */ explicit FlatGL(Flags flags = {}); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param drawCount Size of a @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D / @ref FlatDrawUniform / + * @ref TextureTransformationUniform buffer bound with + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() + * and @ref bindTextureTransformationBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p drawCount + * describes the uniform buffer sizes as these are required to have a + * statically defined size. The draw offset is then set via + * @ref setDrawOffset(). + * + * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is + * ignored and the constructor behaves the same as @ref FlatGL(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + explicit FlatGL(Flags flags, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -416,8 +468,26 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: /** @brief Flags */ Flags flags() const { return _flags; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, @ref FlatDrawUniform and + * @ref TextureTransformationUniform uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -428,6 +498,11 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * @ref Flag::InstancedTransformation is set, the per-instance * transformation matrix coming from the @ref TransformationMatrix * attribute is applied first, before this one. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ FlatGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); @@ -441,6 +516,11 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * identity matrix. If @ref Flag::InstancedTextureOffset is set, the * per-instance offset coming from the @ref TextureOffset attribute is * applied first, before this matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset and call + * @ref bindTextureTransformationBuffer() instead. */ FlatGL& setTextureMatrix(const Matrix3& matrix); @@ -452,6 +532,9 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * is set, the color is multiplied with the texture. If * @ref Flag::VertexColor is set, the color is multiplied with a color * coming from the @ref Color3 / @ref Color4 attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref FlatDrawUniform::color and call @ref bindDrawBuffer() instead. * @see @ref bindTexture() */ FlatGL& setColor(const Magnum::Color4& color); @@ -467,6 +550,10 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * * This corresponds to @m_class{m-doc-external} [glAlphaFunc()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glAlphaFunc.xml) * in classic OpenGL. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref FlatDrawUniform::alphaMask and call @ref bindDrawBuffer() + * instead. * @m_keywords{glAlphaFunc()} */ FlatGL& setAlphaMask(Float mask); @@ -481,6 +568,10 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * @ref Shaders-FlatGL-object-id for more information. 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 FlatDrawUniform::objectId and call @ref bindDrawBuffer() + * instead. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL 1.0. @@ -492,6 +583,97 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, @ref FlatDrawUniform and + * @ref TextureTransformationUniform buffers bound with + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() and + * @ref bindTextureTransformationBuffer() should be used for current + * draw. Expects that @ref Flag::UniformBuffers is set and @p offset is + * less than @ref drawCount(). Initial value is @cpp 0 @ce. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D. At the very least you need + * to call also @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref FlatDrawUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a texture transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that both @ref Flag::UniformBuffers and + * @ref Flag::TextureTransformation is set. The buffer is expected to + * contain @ref drawCount() instances of + * @ref TextureTransformationUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + FlatGL& bindTextureTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + FlatGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ @@ -521,12 +703,18 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: #endif Flags _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _drawCount{}; + #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, _colorUniform{2}, _alphaMaskUniform{3}; #ifndef MAGNUM_TARGET_GLES2 Int _objectIdUniform{4}; + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; #endif }; diff --git a/src/Magnum/Shaders/Generic.h b/src/Magnum/Shaders/Generic.h index e588298ec..c7f73a340 100644 --- a/src/Magnum/Shaders/Generic.h +++ b/src/Magnum/Shaders/Generic.h @@ -25,32 +25,442 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::Generic, alias @ref Magnum::Shaders::Generic2D, @ref Magnum::Shaders::Generic3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/GenericGL.h, the - * @ref Magnum::Shaders::GenericGL "GenericGL" class and - * related typedefs instead. + * @brief Struct @ref Magnum::Shaders::ProjectionUniform2D, @ref Magnum::Shaders::ProjectionUniform3D, @ref Magnum::Shaders::TransformationUniform2D, @ref Magnum::Shaders::TransformationUniform3D, @ref Magnum::Shaders::TextureTransformationUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/GenericGL.h" +#endif + +namespace Magnum { namespace Shaders { + +/** +@brief 2D projection uniform common for all shaders +@m_since_latest + +Contains the per-view projection matrix. +*/ +struct ProjectionUniform2D { + /** @brief Construct with default parameters */ + constexpr explicit ProjectionUniform2D(DefaultInitT = DefaultInit) noexcept: projectionMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit ProjectionUniform2D(NoInitT) noexcept: projectionMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref projectionMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + ProjectionUniform2D& setProjectionMatrix(const Matrix3& matrix) { + projectionMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @} + */ + + /** + * @brief Projection matrix + * + * Default value is an identity matrix (i.e., an orthographic projection of + * the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ cube). The + * bottom row is unused and acts only as a padding to match uniform buffer + * packing rules. + */ + Matrix3x4 projectionMatrix; +}; + +/** +@brief 3D projection uniform common for all shaders +@m_since_latest + +Contains the per-view projection matrix used by the @ref MeshVisualizerGL3D +and @ref PhongGL shaders that need a separate projection and transformation +matrix. +@see @ref MeshVisualizerGL3D::bindProjectionBuffer(), + @ref PhongGL::bindProjectionBuffer() +*/ +struct ProjectionUniform3D { + /** @brief Construct with default parameters */ + constexpr explicit ProjectionUniform3D(DefaultInitT = DefaultInit) noexcept: projectionMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit ProjectionUniform3D(NoInitT) noexcept: projectionMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref projectionMatrix field + * @return Reference to self (for method chaining) + */ + ProjectionUniform3D& setProjectionMatrix(const Matrix4& matrix) { + projectionMatrix = matrix; + return *this; + } + + /** + * @} + */ + + /** + * @brief Projection matrix + * + * Default value is an identity matrix (i.e., an orthographic projection of + * the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ cube). + * @see @ref PhongGL::setProjectionMatrix() + */ + Matrix4 projectionMatrix; +}; + +/** +@brief 2D transformation uniform common for all shaders +@m_since_latest + +Contains the per-draw transformation matrix. +*/ +struct TransformationUniform2D { + /** @brief Construct with default parameters */ + constexpr explicit TransformationUniform2D(DefaultInitT = DefaultInit) noexcept: transformationMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TransformationUniform2D(NoInitT) noexcept: transformationMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref transformationMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + TransformationUniform2D& setTransformationMatrix(const Matrix3& matrix) { + transformationMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @} + */ + + /** + * @brief Transformation matrix + * + * Default value is an identity matrix. The bottom row is unused and acts + * only as a padding to match uniform buffer packing rules. + */ + Matrix3x4 transformationMatrix; +}; + +/** +@brief 3D transformation uniform common for all shaders +@m_since_latest + +Contains the per-draw transformation matrix used by the @ref MeshVisualizerGL3D +and @ref PhongGL shaders that need a separate projection and transformation +matrix. +@see @ref MeshVisualizerGL3D::bindTransformationBuffer(), + @ref PhongGL::bindTransformationBuffer() +*/ +struct TransformationUniform3D { + /** @brief Construct with default parameters */ + constexpr explicit TransformationUniform3D(DefaultInitT = DefaultInit) noexcept: transformationMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TransformationUniform3D(NoInitT) noexcept: transformationMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/GenericGL.h, the GenericGL class and related typedefs instead") + /** + * @brief Set the @ref transformationMatrix field + * @return Reference to self (for method chaining) + */ + TransformationUniform3D& setTransformationMatrix(const Matrix4& matrix) { + transformationMatrix = matrix; + return *this; + } + /** + * @} + */ + + /** + * @brief Transformation matrix + * + * Default value is an identity matrix. + * + * If @ref PhongGL::Flag::InstancedTransformation is enabled, the + * per-instance transformation coming from the + * @ref PhongGL::TransformationMatrix attribute is applied first, before + * this one. + * @see @ref PhongGL::setTransformationMatrix(), + * @ref MeshVisualizerGL3D::setTransformationMatrix() + */ + Matrix4 transformationMatrix; +}; + +/** +@brief Combined 2D projection and transformation uniform common for all shaders +@m_since_latest + +Used by @ref FlatGL, @ref MeshVisualizerGL2D, @ref DistanceFieldVectorGL, +@ref VectorGL and @ref VertexColorGL that don't need to have a separate +projection matrix supplied. +@see @ref DistanceFieldVectorGL2D::bindTransformationProjectionBuffer(), + @ref FlatGL2D::bindTransformationProjectionBuffer(), + @ref MeshVisualizerGL2D::bindTransformationProjectionBuffer(), + @ref VectorGL2D::bindTransformationProjectionBuffer(), + @ref VertexColorGL2D::bindTransformationProjectionBuffer() +*/ +struct TransformationProjectionUniform2D { + /** @brief Construct with default parameters */ + constexpr explicit TransformationProjectionUniform2D(DefaultInitT = DefaultInit) noexcept: transformationProjectionMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TransformationProjectionUniform2D(NoInitT) noexcept: transformationProjectionMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref transformationProjectionMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + TransformationProjectionUniform2D& setTransformationProjectionMatrix(const Matrix3& matrix) { + transformationProjectionMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @} + */ + + /** + * @brief Transformation and projection matrix + * + * Default value is an identity matrix. The bottom row is unused and acts + * only as a padding to match uniform buffer packing rules. + * + * If @ref FlatGL::Flag::InstancedTransformation is enabled, the + * per-instance transformation coming from the + * @ref FlatGL::TransformationMatrix attribute is applied first, before + * this one. + * @see @ref DistanceFieldVectorGL2D::setTransformationProjectionMatrix(), + * @ref FlatGL2D::setTransformationProjectionMatrix(), + * @ref MeshVisualizerGL2D::setTransformationProjectionMatrix(), + * @ref VectorGL2D::setTransformationProjectionMatrix(), + * @ref VertexColorGL2D::setTransformationProjectionMatrix() + */ + Matrix3x4 transformationProjectionMatrix; +}; + +/** +@brief Combined 3D projection and transformation uniform common for all shaders +@m_since_latest + +Used by @ref FlatGL, @ref DistanceFieldVectorGL, @ref VectorGL and +@ref VertexColorGL that don't need to have a separate projection matrix +supplied. +@see @ref DistanceFieldVectorGL3D::bindTransformationProjectionBuffer(), + @ref FlatGL3D::bindTransformationProjectionBuffer(), + @ref VectorGL3D::bindTransformationProjectionBuffer(), + @ref VertexColorGL3D::bindTransformationProjectionBuffer() +*/ +struct TransformationProjectionUniform3D { + /** @brief Construct with default parameters */ + constexpr explicit TransformationProjectionUniform3D(DefaultInitT = DefaultInit) noexcept: transformationProjectionMatrix{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TransformationProjectionUniform3D(NoInitT) noexcept: transformationProjectionMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref transformationProjectionMatrix field + * @return Reference to self (for method chaining) + */ + TransformationProjectionUniform3D& setTransformationProjectionMatrix(const Matrix4& matrix) { + transformationProjectionMatrix = matrix; + return *this; + } + + /** + * @} + */ + + /** + * @brief Transformation and projection matrix + * + * Default value is an identity matrix. + * + * If @ref FlatGL::Flag::InstancedTransformation is enabled, the + * per-instance transformation coming from the + * @ref FlatGL::TransformationMatrix attribute is applied first, before + * this one. + * @see @ref DistanceFieldVectorGL3D::setTransformationProjectionMatrix(), + * @ref FlatGL3D::setTransformationProjectionMatrix(), + * @ref VectorGL3D::setTransformationProjectionMatrix(), + * @ref VertexColorGL3D::setTransformationProjectionMatrix() + */ + Matrix4 transformationProjectionMatrix; +}; + +/** +@brief Texture transformation uniform common for all shaders +@m_since_latest + +Expands upon @ref TransformationUniform2D / @ref TransformationUniform3D with +texture-related parameters. + +Used only if @ref DistanceFieldVectorGL::Flag::TextureTransformation, +@ref FlatGL::Flag::TextureTransformation, +@ref PhongGL::Flag::TextureTransformation or +@ref VectorGL::Flag::TextureTransformation is enabled. +@see @ref DistanceFieldVectorGL::bindTextureTransformationBuffer(), + @ref FlatGL::bindTextureTransformationBuffer(), + @ref PhongGL::bindTextureTransformationBuffer(), + @ref VectorGL::bindTextureTransformationBuffer() +*/ +struct TextureTransformationUniform { + /** @brief Construct with default parameters */ + constexpr explicit TextureTransformationUniform(DefaultInitT = DefaultInit) noexcept: rotationScaling{Math::IdentityInit} {} + /** @brief Construct without initializing the contents */ + explicit TextureTransformationUniform(NoInitT) noexcept: rotationScaling{NoInit}, offset{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref rotationScaling and @ref offset fields + * @return Reference to self (for method chaining) + * + * The @ref rotationScaling field is set to the upper left 2x2 corner of + * @p transformation, @ref offset to the two upper elements of the + * rightmost column of @p transformation. Bottom row is ignored, as it's + * expected to be always @cpp {0.0f, 0.0f, 1.0f} @ce. + */ + TextureTransformationUniform& setTextureMatrix(const Matrix3& transformation) { + rotationScaling = transformation.rotationScaling(); + offset = transformation.translation(); + return *this; + } + + /** + * @} + */ + + /** + * @brief Texture rotation and scaling + * + * The top left part of a 3x3 texture transformation matrix. The + * transformation is split between @ref rotationScaling and @ref offset to + * make it occupy just two @cb{.glsl} vec4 @ce slots in the uniform buffer + * instead of three. Default value is an identity matrix. + * + * If @ref FlatGL::Flag::InstancedTextureOffset / + * @ref PhongGL::Flag::InstancedTextureOffset is enabled, the + * per-instance offset coming from the @ref FlatGL::TextureOffset / + * @ref PhongGL::TextureOffset attribute is applied first, before + * this transformation. + * @see @ref DistanceFieldVectorGL::setTextureMatrix(), + * @ref FlatGL::setTextureMatrix(), @ref PhongGL::setTextureMatrix(), + * @ref VectorGL::setTextureMatrix() + */ + Matrix2x2 rotationScaling; + + /** + * @brief Texture offset + * + * Top two elements of the rightmost column of a 3x3 texture transformation + * matrix. The transformation is split between @ref rotationScaling and + * @ref offset to make it occupy just two @cb{.glsl} vec4 @ce slots in the + * uniform buffer instead of three. Default value is a zero vector. + * + * If @ref FlatGL::Flag::InstancedTextureOffset / + * @ref PhongGL::Flag::InstancedTextureOffset is enabled, the + * per-instance offset coming from the @ref FlatGL::TextureOffset / + * @ref PhongGL::TextureOffset attribute is applied first, before + * this transformation. + * @see @ref DistanceFieldVectorGL::setTextureMatrix(), + * @ref FlatGL::setTextureMatrix(), @ref PhongGL::setTextureMatrix(), + * @ref VectorGL::setTextureMatrix() + */ + Vector2 offset; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; /* reserved for layer */ + Int:32; /* reserved for coordinateSet */ + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /* Deprecated aliases not present here but in GenericGL.h instead, as a lot of existing code relies on these being transitively included from Phong.h etc., and there are no forward declarations in Shaders.h as the type is never used like that. While we *could* include Generic.h from Phong.h, we'd have to also temporarily disable the CORRADE_DEPRECATED_FILE() macro there and it's more pain than it's worth. */ -#else -#error use Magnum/Shaders/GenericGL.h, the GenericGL class and related typedefs instead #endif +}} + #endif diff --git a/src/Magnum/Shaders/MeshVisualizer.frag b/src/Magnum/Shaders/MeshVisualizer.frag index 7b7fb5e0b..e75b64eea 100644 --- a/src/Magnum/Shaders/MeshVisualizer.frag +++ b/src/Magnum/Shaders/MeshVisualizer.frag @@ -46,6 +46,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) #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 1) @@ -110,6 +111,64 @@ uniform lowp vec2 colorMapOffsetScale #define colorMapScale colorMapOffsetScale.y #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +/* Keep in sync with MeshVisualizer.vert and MeshVisualizer.geom. Can't + "outsource" to a common file because the extension directives need to be + always before any code. */ +struct DrawUniform { + #ifdef THREE_DIMENSIONS + highp mat3 normalMatrix; /* actually mat3x4 */ + #elif !defined(TWO_DIMENSIONS) + #error + #endif + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +/* Keep in sync with MeshVisualizer.vert and MeshVisualizer.geom. Can't + "outsource" to a common file because the extension directives need to be + always before any code. */ +struct MaterialUniform { + lowp vec4 color; + lowp vec4 wireframeColor; + lowp vec4 wireframeWidthColorMapOffsetColorMapScaleLineWidth; + #define material_wireframeWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.x + #define material_colorMapOffset wireframeWidthColorMapOffsetColorMapScaleLineWidth.y + #define material_colorMapScale wireframeWidthColorMapOffsetColorMapScaleLineWidth.z + #define material_lineWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.w + lowp vec4 lineLengthSmoothnessReservedReserved; + #define material_lineLength lineLengthSmoothnessReservedReserved.x + #define material_smoothness lineLengthSmoothnessReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Textures */ #if defined(INSTANCED_OBJECT_ID) || defined(VERTEX_ID) || defined(PRIMITIVE_ID) || defined(PRIMITIVE_ID_FROM_VERTEX_ID) @@ -157,6 +216,26 @@ out lowp vec4 fragmentColor; #endif void main() { + #ifdef UNIFORM_BUFFERS + mediump const uint materialId = draws[drawOffset].draw_materialIdReserved & 0xffffu; + #if (defined(WIREFRAME_RENDERING) || defined(INSTANCED_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 + #ifdef WIREFRAME_RENDERING + lowp const float wireframeWidth = materials[materialId].material_wireframeWidth; + #elif defined(TBN_DIRECTION) + lowp const float lineWidth = materials[materialId].material_lineWidth; + #endif + #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) + lowp const float colorMapOffset = materials[materialId].material_colorMapOffset; + lowp const float colorMapScale = materials[materialId].material_colorMapScale; + #endif + #endif + /* 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 */ diff --git a/src/Magnum/Shaders/MeshVisualizer.geom b/src/Magnum/Shaders/MeshVisualizer.geom index e609692d2..928e1f2b2 100644 --- a/src/Magnum/Shaders/MeshVisualizer.geom +++ b/src/Magnum/Shaders/MeshVisualizer.geom @@ -36,12 +36,14 @@ /* Uniforms */ +/* This one is for both classic and UBOs, as it's usually set globally instead + of changing per-draw */ #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 5) #endif uniform lowp vec2 viewportSize; /* defaults to zero */ - +#ifndef UNIFORM_BUFFERS #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) @@ -82,6 +84,64 @@ uniform lowp float smoothness ; #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +/* Keep in sync with MeshVisualizer.vert and MeshVisualizer.frag. Can't + "outsource" to a common file because the #extension directives need to be + always before any code. */ +struct DrawUniform { + #ifdef THREE_DIMENSIONS + highp mat3 normalMatrix; /* actually mat3x4 */ + #elif !defined(TWO_DIMENSIONS) + #error + #endif + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +/* Keep in sync with MeshVisualizer.vert and MeshVisualizer.frag. Can't + "outsource" to a common file because the #extension directives need to be + always before any code. */ +struct MaterialUniform { + lowp vec4 color; + lowp vec4 wireframeColor; + lowp vec4 wireframeWidthColorMapOffsetColorMapScaleLineWidth; + #define material_wireframeWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.x + #define material_colorMapOffset wireframeWidthColorMapOffsetColorMapScaleLineWidth.y + #define material_colorMapScale wireframeWidthColorMapOffsetColorMapScaleLineWidth.z + #define material_lineWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.w + lowp vec4 lineLengthSmoothnessReservedReserved; + #define material_lineLength lineLengthSmoothnessReservedReserved.x + #define material_smoothness lineLengthSmoothnessReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Inputs */ layout(triangles) in; @@ -130,7 +190,17 @@ flat out highp uint interpolatedPrimitiveId; out lowp vec4 backgroundColor; out lowp vec4 lineColor; -void emitQuad(vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointScreen) { +void emitQuad( + #ifdef UNIFORM_BUFFERS + uint materialId, + #endif + vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointScreen +) { + #if defined(UNIFORM_BUFFERS) && (defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION)) + lowp const float lineWidth = materials[materialId].material_lineWidth; + lowp const float smoothness = materials[materialId].material_smoothness; + #endif + /* Calculate screen-space locations for the bar vertices and form two triangles out of them. In case TBN is rendered alone, half bar width is lineWidth + smoothness to allow for antialiasing, in case it's rendered @@ -184,6 +254,18 @@ void emitQuad(vec4 position, vec2 positionScreen, vec4 endpoint, vec2 endpointSc #endif void main() { + #ifdef UNIFORM_BUFFERS + mediump const uint materialId = draws[drawOffset].draw_materialIdReserved & 0xffffu; + #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)) + lowp const vec4 color = materials[materialId].color; + lowp const vec4 wireframeColor = materials[materialId].wireframeColor; + #endif + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(NORMAL_DIRECTION) + lowp const float lineWidth = materials[materialId].material_lineWidth; + lowp const float smoothness = materials[materialId].material_smoothness; + #endif + #endif + /* Passthrough for unchanged variables */ #ifdef INSTANCED_OBJECT_ID interpolatedInstanceObjectId = interpolatedVsInstanceObjectId[0]; @@ -262,17 +344,32 @@ void main() { for(int i = 0; i != 3; ++i) { #ifdef TANGENT_DIRECTION lineColor = vec4(1.0, 0.0, 0.0, 1.0); - emitQuad(gl_in[i].gl_Position, p[i], tangentEndpoint[i], t[i]); + emitQuad( + #ifdef UNIFORM_BUFFERS + materialId, + #endif + gl_in[i].gl_Position, p[i], tangentEndpoint[i], t[i] + ); #endif #ifdef BITANGENT_DIRECTION lineColor = vec4(0.0, 1.0, 0.0, 1.0); - emitQuad(gl_in[i].gl_Position, p[i], bitangentEndpoint[i], b[i]); + emitQuad( + #ifdef UNIFORM_BUFFERS + materialId, + #endif + gl_in[i].gl_Position, p[i], bitangentEndpoint[i], b[i] + ); #endif #ifdef NORMAL_DIRECTION lineColor = vec4(0.0, 0.0, 1.0, 1.0); - emitQuad(gl_in[i].gl_Position, p[i], normalEndpoint[i], n[i]); + emitQuad( + #ifdef UNIFORM_BUFFERS + materialId, + #endif + gl_in[i].gl_Position, p[i], normalEndpoint[i], n[i] + ); #endif } #endif diff --git a/src/Magnum/Shaders/MeshVisualizer.h b/src/Magnum/Shaders/MeshVisualizer.h index 3a725bdef..85463592c 100644 --- a/src/Magnum/Shaders/MeshVisualizer.h +++ b/src/Magnum/Shaders/MeshVisualizer.h @@ -25,27 +25,419 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::MeshVisualizer2D, @ref Magnum::Shaders::MeshVisualizer3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/MeshVisualizerGL.h and - * the @ref Magnum::Shaders::MeshVisualizerGL2D "MeshVisualizerGL2D" / - * @ref Magnum::Shaders::MeshVisualizerGL3D "MeshVisualizerGL3D" class - * instead + * @brief Struct @ref Magnum::Shaders::MeshVisualizerDrawUniform2D, @ref Magnum::Shaders::MeshVisualizerDrawUniform3D, @ref Magnum::Shaders::MeshVisualizerMaterialUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/MeshVisualizerGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/MeshVisualizerGL.h and the MeshVisualizerGL2D / MeshVisualizer3D class instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for 2D mesh visualizer shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D contains +parameters that are specific to each draw call. Material-related properties are +expected to be shared among multiple draw calls and thus are provided in a +separate @ref MeshVisualizerMaterialUniform structure, referenced by +@ref materialId. +@see @ref MeshVisualizerGL2D::bindDrawBuffer() +*/ +struct MeshVisualizerDrawUniform2D { + /** @brief Construct with default parameters */ + constexpr explicit MeshVisualizerDrawUniform2D(DefaultInitT = DefaultInit) noexcept: materialId{0} {} + + /** @brief Construct without initializing the contents */ + explicit MeshVisualizerDrawUniform2D(NoInitT) noexcept {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform2D& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @} + */ + + /** @var materialId + * @brief Material ID + * + * References a particular material from a + * @ref MeshVisualizerMaterialUniform array. Useful when an UBO with + * more than one material is supplied or in a multi-draw scenario. Should + * be less than the material count passed to the @ref MeshVisualizerGL2D::MeshVisualizerGL2D(Flags, UnsignedInt, UnsignedInt) + * / @ref MeshVisualizerGL3D::MeshVisualizerGL3D(Flags, UnsignedInt, UnsignedInt) + * constructor. Default value is @cpp 0 @ce, meaning the first material + * gets used. + */ + + /* 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 + 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 + UnsignedShort:16; /* reserved for skinOffset */ + #endif + #else + UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; + Int:32; + Int:32; + #endif +}; + +/** +@brief Per-draw uniform for 3D mesh visualizer shaders +@m_since_latest + +Together with the generic @ref TransformationUniform3D contains parameters that +are specific to each draw call. Material-related properties are expected to be +shared among multiple draw calls and thus are provided in a separate +@ref MeshVisualizerMaterialUniform structure, referenced by @ref materialId. +@see @ref MeshVisualizerGL3D::bindDrawBuffer() +*/ +struct MeshVisualizerDrawUniform3D { + /** @brief Construct with default parameters */ + constexpr explicit MeshVisualizerDrawUniform3D(DefaultInitT = DefaultInit) noexcept: normalMatrix{Math::IdentityInit}, materialId{0} {} + + /** @brief Construct without initializing the contents */ + explicit MeshVisualizerDrawUniform3D(NoInitT) noexcept: normalMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref normalMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + MeshVisualizerDrawUniform3D& setNormalMatrix(const Matrix3x3& matrix) { + normalMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + MeshVisualizerDrawUniform3D& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @} + */ + + /** + * @brief Normal matrix + * + * Default value is an identity matrix. The bottom row is unused and acts + * only as a padding to match uniform buffer packing rules. + * @see @ref MeshVisualizerGL3D::setNormalMatrix() + */ + Matrix3x4 normalMatrix; + + /** @var materialId + * @brief Material ID + * + * References a particular material from a + * @ref MeshVisualizerMaterialUniform array. Useful when an UBO with + * more than one material is supplied or in a multi-draw scenario. Should + * be less than the material count passed to @ref MeshVisualizerGL2D / + * @ref MeshVisualizerGL3D constructor. Default value is @cpp 0 @ce, + * meaning the first material gets used. + */ + + /* 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 + 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 + UnsignedShort:16; /* reserved for skinOffset */ + #endif + #else + UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; + Int:32; + Int:32; + #endif +}; + +/** +@brief Material uniform for mesh visualizer shaders +@m_since_latest + +Describes material properties referenced from +@ref MeshVisualizerDrawUniform2D::materialId and +@ref MeshVisualizerDrawUniform3D::materialId. +*/ +struct MeshVisualizerMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit MeshVisualizerMaterialUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, wireframeColor{0.0f, 0.0f, 0.0f, 1.0f}, wireframeWidth{1.0f}, colorMapOffset{1.0f/512.0f}, colorMapScale{1.0f/256.0f}, lineWidth{1.0f}, lineLength{1.0f}, smoothness{2.0f} {} + + /** @brief Construct without initializing the contents */ + explicit MeshVisualizerMaterialUniform(NoInitT) noexcept: color{NoInit}, wireframeColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref wireframeColor field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setWireframeColor(const Color4& color) { + wireframeColor = color; + return *this; + } + + /** + * @brief Set the @ref wireframeWidth field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setWireframeWidth(Float width) { + wireframeWidth = width; + return *this; + } + + /** + * @brief Set the @ref colorMapOffset and @ref colorMapScale fields + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setColorMapTransformation(Float offset, Float scale) { + colorMapOffset = offset; + colorMapScale = scale; + return *this; + } + + /** + * @brief Set the @ref lineWidth field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setLineWidth(Float width) { + lineWidth = width; + return *this; + } + + /** + * @brief Set the @ref lineLength field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setLineLength(Float length) { + lineLength = length; + return *this; + } + + /** + * @brief Set the @ref smoothness field + * @return Reference to self (for method chaining) + */ + MeshVisualizerMaterialUniform& setSmoothness(Float smoothness) { + this->smoothness = smoothness; + return *this; + } + + /** + * @} + */ + + /** + * @brief Base object color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::Wireframe "MeshVisualizerGL*D::Flag::Wireframe" + * or @relativeref{MeshVisualizerGL3D,Flag::InstancedObjectId} / + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveId} / + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveIdFromVertexId} is + * enabled. In case of the latter, the color is multiplied with the color + * map coming from @ref MeshVisualizerGL3D::bindColorMapTexture() "MeshVisualizerGL*D::bindColorMapTexture()". + * @see @ref MeshVisualizerGL2D::setColor(), + * @ref MeshVisualizerGL3D::setColor() + */ + Color4 color; + + /** + * @brief Wireframe color + * + * Default value is @cpp 0x000000ff_rgbaf @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::Wireframe "MeshVisualizerGL*D::Flag::Wireframe" + * is enabled. + * @see @ref MeshVisualizerGL2D::setWireframeColor(), + * @ref MeshVisualizerGL3D::setWireframeColor() + */ + Color4 wireframeColor; + + /** + * @brief Wireframe width + * + * The value is in screen space (depending on + * @ref MeshVisualizerGL3D::setViewportSize() "MeshVisualizerGL*D::setViewportSize()"), + * default value is @cpp 1.0f @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::Wireframe "MeshVisualizerGL*D::Flag::Wireframe" + * is enabled. + * @see @ref MeshVisualizerGL2D::setWireframeWidth(), + * @ref MeshVisualizerGL3D::setWireframeWidth() + */ + Float wireframeWidth; + + /** + * @brief Color map offset + * + * Together with @ref colorMapScale forms an offset and scale applied to + * the input value coming either from the + * @ref MeshVisualizerGL3D::ObjectId "MeshVisualizerGL*D::ObjectId" + * attribute or @glsl gl_PrimitiveID @ce, resulting value is then used to + * fetch a color from a color map bound with @ref MeshVisualizerGL3D::bindColorMapTexture() "MeshVisualizerGL*D::bindColorMapTexture()". + * Default offset and scale values are @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. + * + * Used only if @ref MeshVisualizerGL3D::Flag::InstancedObjectId "MeshVisualizerGL*D::Flag::InstancedObjectId" or + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveId} / + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveIdFromVertexId} is + * enabled. + * @see @ref MeshVisualizerGL2D::setColorMapTransformation(), + * @ref MeshVisualizerGL3D::setColorMapTransformation() + */ + Float colorMapOffset; + + /** + * @brief Color map offset + * + * See @ref colorMapOffset for more information. + * + * Used only by @ref MeshVisualizerGL3D and only if + * @ref MeshVisualizerGL3D::Flag::InstancedObjectId "MeshVisualizerGL*D::Flag::InstancedObjectId" or + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveId} / + * @relativeref{MeshVisualizerGL3D,Flag::PrimitiveIdFromVertexId} is + * enabled. + * @see @ref MeshVisualizerGL2D::setColorMapTransformation(), + * @ref MeshVisualizerGL3D::setColorMapTransformation() + */ + Float colorMapScale; + + /** + * @brief Line width + * + * The value is in screen space (depending on + * @ref MeshVisualizerGL3D::setViewportSize() "MeshVisualizerGL*D::setViewportSize()"), + * default value is @cpp 1.0f @ce. + * + * Used only by @ref MeshVisualizerGL3D and only if + * @ref MeshVisualizerGL3D::Flag::TangentDirection, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentFromTangentDirection}, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentDirection} or + * @relativeref{MeshVisualizerGL3D,Flag::NormalDirection} is enabled. + * @see @ref MeshVisualizerGL3D::setLineWidth() + */ + Float lineWidth; + + /** + * @brief Line length + * + * The value is in object space, default value is @cpp 1.0f @ce. + * + * Used only by @ref MeshVisualizerGL3D and only if + * @ref MeshVisualizerGL3D::Flag::TangentDirection, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentFromTangentDirection}, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentDirection} or + * @relativeref{MeshVisualizerGL3D,Flag::NormalDirection} is enabled. + * @see @ref MeshVisualizerGL3D::setLineLength() + */ + Float lineLength; + + /** + * @brief Line smoothness + * + * The value is in screen space (depending on + * @ref MeshVisualizerGL3D::setViewportSize() "MeshVisualizerGL*D::setViewportSize()"), + * initial value is @cpp 2.0f @ce. + * + * Used only if @ref MeshVisualizerGL3D::Flag::Wireframe "MeshVisualizerGL*D::Flag::Wireframe", + * @ref MeshVisualizerGL3D::Flag::TangentDirection, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentFromTangentDirection}, + * @relativeref{MeshVisualizerGL3D,Flag::BitangentDirection} or + * @relativeref{MeshVisualizerGL3D,Flag::NormalDirection} is enabled. + * @see @ref MeshVisualizerGL2D::setSmoothness(), + * @ref MeshVisualizerGL3D::setSmoothness() + */ + Float smoothness; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; + Int:32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief MeshVisualizerGL2D * @m_deprecated_since_latest Use @ref MeshVisualizerGL2D instead. */ @@ -60,10 +452,8 @@ typedef CORRADE_DEPRECATED("use MeshVisualizerGL3D instead") MeshVisualizerGL3D * @m_deprecated_since{2020,06} Use @ref MeshVisualizerGL3D instead. */ typedef CORRADE_DEPRECATED("use MeshVisualizerGL3D instead") MeshVisualizerGL3D MeshVisualizer; +#endif }} -#else -#error use Magnum/Shaders/MeshVisualizerGL.h and the MeshVisualizerGL2D / MeshVisualizer3D class instead -#endif #endif diff --git a/src/Magnum/Shaders/MeshVisualizer.vert b/src/Magnum/Shaders/MeshVisualizer.vert index a67ebc024..44b270b50 100644 --- a/src/Magnum/Shaders/MeshVisualizer.vert +++ b/src/Magnum/Shaders/MeshVisualizer.vert @@ -32,8 +32,13 @@ #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef TWO_DIMENSIONS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) @@ -97,6 +102,92 @@ uniform highp float lineLength ; #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +#ifdef TWO_DIMENSIONS +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + highp mat3 transformationProjectionMatrices[DRAW_COUNT]; +}; +#elif defined(THREE_DIMENSIONS) +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 0 + #endif +) uniform Projection { + highp mat4 projectionMatrix; +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform Transformation { + highp mat4 transformationMatrices[DRAW_COUNT]; +}; +#else +#error +#endif + +/* Keep in sync with MeshVisualizer.geom and MeshVisualizer.frag. Can't + "outsource" to a common file because the #extension directives need to be + always before any code. */ +struct DrawUniform { + #ifdef THREE_DIMENSIONS + highp mat3 normalMatrix; /* actually mat3x4 */ + #elif !defined(TWO_DIMENSIONS) + #error + #endif + highp uvec4 materialIdReservedReservedReservedReserved; + #define draw_materialIdReserved materialIdReservedReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +/* Keep in sync with MeshVisualizer.geom and MeshVisualizer.frag. Can't + "outsource" to a common file because the #extension directives need to be + always before any code. */ +struct MaterialUniform { + lowp vec4 color; + lowp vec4 wireframeColor; + lowp vec4 wireframeWidthColorMapOffsetColorMapScaleLineWidth; + #define material_wireframeWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.x + #define material_colorMapOffset wireframeWidthColorMapOffsetColorMapScaleLineWidth.y + #define material_colorMapScale wireframeWidthColorMapOffsetColorMapScaleLineWidth.z + #define material_lineWidth wireframeWidthColorMapOffsetColorMapScaleLineWidth.w + lowp vec4 lineLengthSmoothnessReservedReserved; + #define material_lineLength lineLengthSmoothnessReservedReserved.x + #define material_smoothness lineLengthSmoothnessReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -189,6 +280,23 @@ out highp vec4 normalEndpoint; #endif void main() { + #ifdef UNIFORM_BUFFERS + #ifdef TWO_DIMENSIONS + highp const mat3 transformationProjectionMatrix = transformationProjectionMatrices[drawOffset]; + #elif defined(THREE_DIMENSIONS) + highp const mat4 transformationMatrix = transformationMatrices[drawOffset]; + #else + #error + #endif + #if defined(TANGENT_DIRECTION) || defined(BITANGENT_DIRECTION) || defined(BITANGENT_FROM_TANGENT_DIRECTION) || defined(NORMAL_DIRECTION) + mediump const mat3 normalMatrix = draws[drawOffset].normalMatrix; + #endif + mediump const uint materialId = draws[drawOffset].draw_materialIdReserved & 0xffffu; + lowp float colorMapOffset = materials[materialId].material_colorMapOffset; + lowp float colorMapScale = materials[materialId].material_colorMapScale; + highp float lineLength = materials[materialId].material_lineLength; + #endif + #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix*vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) diff --git a/src/Magnum/Shaders/MeshVisualizerGL.cpp b/src/Magnum/Shaders/MeshVisualizerGL.cpp index a81eeb037..6d920a597 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.cpp +++ b/src/Magnum/Shaders/MeshVisualizerGL.cpp @@ -39,6 +39,10 @@ #include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { @@ -48,11 +52,36 @@ namespace { /* First four taken by Phong (A/D/S/N) */ ColorMapTextureUnit = 4 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + ProjectionBufferBinding = 0, + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from the 3D variant which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1, + TransformationBufferBinding = 1, + DrawBufferBinding = 2, + /* Binding 3 is commonly used by TextureTransformationBufferBinding, + leave it reserved */ + MaterialBufferBinding = 4, + }; + #endif } namespace Implementation { -MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags): _flags{flags} { +MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _materialCount{materialCount}, + _drawCount{drawCount} + #endif +{ #ifndef MAGNUM_TARGET_GLES2 #ifndef CORRADE_NO_ASSERT Int countMutuallyExclusive = 0; @@ -64,6 +93,11 @@ MeshVisualizerGLBase::MeshVisualizerGLBase(FlagsBase flags): _flags{flags} { "Shaders::MeshVisualizerGL: Flag::InstancedObjectId, Flag::VertexId and Flag::PrimitiveId are mutually exclusive", ); #endif + #ifndef MAGNUM_TARGET_GLES + if(flags >= FlagBase::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifndef MAGNUM_TARGET_GLES2 if(_flags & FlagBase::Wireframe && !(_flags & FlagBase::NoGeometryShader)) { #ifndef MAGNUM_TARGET_GLES @@ -126,6 +160,16 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra "#define SUBSCRIPTING_WORKAROUND\n" : "") #endif ; + #ifndef MAGNUM_TARGET_GLES2 + if(_flags >= FlagBase::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + _drawCount, + _materialCount)); + } + #endif frag.addSource(_flags & FlagBase::Wireframe ? "#define WIREFRAME_RENDERING\n" : "") #ifndef MAGNUM_TARGET_GLES2 .addSource(_flags & FlagBase::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") @@ -136,11 +180,25 @@ GL::Version MeshVisualizerGLBase::setupShaders(GL::Shader& vert, GL::Shader& fra "#define PRIMITIVE_ID\n") : "") #endif ; + #ifndef MAGNUM_TARGET_GLES2 + if(_flags >= FlagBase::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + _drawCount, + _materialCount)); + } + #endif return version; } 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), "Shaders::MeshVisualizerGL::setColor(): the shader was not created with wireframe or object/vertex/primitive ID enabled", *this); @@ -153,6 +211,10 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::setColor(const Color4& color) { } MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeColor(const Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), + "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & FlagBase::Wireframe, "Shaders::MeshVisualizerGL::setWireframeColor(): the shader was not created with wireframe enabled", *this); setUniform(_wireframeColorUniform, color); @@ -160,6 +222,10 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeColor(const Color4& colo } MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeWidth(const Float width) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= FlagBase::UniformBuffers), + "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & FlagBase::Wireframe, "Shaders::MeshVisualizerGL::setWireframeWidth(): the shader was not created with wireframe enabled", *this); setUniform(_wireframeWidthUniform, width); @@ -168,12 +234,41 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::setWireframeWidth(const Float width) #ifndef MAGNUM_TARGET_GLES2 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), "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was not created with object/vertex/primitive ID enabled", *this); setUniform(_colorMapOffsetScaleUniform, Vector2{offset, scale}); return *this; } +#endif +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGLBase& MeshVisualizerGLBase::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= FlagBase::UniformBuffers, + "Shaders::MeshVisualizerGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::MeshVisualizerGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + setUniform(_drawOffsetUniform, offset); + return *this; +} + +MeshVisualizerGLBase& MeshVisualizerGLBase::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= FlagBase::UniformBuffers, + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +MeshVisualizerGLBase& MeshVisualizerGLBase::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= FlagBase::UniformBuffers, + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 MeshVisualizerGLBase& MeshVisualizerGLBase::bindColorMapTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & (FlagBase::InstancedObjectId|FlagBase::VertexId|FlagBase::PrimitiveId), "Shaders::MeshVisualizerGL::bindColorMapTexture(): the shader was not created with object/vertex/primitive ID enabled", *this); @@ -184,7 +279,15 @@ MeshVisualizerGLBase& MeshVisualizerGLBase::bindColorMapTexture(GL::Texture2D& t } -MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedShort(flags))} { +MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedShort(flags)) + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #endif +} { #ifndef MAGNUM_TARGET_GLES2 CORRADE_ASSERT(flags & ((Flag::Wireframe|Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId) & ~Flag::NoGeometryShader), "Shaders::MeshVisualizerGL2D: at least one visualization feature has to be enabled", ); @@ -193,6 +296,16 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV "Shaders::MeshVisualizerGL2D: at least Flag::Wireframe has to be enabled", ); #endif + /* Has to be here and not in the base class in order to have it exit the + constructor when testing for asserts -- GLSL compilation would fail + otherwise */ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::MeshVisualizerGL2D: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::MeshVisualizerGL2D: draw count can't be zero", ); + #endif + #ifndef MAGNUM_TARGET_GLES const GL::Context& context = GL::Context::current(); #endif @@ -215,8 +328,12 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV nothing actually needs it, as that makes checks much simpler in the shader code */ .addSource((flags & Flag::NoGeometryShader) || !(flags & Flag::Wireframe) ? - "#define NO_GEOMETRY_SHADER\n" : "") - .addSource(rs.get("generic.glsl")) + "#define NO_GEOMETRY_SHADER\n" : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) + frag.addSource("#define TWO_DIMENSIONS\n"); + #endif + frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("MeshVisualizer.frag")); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -230,8 +347,19 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV .addSource(_flags & FlagBase::PrimitiveId ? (_flags >= FlagBase::PrimitiveIdFromVertexId ? "#define PRIMITIVE_ID_FROM_VERTEX_ID\n" : - "#define PRIMITIVE_ID\n") : "") - .addSource(rs.get("MeshVisualizer.geom")); + "#define PRIMITIVE_ID\n") : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + geom->addSource(Utility::formatString( + "#define TWO_DIMENSIONS\n" + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + _drawCount, + _materialCount)); + } + #endif + geom->addSource(rs.get("MeshVisualizer.geom")); } #else static_cast(version); @@ -276,25 +404,35 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & (Flag::Wireframe + /* This one is used also in the UBO case as it's usually a global + setting */ + if((flags & Flag::Wireframe) && !(flags & Flag::NoGeometryShader)) + _viewportSizeUniform = uniformLocation("viewportSize"); + + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + #endif + )) + _colorUniform = uniformLocation("color"); + if(flags & Flag::Wireframe) { + _wireframeColorUniform = uniformLocation("wireframeColor"); + _wireframeWidthUniform = uniformLocation("wireframeWidth"); + _smoothnessUniform = uniformLocation("smoothness"); + } #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); + } #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::VertexId|Flag::PrimitiveIdFromVertexId)) { - _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); } - #endif } #ifndef MAGNUM_TARGET_GLES2 @@ -305,31 +443,50 @@ MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): Implementation::MeshV if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + } + #endif } #endif /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(Matrix3{Math::IdentityInit}); - if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Viewport size is zero by default */ + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(Matrix3{Math::IdentityInit}); + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::VertexId|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 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) + setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #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::VertexId|Flag::PrimitiveIdFromVertexId)) - setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); - #endif #endif } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL2D::MeshVisualizerGL2D(const Flags flags): MeshVisualizerGL2D{flags, 1, 1} {} +#endif + MeshVisualizerGL2D& MeshVisualizerGL2D::setViewportSize(const Vector2& size) { /* Not asserting here, since the relation to wireframe is a bit vague. Also it's an ugly hack that should be removed, ideally. */ @@ -339,11 +496,19 @@ MeshVisualizerGL2D& MeshVisualizerGL2D::setViewportSize(const Vector2& size) { } MeshVisualizerGL2D& MeshVisualizerGL2D::setTransformationProjectionMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL2D::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } MeshVisualizerGL2D& MeshVisualizerGL2D::setSmoothness(const Float smoothness) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL2D::setSmoothness(): the shader was created with uniform buffers enabled", *this); + #endif /* This is a bit vaguely related but less vague than setViewportSize() so asserting in this case. */ CORRADE_ASSERT(flags() & Flag::Wireframe, @@ -352,7 +517,45 @@ MeshVisualizerGL2D& MeshVisualizerGL2D::setSmoothness(const Float smoothness) { return *this; } -MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedShort(flags))} { +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL2D& MeshVisualizerGL2D::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +MeshVisualizerGL2D& MeshVisualizerGL2D::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +MeshVisualizerGL2D& MeshVisualizerGL2D::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL2D::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +MeshVisualizerGL2D& MeshVisualizerGL2D::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL2D::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} +#endif + +MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): Implementation::MeshVisualizerGLBase{Implementation::MeshVisualizerGLBase::FlagBase(UnsignedShort(flags)) + #ifndef MAGNUM_TARGET_GLES2 + , materialCount, drawCount + #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), "Shaders::MeshVisualizerGL3D: at least one visualization feature has to be enabled", ); @@ -368,6 +571,16 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV "Shaders::MeshVisualizerGL3D: at least Flag::Wireframe has to be enabled", ); #endif + /* Has to be here and not in the base class in order to have it exit the + constructor when testing for asserts -- GLSL compilation would fail + otherwise */ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::MeshVisualizerGL3D: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::MeshVisualizerGL3D: draw count can't be zero", ); + #endif + #ifndef MAGNUM_TARGET_GLES const GL::Context& context = GL::Context::current(); #endif @@ -399,7 +612,8 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV .addSource(flags & Flag::BitangentDirection ? "#define BITANGENT_DIRECTION\n" : "") .addSource(flags & Flag::NormalDirection ? "#define NORMAL_DIRECTION\n" : "") #endif - .addSource(rs.get("generic.glsl")) + ; + vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("MeshVisualizer.vert")); frag /* Pass NO_GEOMETRY_SHADER not only when NoGeometryShader but also when @@ -413,7 +627,12 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) .addSource(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection) ? "#define TBN_DIRECTION\n" : "") #endif - .addSource(rs.get("generic.glsl")) + ; + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) + frag.addSource("#define THREE_DIMENSIONS\n"); + #endif + frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("MeshVisualizer.frag")); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) @@ -438,8 +657,19 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV "#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" : "") - .addSource(rs.get("MeshVisualizer.geom")); + .addSource(flags & Flag::NormalDirection ? "#define NORMAL_DIRECTION\n" : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + geom->addSource(Utility::formatString( + "#define THREE_DIMENSIONS\n" + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + _drawCount, + _materialCount)); + } + #endif + geom->addSource(rs.get("MeshVisualizer.geom")); } #else static_cast(version); @@ -495,39 +725,53 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV if(!context.isExtensionSupported(version)) #endif { - _transformationMatrixUniform = uniformLocation("transformationMatrix"); - _projectionMatrixUniform = uniformLocation("projectionMatrix"); - if(flags & (Flag::Wireframe - #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId - #endif - )) - _colorUniform = uniformLocation("color"); - if(flags & Flag::Wireframe) { - _wireframeColorUniform = uniformLocation("wireframeColor"); - _wireframeWidthUniform = uniformLocation("wireframeWidth"); - } - if(flags & (Flag::Wireframe + /* This one is used also in the UBO case as it's usually a global + setting */ + if(((flags & Flag::Wireframe) && !(flags & Flag::NoGeometryShader)) #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection + || (flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) #endif - )) { - _smoothnessUniform = uniformLocation("smoothness"); - if(!(flags & Flag::NoGeometryShader)) - _viewportSizeUniform = uniformLocation("viewportSize"); - } + ) + _viewportSizeUniform = uniformLocation("viewportSize"); + #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { - _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); - } + if(flags >= Flag::UniformBuffers) { + _drawOffsetUniform = uniformLocation("drawOffset"); + } else #endif - #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { - _normalMatrixUniform = uniformLocation("normalMatrix"); - _lineWidthUniform = uniformLocation("lineWidth"); - _lineLengthUniform = uniformLocation("lineLength"); + { + _transformationMatrixUniform = uniformLocation("transformationMatrix"); + _projectionMatrixUniform = uniformLocation("projectionMatrix"); + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + #endif + )) + _colorUniform = uniformLocation("color"); + if(flags & Flag::Wireframe) { + _wireframeColorUniform = uniformLocation("wireframeColor"); + _wireframeWidthUniform = uniformLocation("wireframeWidth"); + } + if(flags & (Flag::Wireframe + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection + #endif + )) { + _smoothnessUniform = uniformLocation("smoothness"); + } + #ifndef MAGNUM_TARGET_GLES2 + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { + _colorMapOffsetScaleUniform = uniformLocation("colorMapOffsetScale"); + } + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { + _normalMatrixUniform = uniformLocation("normalMatrix"); + _lineWidthUniform = uniformLocation("lineWidth"); + _lineLengthUniform = uniformLocation("lineLength"); + } + #endif } - #endif } #ifndef MAGNUM_TARGET_GLES2 @@ -538,57 +782,87 @@ MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): Implementation::MeshV if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) { setUniform(uniformLocation("colorMapTexture"), ColorMapTextureUnit); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("Projection"), ProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Transformation"), TransformationBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + } + #endif } #endif /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationMatrix(Matrix4{Math::IdentityInit}); - setProjectionMatrix(Matrix4{Math::IdentityInit}); - if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Viewport size is zero by default */ + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationMatrix(Matrix4{Math::IdentityInit}); + setProjectionMatrix(Matrix4{Math::IdentityInit}); + if(flags & (Flag::Wireframe + #ifndef MAGNUM_TARGET_GLES2 + |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + #endif + )) + setColor(Color3(1.0f)); + if(flags & Flag::Wireframe) { + /* Viewport size is zero by default */ + setWireframeColor(Color3{0.0f}); + setWireframeWidth(1.0f); + } + if(flags & (Flag::Wireframe + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection + #endif + )) { + setSmoothness(2.0f); + } #ifndef MAGNUM_TARGET_GLES2 - |Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId + if(flags & (Flag::InstancedObjectId|Flag::VertexId|Flag::PrimitiveIdFromVertexId)) + setColorMapTransformation(1.0f/512.0f, 1.0f/256.0f); #endif - )) - setColor(Color3(1.0f)); - if(flags & Flag::Wireframe) { - /* Viewport size is zero by default */ - setWireframeColor(Color3{0.0f}); - setWireframeWidth(1.0f); - } - if(flags & (Flag::Wireframe #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - |Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection + if(flags & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection)) { + setNormalMatrix(Matrix3x3{Math::IdentityInit}); + setLineWidth(1.0f); + setLineLength(1.0f); + } #endif - )) { - setSmoothness(2.0f); - } - #ifndef MAGNUM_TARGET_GLES2 - if(flags & (Flag::InstancedObjectId|Flag::VertexId|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(Matrix3x3{Math::IdentityInit}); - setLineWidth(1.0f); - setLineLength(1.0f); } #endif - #endif } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL3D::MeshVisualizerGL3D(const Flags flags): MeshVisualizerGL3D{flags, 1, 1} {} +#endif + MeshVisualizerGL3D& MeshVisualizerGL3D::setTransformationMatrix(const Matrix4& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setTransformationMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationMatrixUniform, matrix); return *this; } MeshVisualizerGL3D& MeshVisualizerGL3D::setProjectionMatrix(const Matrix4& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_projectionMatrixUniform, matrix); return *this; } #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) MeshVisualizerGL3D& MeshVisualizerGL3D::setNormalMatrix(const Matrix3x3& matrix) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setNormalMatrix(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), "Shaders::MeshVisualizerGL3D::setNormalMatrix(): the shader was not created with TBN direction enabled", *this); setUniform(_normalMatrixUniform, matrix); @@ -610,6 +884,8 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setViewportSize(const Vector2& size) { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) MeshVisualizerGL3D& MeshVisualizerGL3D::setLineWidth(const Float width) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setLineWidth(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), "Shaders::MeshVisualizerGL3D::setLineWidth(): the shader was not created with TBN direction enabled", *this); setUniform(_lineWidthUniform, width); @@ -617,6 +893,8 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setLineWidth(const Float width) { } MeshVisualizerGL3D& MeshVisualizerGL3D::setLineLength(const Float length) { + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setLineLength(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(flags() & (Flag::TangentDirection|Flag::BitangentFromTangentDirection|Flag::BitangentDirection|Flag::NormalDirection), "Shaders::MeshVisualizerGL3D::setLineLength(): the shader was not created with TBN direction enabled", *this); setUniform(_lineLengthUniform, length); @@ -625,6 +903,10 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setLineLength(const Float length) { #endif MeshVisualizerGL3D& MeshVisualizerGL3D::setSmoothness(const Float smoothness) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags() >= Flag::UniformBuffers), + "Shaders::MeshVisualizerGL3D::setSmoothness(): the shader was created with uniform buffers enabled", *this); + #endif #ifndef CORRADE_NO_ASSERT /* This is a bit vaguely related but less vague than setViewportSize() so asserting in this case. */ @@ -640,6 +922,50 @@ MeshVisualizerGL3D& MeshVisualizerGL3D::setSmoothness(const Float smoothness) { return *this; } +#ifndef MAGNUM_TARGET_GLES2 +MeshVisualizerGL3D& MeshVisualizerGL3D::bindProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, ProjectionBufferBinding); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, ProjectionBufferBinding, offset, size); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationBufferBinding); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationBufferBinding, offset, size); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +MeshVisualizerGL3D& MeshVisualizerGL3D::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(flags() >= Flag::UniformBuffers, + "Shaders::MeshVisualizerGL3D::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} +#endif + Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flag value) { debug << "Shaders::MeshVisualizerGL2D::Flag" << Debug::nospace; @@ -656,6 +982,9 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flag value) { #endif _c(PrimitiveIdFromVertexId) #endif + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -685,6 +1014,9 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flag value) { #endif _c(PrimitiveIdFromVertexId) #endif + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -703,7 +1035,10 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL2D::Flags value) { MeshVisualizerGL2D::Flag::VertexId, MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL - MeshVisualizerGL2D::Flag::PrimitiveId + MeshVisualizerGL2D::Flag::PrimitiveId, + #endif + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGL2D::Flag::UniformBuffers #endif #endif }); @@ -726,7 +1061,10 @@ Debug& operator<<(Debug& debug, const MeshVisualizerGL3D::Flags value) { MeshVisualizerGL3D::Flag::VertexId, MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, /* Superset of PrimitiveId */ #ifndef MAGNUM_TARGET_WEBGL - MeshVisualizerGL3D::Flag::PrimitiveId + MeshVisualizerGL3D::Flag::PrimitiveId, + #endif + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGL3D::Flag::UniformBuffers #endif #endif }); diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index 949954a05..ca21d8de9 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -53,14 +53,20 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr InstancedObjectId = 1 << 2, VertexId = 1 << 3, PrimitiveId = 1 << 4, - PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId + PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId, + /* bit 6, 7, 8, 9 used by 3D-specific TBN visualization */ + UniformBuffers = 1 << 10 #endif }; typedef Containers::EnumSet FlagsBase; CORRADE_ENUMSET_FRIEND_OPERATORS(FlagsBase) - explicit MeshVisualizerGLBase(FlagsBase flags); + explicit MeshVisualizerGLBase(FlagsBase flags + #ifndef MAGNUM_TARGET_GLES2 + , UnsignedInt materialCount, UnsignedInt drawCount + #endif + ); explicit MeshVisualizerGLBase(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} MAGNUM_SHADERS_LOCAL GL::Version setupShaders(GL::Shader& vert, GL::Shader& frag, const Utility::Resource& rs) const; @@ -73,6 +79,12 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr MeshVisualizerGLBase& bindColorMapTexture(GL::Texture2D& texture); #endif + #ifndef MAGNUM_TARGET_GLES2 + MeshVisualizerGLBase& setDrawOffset(UnsignedInt offset); + MeshVisualizerGLBase& bindMaterialBuffer(GL::Buffer& buffer); + MeshVisualizerGLBase& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + #endif + /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES using GL::AbstractShaderProgram::drawTransformFeedback; @@ -82,6 +94,9 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr #endif FlagsBase _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _materialCount{}, _drawCount{}; + #endif Int _colorUniform{1}, _wireframeColorUniform{2}, _wireframeWidthUniform{3}, @@ -89,6 +104,9 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGLBase: public GL::AbstractShaderProgr _viewportSizeUniform{5}; #ifndef MAGNUM_TARGET_GLES2 Int _colorMapOffsetScaleUniform{6}; + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; #endif }; @@ -200,11 +218,26 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua /** @copydoc MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId */ #ifndef MAGNUM_TARGET_WEBGL - PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId + PrimitiveIdFromVertexId = (1 << 5)|PrimitiveId, #else - PrimitiveIdFromVertexId = (1 << 5)|(1 << 4) + PrimitiveIdFromVertexId = (1 << 5)|(1 << 4), #endif #endif + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer(), @ref bindDrawBuffer() + * and @ref bindMaterialBuffer() instead of direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 10 + #endif }; /** @brief Flags */ @@ -215,9 +248,50 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @param flags Flags * * At least @ref Flag::Wireframe is expected to be enabled. + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref MeshVisualizerGL2D(Flags, UnsignedInt, UnsignedInt) with + * @p materialCount and @p drawCount set to @cpp 1 @ce. */ explicit MeshVisualizerGL2D(Flags flags); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param materialCount Size of a @ref MeshVisualizerMaterialUniform + * buffer bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref MeshVisualizerMaterialUniform buffer bound with + * @ref bindTransformationProjectionBuffer() and + * @ref bindDrawBuffer() + * + * At least @ref Flag::Wireframe is expected to be enabled. + * + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials are specified via + * @ref MeshVisualizerDrawUniform2D::materialId. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref MeshVisualizerGL2D(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit MeshVisualizerGL2D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -249,8 +323,37 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua return Flag(UnsignedShort(Implementation::MeshVisualizerGLBase::_flags)); } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref MeshVisualizerMaterialUniform + * uniform buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D and + * @ref MeshVisualizerDrawUniform2D uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -258,6 +361,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @return Reference to self (for method chaining) * * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ MeshVisualizerGL2D& setTransformationProjectionMatrix(const Matrix3& matrix); @@ -280,6 +387,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @ref Flag::PrimitiveId / @ref Flag::PrimitiveIdFromVertexId is * enabled. In case of the latter, the color is multiplied with the * color map coming from @ref bindColorMapTexture(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::color and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL2D& setColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerGLBase::setColor(color)); @@ -291,6 +402,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * * Initial value is @cpp 0x000000ff_rgbaf @ce. Expects that * @ref Flag::Wireframe is enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::wireframeColor and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL2D& setWireframeColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerGLBase::setWireframeColor(color)); @@ -303,6 +418,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * The value is in screen space (depending on @ref setViewportSize()), * initial value is @cpp 1.0f @ce. Expects that @ref Flag::Wireframe is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::wireframeWidth and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL2D& setWireframeWidth(Float width) { return static_cast(Implementation::MeshVisualizerGLBase::setWireframeWidth(width)); @@ -322,6 +441,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * Value is in screen space (depending on @ref setViewportSize()), * initial value is @cpp 2.0f @ce. Expects that @ref Flag::Wireframe is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::smoothness and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL2D& setSmoothness(Float smoothness); @@ -329,6 +452,103 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D + * and @ref MeshVisualizerDrawUniform2D buffers bound with + * @ref bindTransformationProjectionBuffer() and @ref bindDrawBuffer() + * should be used for current draw. Expects that + * @ref Flag::UniformBuffers is set and @p offset is less than + * @ref drawCount(). Initial value is @cpp 0 @ce. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL2D& setDrawOffset(UnsignedInt offset) { + return static_cast(Implementation::MeshVisualizerGLBase::setDrawOffset(offset)); + } + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationUniform3D. At the very least you need to call + * also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL2D& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL2D& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref MeshVisualizerDrawUniform2D. At the very least you need to call + * also @ref bindTransformationProjectionBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL2D& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL2D& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref MeshVisualizerMaterialUniform. At the very least you need to + * call also @ref bindTransformationProjectionBuffer() and + * @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL2D& bindMaterialBuffer(GL::Buffer& buffer) { + return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer)); + } + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL2D& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size) { + return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer, offset, size)); + } + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ @@ -761,7 +981,23 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @requires_gles Geometry shaders are not available in WebGL. * @m_since{2020,06} */ - NormalDirection = 1 << 9 + NormalDirection = 1 << 9, + #endif + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer(), + * @ref bindDrawBuffer() and @ref bindMaterialBuffer() instead of + * direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 10 #endif }; @@ -776,6 +1012,11 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentFromTangentDirection, * @ref Flag::BitangentDirection, @ref Flag::NormalDirection is * expected to be enabled. + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref MeshVisualizerGL3D(Flags, UnsignedInt, UnsignedInt) with + * @p materialCount and @p drawCount set to @cpp 1 @ce. */ explicit MeshVisualizerGL3D(Flags flags); @@ -788,6 +1029,46 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua explicit CORRADE_DEPRECATED("use MeshVisualizerGL3D(Flags) instead") MeshVisualizerGL3D(): MeshVisualizerGL3D{{}} {} #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param materialCount Size of a @ref MeshVisualizerMaterialUniform + * buffer bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref ProjectionUniform3D / + * @ref TransformationUniform3D / + * @ref MeshVisualizerMaterialUniform buffer bound with + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer() + * and @ref bindDrawBuffer() + * + * At least @ref Flag::Wireframe or one of @ref Flag::TangentDirection, + * @ref Flag::BitangentFromTangentDirection, + * @ref Flag::BitangentDirection, @ref Flag::NormalDirection is + * expected to be enabled. + * + * If @p flags contains @ref Flag::UniformBuffers, @p materialCount and + * @p drawCount describe the uniform buffer sizes as these are required + * to have a statically defined size. The draw offset is then set via + * @ref setDrawOffset() and the per-draw materials are specified via + * @ref MeshVisualizerDrawUniform3D::materialId. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref MeshVisualizerGL3D(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit MeshVisualizerGL3D(Flags flags, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -819,8 +1100,37 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua return Flag(UnsignedShort(Implementation::MeshVisualizerGLBase::_flags)); } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref MeshVisualizerMaterialUniform + * uniform buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform3D and + * @ref MeshVisualizerDrawUniform3D uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ #ifdef MAGNUM_BUILD_DEPRECATED @@ -841,6 +1151,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @return Reference to self (for method chaining) * * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindTransformationBuffer() instead. */ MeshVisualizerGL3D& setTransformationMatrix(const Matrix4& matrix); @@ -851,6 +1165,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * Initial value is an identity matrix. (i.e., an orthographic * projection of the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ * cube). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref ProjectionUniform3D::projectionMatrix and call + * @ref bindProjectionBuffer() instead. */ MeshVisualizerGL3D& setProjectionMatrix(const Matrix4& matrix); @@ -865,6 +1183,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * enabled. The matrix doesn't need to be normalized, as * renormalization is done per-fragment anyway. * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerDrawUniform3D::normalMatrix and call + * @ref bindDrawBuffer() instead. * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} * @requires_gles30 Not defined in OpenGL ES 2.0. * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / @@ -894,6 +1216,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::PrimitiveId / @ref Flag::PrimitiveIdFromVertexId is * enabled. In case of the latter, the color is multiplied with the * color map coming from @ref bindColorMapTexture(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::color and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL3D& setColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerGLBase::setColor(color)); @@ -905,6 +1231,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * * Initial value is @cpp 0x000000ff_rgbaf @ce. Expects that * @ref Flag::Wireframe is enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::wireframeColor and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL3D& setWireframeColor(const Color4& color) { return static_cast(Implementation::MeshVisualizerGLBase::setWireframeColor(color)); @@ -917,6 +1247,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * The value is in screen space (depending on @ref setViewportSize()), * initial value is @cpp 1.0f @ce. Expects that @ref Flag::Wireframe is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::wireframeWidth and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL3D& setWireframeWidth(Float width) { return static_cast(Implementation::MeshVisualizerGLBase::setWireframeWidth(width)); @@ -943,6 +1277,11 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @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 + * @ref bindMaterialBuffer() instead. * @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 @@ -969,6 +1308,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentFromTangentDirection, * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::lineWidth and call + * @ref bindMaterialBuffer() instead. * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} * @requires_gles30 Not defined in OpenGL ES 2.0. * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / @@ -987,6 +1330,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentFromTangentDirection, * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::lineLength and call + * @ref bindMaterialBuffer() instead. * @requires_gl32 Extension @gl_extension{ARB,geometry_shader4} * @requires_gles30 Not defined in OpenGL ES 2.0. * @requires_gles32 Extension @gl_extension{ANDROID,extension_pack_es31a} / @@ -1006,6 +1353,10 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @ref Flag::BitangentFromTangentDirection, * @ref Flag::BitangentDirection or @ref Flag::NormalDirection is * enabled. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref MeshVisualizerMaterialUniform::smoothness and call + * @ref bindMaterialBuffer() instead. */ MeshVisualizerGL3D& setSmoothness(Float smoothness); @@ -1013,6 +1364,124 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationUniform3D and + * @ref MeshVisualizerDrawUniform3D buffers bound with + * @ref bindTransformationBuffer() and @ref bindDrawBuffer() should be + * used for current draw. Expects that @ref Flag::UniformBuffers is set + * and @p offset is less than @ref drawCount(). Initial value is + * @cpp 0 @ce. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& setDrawOffset(UnsignedInt offset) { + return static_cast(Implementation::MeshVisualizerGLBase::setDrawOffset(offset)); + } + + /** + * @brief Set a projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain at least one instance of + * @ref ProjectionUniform3D. At the very least you need to call also + * @ref bindTransformationBuffer(), @ref bindDrawBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& bindProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationUniform3D. At the very least you need to call + * also @ref bindDrawBuffer() and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& bindTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref MeshVisualizerDrawUniform3D. At the very least you need to call + * also @ref bindProjectionBuffer(), @ref bindTransformationBuffer() + * and @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref MeshVisualizerMaterialUniform. At the very least you need to + * call also @ref bindProjectionBuffer(), + * @ref bindTransformationBuffer() and @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + MeshVisualizerGL3D& bindMaterialBuffer(GL::Buffer& buffer) { + return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer)); + } + /** + * @overload + * @m_since_latest + */ + MeshVisualizerGL3D& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size) { + return static_cast(Implementation::MeshVisualizerGLBase::bindMaterialBuffer(buffer, offset, size)); + } + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ diff --git a/src/Magnum/Shaders/Phong.frag b/src/Magnum/Shaders/Phong.frag index ca6fefe01..07c11a2fe 100644 --- a/src/Magnum/Shaders/Phong.frag +++ b/src/Magnum/Shaders/Phong.frag @@ -39,6 +39,7 @@ /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 4) #endif @@ -53,7 +54,6 @@ uniform lowp vec4 ambientColor ; #if LIGHT_COUNT - #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 5) #endif @@ -145,6 +145,78 @@ uniform lowp float lightRanges[LIGHT_COUNT] ; #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +/* Keep in sync with Phong.vert. Can't "outsource" to a common file because + the #extension directive needs to be always before any code. */ +struct DrawUniform { + mediump mat3 normalMatrix; /* actually mat3x4 */ + highp uvec4 materialIdReservedObjectIdLightOffsetLightCount; + #define draw_materialIdReserved materialIdReservedObjectIdLightOffsetLightCount.x + #define draw_objectId materialIdReservedObjectIdLightOffsetLightCount.y + #define draw_lightOffset materialIdReservedObjectIdLightOffsetLightCount.z + #define draw_lightCount materialIdReservedObjectIdLightOffsetLightCount.w +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +struct MaterialUniform { + lowp vec4 ambientColor; + lowp vec4 diffuseColor; + lowp vec4 specularColor; + mediump vec4 normalTextureScaleShininessAlphaMaskReserved; + #define material_normalTextureScale normalTextureScaleShininessAlphaMaskReserved.x + #define material_shininess normalTextureScaleShininessAlphaMaskReserved.y + #define material_alphaMask normalTextureScaleShininessAlphaMaskReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 4 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; + +/* Keep in sync with Phong.vert. Can't "outsource" to a common file because + the #extension directive needs to be always before any code. */ +struct LightUniform { + highp vec4 position; + lowp vec3 colorReserved; + #define light_color colorReserved.xyz + lowp vec4 specularColorReserved; + #define light_specularColor specularColorReserved.xyz + lowp vec4 rangeReservedReservedReserved; + #define light_range rangeReservedReservedReserved.x +}; + +#if LIGHT_COUNT +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 5 + #endif +) uniform Light { + LightUniform lights[LIGHT_COUNT]; +}; +#endif +#endif + /* Textures */ #ifdef AMBIENT_TEXTURE @@ -222,6 +294,28 @@ out highp uint fragmentObjectId; #endif void main() { + #ifdef UNIFORM_BUFFERS + #ifdef OBJECT_ID + highp const uint objectId = draws[drawOffset].draw_objectId; + #endif + mediump const uint materialId = draws[drawOffset].draw_materialIdReserved & 0xffffu; + lowp const vec4 ambientColor = materials[materialId].ambientColor; + #if LIGHT_COUNT + lowp const vec4 diffuseColor = materials[materialId].diffuseColor; + lowp const vec4 specularColor = materials[materialId].specularColor; + mediump const float shininess = materials[materialId].material_shininess; + #endif + #ifdef NORMAL_TEXTURE + mediump float normalTextureScale = materials[materialId].material_normalTextureScale; + #endif + #ifdef ALPHA_MASK + lowp const float alphaMask = materials[materialId].material_alphaMask; + #endif + #if LIGHT_COUNT + mediump const uint lightOffset = draws[drawOffset].draw_lightOffset; + #endif + #endif + lowp const vec4 finalAmbientColor = #ifdef AMBIENT_TEXTURE texture(ambientTexture, interpolatedTextureCoordinates)* @@ -273,7 +367,34 @@ void main() { #endif /* Add diffuse color for each light */ - for(int i = 0; i < LIGHT_COUNT; ++i) { + #ifndef UNIFORM_BUFFERS + for(int i = 0; i < LIGHT_COUNT; ++i) + #else + for(uint i = 0u, actualLightCount = min(uint(LIGHT_COUNT), draws[drawOffset].draw_lightCount); i < actualLightCount; ++i) + #endif + { + lowp const vec3 lightColor = + #ifndef UNIFORM_BUFFERS + lightColors[i] + #else + lights[lightOffset + i].light_color + #endif + ; + lowp const vec3 lightSpecularColor = + #ifndef UNIFORM_BUFFERS + lightSpecularColors[i] + #else + lights[lightOffset + i].light_specularColor + #endif + ; + lowp const float lightRange = + #ifndef UNIFORM_BUFFERS + lightRanges[i] + #else + lights[lightOffset + i].light_range + #endif + ; + /* Attenuation. Directional lights have the .w component set to 0, use that to make the distance zero -- which will then ensure the attenuation is always 1.0 */ @@ -281,19 +402,25 @@ void main() { /* If range is 0 for whatever reason, clamp it to a small value to avoid a NaN when dist is 0 as well (which is the case for directional lights). */ - highp float attenuation = clamp(1.0 - pow(dist/max(lightRanges[i], 0.0001), 4.0), 0.0, 1.0); + highp float attenuation = clamp(1.0 - pow(dist/max(lightRange, 0.0001), 4.0), 0.0, 1.0); attenuation = attenuation*attenuation/(1.0 + dist*dist); highp vec3 normalizedLightDirection = normalize(lightDirections[i].xyz); lowp float intensity = max(0.0, dot(normalizedTransformedNormal, normalizedLightDirection))*attenuation; - fragmentColor += vec4(finalDiffuseColor.rgb*lightColors[i]*intensity, finalDiffuseColor.a/float(LIGHT_COUNT)); + fragmentColor += vec4(finalDiffuseColor.rgb*lightColor*intensity, finalDiffuseColor.a/float( + #ifndef UNIFORM_BUFFERS + LIGHT_COUNT + #else + actualLightCount + #endif + )); /* Add specular color, if needed */ if(intensity > 0.001) { highp vec3 reflection = reflect(-normalizedLightDirection, normalizedTransformedNormal); /* Use attenuation for the specularity as well */ mediump float specularity = clamp(pow(max(0.0, dot(normalize(cameraDirection), reflection)), shininess), 0.0, 1.0)*attenuation; - fragmentColor += vec4(finalSpecularColor.rgb*lightSpecularColors[i].rgb*specularity, finalSpecularColor.a); + fragmentColor += vec4(finalSpecularColor.rgb*lightSpecularColor.rgb*specularity, finalSpecularColor.a); } } #endif diff --git a/src/Magnum/Shaders/Phong.h b/src/Magnum/Shaders/Phong.h index 24a5cd9af..8252f62e9 100644 --- a/src/Magnum/Shaders/Phong.h +++ b/src/Magnum/Shaders/Phong.h @@ -25,33 +25,460 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::Phong - * @m_deprecated_since_latest Use @ref Magnum/Shaders/PhongGL.h and the - * @ref Magnum::Shaders::PhongGL "PhongGL" class instead + * @brief Struct @ref Magnum::Shaders::PhongDrawUniform, @ref Magnum::Shaders::PhongMaterialUniform, @ref Magnum::Shaders::PhongLightUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Tags.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/PhongGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/PhongGL.h and the PhongGL class instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for Phong shaders +@m_since_latest + +Together with the generic @ref TransformationUniform3D contains parameters that +are specific to each draw call. Texture transformation, if needed, is supplied +separately in a @ref TextureTransformationUniform; material-related properties +are expected to be shared among multiple draw calls and thus are provided in a +separate @ref PhongMaterialUniform structure, referenced by @ref materialId. +@see @ref PhongGL::bindDrawBuffer() +*/ +struct PhongDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit PhongDrawUniform(DefaultInitT = DefaultInit) noexcept: normalMatrix{Math::IdentityInit}, materialId{0}, objectId{0}, lightOffset{0}, lightCount{0xffffffffu} {} + + /** @brief Construct without initializing the contents */ + explicit PhongDrawUniform(NoInitT) noexcept: normalMatrix{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref normalMatrix field + * @return Reference to self (for method chaining) + * + * The matrix is expanded to @relativeref{Magnum,Matrix3x4}, with the + * bottom row being zeros. + */ + PhongDrawUniform& setNormalMatrix(const Matrix3x3& matrix) { + normalMatrix = Matrix3x4{matrix}; + return *this; + } + + /** + * @brief Set the @ref materialId field + * @return Reference to self (for method chaining) + */ + PhongDrawUniform& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @brief Set the @ref objectId field + * @return Reference to self (for method chaining) + */ + PhongDrawUniform& setObjectId(UnsignedInt id) { + objectId = id; + return *this; + } + + /** + * @brief Set the @ref lightOffset and @ref lightCount fields + * @return Reference to self (for method chaining) + */ + PhongDrawUniform& setLightOffsetCount(UnsignedInt offset, UnsignedInt count) { + lightOffset = offset; + lightCount = count; + return *this; + } + + /** + * @} + */ + + /** + * @brief Normal matrix + * + * Default value is an identity matrix. The bottom row is unused and acts + * only as a padding to match uniform buffer packing rules. + * + * If @ref PhongGL::Flag::InstancedTransformation is enabled, the + * per-instance normal matrix coming from the @ref PhongGL::NormalMatrix + * attribute is applied first, before this one. + * @see @ref PhongGL::setNormalMatrix() + */ + Matrix3x4 normalMatrix; + + /** @var materialId + * @brief Material ID + * + * References a particular material from a @ref PhongMaterialUniform array. + * Useful when a UBO with more than one material is supplied or in a + * multi-draw scenario. Should be less than the material count passed to + * the @ref PhongGL::PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) + * constructor. Default value is @cpp 0 @ce, meaning the first material + * gets used. + */ + + /* 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 + 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 + UnsignedShort:16; /* reserved for skinOffset */ + #endif + #else + UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort materialId; + #endif + + /** + * @brief Object ID + * + * Unlike @ref materialId, this index is used only for the object ID + * framebuffer output, not to access any other uniform data. Default value + * is @cpp 0 @ce. + * + * Used only if @ref PhongGL::Flag::ObjectId is enabled, ignored otherwise. + * If @ref PhongGL::Flag::InstancedObjectId is enabled as well, this value + * is added to the ID coming from the @ref PhongGL::ObjectId attribute. + * @see @ref PhongGL::setObjectId() + */ + UnsignedInt objectId; + + /** + * @brief Light offset + * + * References the first light in the @ref PhongLightUniform array. Should + * be less than the light count passed to @ref PhongGL constructor. Default + * value is @cpp 0 @ce. + */ + UnsignedInt lightOffset; + + /** + * @brief Light count + * + * Specifies how many lights after the @p lightOffset are used from the + * @ref PhongLightUniform array. Gets clamped by the shader so it's + * together with @ref lightOffset not larger than the light count passed to + * @ref PhongGL constructor. Default value is @cpp 0xffffffffu @ce. + */ + UnsignedInt lightCount; +}; + +/** +@brief Material uniform for Phong shaders +@m_since_latest + +Describes material properties referenced from +@ref PhongDrawUniform::materialId. +@see @ref PhongGL::bindMaterialBuffer() +*/ +struct PhongMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit PhongMaterialUniform(DefaultInitT = DefaultInit) noexcept: ambientColor{0.0f, 0.0f, 0.0f, 0.0f}, diffuseColor{1.0f, 1.0f, 1.0f, 1.0f}, specularColor{1.0f, 1.0f, 1.0f, 0.0f}, normalTextureScale{1.0f}, shininess{80.0f}, alphaMask{0.5f} {} + + /** @brief Construct without initializing the contents */ + explicit PhongMaterialUniform(NoInitT) noexcept: ambientColor{NoInit}, diffuseColor{NoInit}, specularColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref ambientColor field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setAmbientColor(const Color4& color) { + ambientColor = color; + return *this; + } + + /** + * @brief Set the @ref diffuseColor field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setDiffuseColor(const Color4& color) { + diffuseColor = color; + return *this; + } + + /** + * @brief Set the @ref specularColor field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setSpecularColor(const Color4& color) { + specularColor = color; + return *this; + } + + /** + * @brief Set the @ref normalTextureScale field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setNormalTextureScale(Float scale) { + normalTextureScale = scale; + return *this; + } + + /** + * @brief Set the @ref shininess field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setShininess(Float shininess) { + this->shininess = shininess; + return *this; + } + + /** + * @brief Set the @ref alphaMask field + * @return Reference to self (for method chaining) + */ + PhongMaterialUniform& setAlphaMask(Float alphaMask) { + this->alphaMask = alphaMask; + return *this; + } + + /** + * @} + */ + + /** + * @brief Ambient color + * + * Default value is @cpp 0x00000000_rgbaf @ce. If + * @ref PhongGL::Flag::AmbientTexture is enabled, be sure to set this field + * to @cpp 0xffffffff_rgbaf @ce, otherwise the texture will be ignored. + * + * If @ref PhongGL::Flag::VertexColor is enabled, the color is multiplied + * with a color coming from the @ref PhongGL::Color3 / @ref PhongGL::Color4 + * attribute. + * @see @ref PhongGL::setAmbientColor() + */ + Color4 ambientColor; + + /** + * @brief Diffuse color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * + * Used only if @ref PhongDrawUniform::lightCount is not zero, ignored + * otherwise. If @ref PhongGL::Flag::VertexColor is enabled, the color is + * multiplied with a color coming from the @ref PhongGL::Color3 / + * @ref PhongGL::Color4 attribute. + * @see @ref PhongGL::setDiffuseColor() + */ + Color4 diffuseColor; + + /** + * @brief Specular color + * + * Default value is @cpp 0xffffff00_rgbaf @ce. + * + * Used only if @ref PhongDrawUniform::lightCount is not zero, ignored + * otherwise. + * @see @ref PhongGL::setSpecularColor() + */ + Color4 specularColor; + + /** + * @brief Normal texture scale + * + * Affects strength of the normal mapping. Default value is @cpp 1.0f @ce, + * meaning the normal texture is not changed in any way; a value of + * @cpp 0.0f @ce disables the normal texture effect altogether. + * + * Used only if @ref PhongGL::Flag::NormalTexture is enabled and + * @ref PhongDrawUniform::lightCount is not zero, ignored otherwise. + * @see @ref PhongGL::setNormalTextureScale() + */ + Float normalTextureScale; + + /** + * @brief Shininess + * + * The larger value, the harder surface (smaller specular highlight). + * Default value is @cpp 80.0f @ce. + * + * Used only if @ref PhongDrawUniform::lightCount is not zero, ignored + * otherwise. + * @see @ref PhongGL::setShininess() + */ + Float shininess; + + /** + * @brief Alpha mask value + * + * Fragments with alpha values smaller than the mask value will be + * discarded. Default value is @cpp 0.5f @ce. + * + * Used only if @ref PhongGL::Flag::AlphaMask is enabled, ignored + * otherwise. + * @see @ref PhongGL::setAlphaMask() + */ + Float alphaMask; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; + #endif +}; + +/** +@brief Light parameters for Phong shaders +@m_since_latest + +Describes light properties referenced by the @ref PhongDrawUniform::lightOffset +and @ref PhongDrawUniform::lightCount range. +@see @ref PhongGL::bindLightBuffer() +*/ +struct PhongLightUniform { + /** @brief Construct with default parameters */ + constexpr explicit PhongLightUniform(DefaultInitT = DefaultInit) noexcept: position{0.0f, 0.0f, 1.0f, 0.0f}, color{1.0f, 1.0f, 1.0f}, specularColor{1.0f, 1.0f, 1.0f}, range{Constants::inf()} {} + /** @brief Construct without initializing the contents */ + explicit PhongLightUniform(NoInitT) noexcept: position{NoInit}, color{NoInit}, specularColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref position field + * @return Reference to self (for method chaining) + */ + PhongLightUniform& setPosition(const Vector4& position) { + this->position = position; + return *this; + } + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + PhongLightUniform& setColor(const Color3& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref specularColor field + * @return Reference to self (for method chaining) + */ + PhongLightUniform& setSpecularColor(const Color3& specularColor) { + this->specularColor = specularColor; + return *this; + } + + /** + * @brief Set the @ref range field + * @return Reference to self (for method chaining) + */ + PhongLightUniform& setRange(Float range) { + this->range = range; + return *this; + } + + /** + * @} + */ + + /** + * @brief Position + * + * Depending on the fourth component, the value is treated as either a + * camera-relative position of a point light, if the fourth component is + * @cpp 1.0f @ce; or a direction *to* a directional light, if the fourth + * component is @cpp 0.0f @ce. Default value is + * @cpp {0.0f, 0.0f, 1.0f, 0.0f} @ce --- a directional "fill" light coming + * from the camera. + * @see @ref PhongGL::setLightPositions() + */ + Vector4 position; + + /** + * @brief Color + * + * Default value is @cpp 0xffffff_rgbf @ce. + * @see @ref PhongGL::setLightColors() + */ + Color3 color; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; /* reserved for cone inner angle */ + #endif + + /** + * @brief Specular color + * + * Usually you'd set this value to the same as @ref color, but it allows + * for greater flexibility such as disabling specular highlights on certain + * lights. Default value is @cpp 0xffffff_rgbf @ce. + * @see @ref PhongGL::setLightColors() + */ + Color3 specularColor; + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; /* reserved for cone outer angle */ + #endif + + /** + * @brief Attenuation range + * + * Default value is @ref Constants::inf(). + * @see @ref PhongGL::setLightRanges() + */ + Float range; + + /* warning: Member __pad2__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; /* reserved for cone direction */ + Int:32; + Int:32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief PhongGL * @m_deprecated_since_latest Use @ref PhongGL instead. */ typedef CORRADE_DEPRECATED("use PhongGL instead") PhongGL Phong; +#endif }} -#else -#error use Magnum/Shaders/PhongGL.h and the PhongGL class instead -#endif #endif diff --git a/src/Magnum/Shaders/Phong.vert b/src/Magnum/Shaders/Phong.vert index 80f48ce04..d4a4c5514 100644 --- a/src/Magnum/Shaders/Phong.vert +++ b/src/Magnum/Shaders/Phong.vert @@ -32,8 +32,13 @@ #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif @@ -86,6 +91,92 @@ uniform highp vec4 lightPositions[LIGHT_COUNT] ; #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +/* Keep in sync with Phong.frag. Can't "outsource" to a common file because + the #extension directive needs to be always before any code. */ +struct DrawUniform { + mediump mat3 normalMatrix; /* actually mat3x4 */ + highp uvec4 materialIdReservedObjectIdLightOffsetLightCount; + #define draw_materialIdReserved materialIdReservedObjectIdLightOffsetLightCount.x + #define draw_objectId materialIdReservedObjectIdLightOffsetLightCount.y + #define draw_lightOffset materialIdReservedObjectIdLightOffsetLightCount.z + #define draw_lightCount materialIdReservedObjectIdLightOffsetLightCount.w +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 0 + #endif +) uniform Projection { + highp mat4 projectionMatrix; +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform Transformation { + highp mat4 transformationMatrices[DRAW_COUNT]; +}; + +#ifdef TEXTURE_TRANSFORMATION +struct TextureTransformationUniform { + highp vec4 rotationScaling; + highp vec4 offsetReservedReserved; + #define textureTransformation_offset offsetReservedReserved.xy +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 3 + #endif +) uniform TextureTransformation { + TextureTransformationUniform textureTransformations[DRAW_COUNT]; +}; +#endif + +#if LIGHT_COUNT +/* Keep in sync with Phong.frag. Can't "outsource" to a common file because + the #extension directive needs to be always before any code. */ +struct LightUniform { + highp vec4 position; + lowp vec3 colorReserved; + #define light_color colorReserved.xyz + lowp vec4 specularColorReserved; + #define light_specularColor specularColorReserved.xyz + lowp vec4 rangeReservedReservedReserved; + #define light_range rangeReservedReservedReserved.x +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 5 + #endif +) uniform Light { + LightUniform lights[LIGHT_COUNT]; +}; +#endif +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -183,6 +274,19 @@ out highp vec3 cameraDirection; #endif void main() { + #ifdef UNIFORM_BUFFERS + highp const mat4 transformationMatrix = transformationMatrices[drawOffset]; + #if LIGHT_COUNT + mediump const mat3 normalMatrix = draws[drawOffset].normalMatrix; + #endif + #ifdef TEXTURE_TRANSFORMATION + mediump const mat3 textureMatrix = mat3(textureTransformations[drawOffset].rotationScaling.xy, 0.0, textureTransformations[drawOffset].rotationScaling.zw, 0.0, textureTransformations[drawOffset].textureTransformation_offset, 1.0); + #endif + #if LIGHT_COUNT + mediump const uint lightOffset = draws[drawOffset].draw_lightOffset; + #endif + #endif + /* Transformed vertex position */ highp vec4 transformedPosition4 = transformationMatrix* #ifdef INSTANCED_TRANSFORMATION @@ -221,8 +325,21 @@ void main() { /* Direction to the light. Directional lights have the last component set to 0, which gets used to ignore the transformed position. */ + #ifndef UNIFORM_BUFFERS for(int i = 0; i < LIGHT_COUNT; ++i) - lightDirections[i] = vec4(lightPositions[i].xyz - transformedPosition*lightPositions[i].w, lightPositions[i].w); + #else + for(uint i = 0u, actualLightCount = min(uint(LIGHT_COUNT), draws[drawOffset].draw_lightCount); i < actualLightCount; ++i) + #endif + { + highp const vec4 lightPosition = + #ifndef UNIFORM_BUFFERS + lightPositions[i] + #else + lights[lightOffset + i].position + #endif + ; + lightDirections[i] = vec4(lightPosition.xyz - transformedPosition*lightPosition.w, lightPosition.w); + } /* Direction to the camera */ cameraDirection = -transformedPosition; diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index 750c6c2ad..87d62e4bd 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -43,6 +43,10 @@ #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { @@ -54,12 +58,49 @@ namespace { SpecularTextureUnit = 2, NormalTextureUnit = 3 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + ProjectionBufferBinding = 0, + TransformationBufferBinding = 1, + DrawBufferBinding = 2, + TextureTransformationBufferBinding = 3, + MaterialBufferBinding = 4, + LightBufferBinding = 5 + }; + #endif } -PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _lightCount{lightCount}, _lightColorsUniform{_lightPositionsUniform + Int(lightCount)}, _lightSpecularColorsUniform{_lightPositionsUniform + 2*Int(lightCount)}, _lightRangesUniform{_lightPositionsUniform + 3*Int(lightCount)} { +PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt materialCount, const UnsignedInt drawCount + #endif +): + _flags{flags}, + _lightCount{lightCount}, + #ifndef MAGNUM_TARGET_GLES2 + _materialCount{materialCount}, + _drawCount{drawCount}, + #endif + _lightColorsUniform{_lightPositionsUniform + Int(lightCount)}, + _lightSpecularColorsUniform{_lightPositionsUniform + 2*Int(lightCount)}, + _lightRangesUniform{_lightPositionsUniform + 3*Int(lightCount)} +{ CORRADE_ASSERT(!(flags & Flag::TextureTransformation) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)), "Shaders::PhongGL: texture transformation enabled but the shader is not textured", ); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || materialCount, + "Shaders::PhongGL: material count can't be zero", ); + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::PhongGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -69,6 +110,11 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} const GL::Context& context = GL::Context::current(); + #ifndef MAGNUM_TARGET_GLES + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || context.isExtensionSupported(), + "Shaders::PhongGL: uniform buffers require" << GL::Extensions::ARB::uniform_buffer_object::string(), ); + #endif + #ifndef MAGNUM_TARGET_GLES const GL::Version version = context.supportedVersion({GL::Version::GL320, GL::Version::GL310, GL::Version::GL300, GL::Version::GL210}); #else @@ -80,7 +126,7 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} #ifndef MAGNUM_TARGET_GLES std::string lightInitializerVertex, lightInitializerFragment; - if(lightCount) { + if(!(flags >= Flag::UniformBuffers) && lightCount) { using namespace Containers::Literals; /* Initializer for the light color / position / range arrays -- we need @@ -140,8 +186,19 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} #endif .addSource(flags & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "") .addSource(flags >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : ""); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define LIGHT_COUNT {}\n", + drawCount, + lightCount)); + } + #endif #ifndef MAGNUM_TARGET_GLES - if(lightCount) vert.addSource(std::move(lightInitializerVertex)); + if(!(flags >= Flag::UniformBuffers) && lightCount) + vert.addSource(std::move(lightInitializerVertex)); #endif vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("Phong.vert")); @@ -156,7 +213,21 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} .addSource(flags & Flag::ObjectId ? "#define OBJECT_ID\n" : "") .addSource(flags >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "") #endif - .addSource(Utility::formatString( + ; + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n" + "#define LIGHT_COUNT {}\n", + drawCount, + materialCount, + lightCount)); + } else + #endif + { + frag.addSource(Utility::formatString( "#define LIGHT_COUNT {}\n" "#define LIGHT_COLORS_LOCATION {}\n" "#define LIGHT_SPECULAR_COLORS_LOCATION {}\n" @@ -165,8 +236,10 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} _lightPositionsUniform + lightCount, _lightPositionsUniform + 2*lightCount, _lightPositionsUniform + 3*lightCount)); + } #ifndef MAGNUM_TARGET_GLES - if(lightCount) frag.addSource(std::move(lightInitializerFragment)); + if(!(flags >= Flag::UniformBuffers) && lightCount) + frag.addSource(std::move(lightInitializerFragment)); #endif frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("Phong.frag")); @@ -215,27 +288,34 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} if(!context.isExtensionSupported(version)) #endif { - _transformationMatrixUniform = uniformLocation("transformationMatrix"); - if(flags & Flag::TextureTransformation) - _textureMatrixUniform = uniformLocation("textureMatrix"); - _projectionMatrixUniform = uniformLocation("projectionMatrix"); - _ambientColorUniform = uniformLocation("ambientColor"); - if(lightCount) { - _normalMatrixUniform = uniformLocation("normalMatrix"); - _diffuseColorUniform = uniformLocation("diffuseColor"); - _specularColorUniform = uniformLocation("specularColor"); - _shininessUniform = uniformLocation("shininess"); - if(flags & Flag::NormalTexture) - _normalTextureScaleUniform = uniformLocation("normalTextureScale"); - _lightPositionsUniform = uniformLocation("lightPositions"); - _lightColorsUniform = uniformLocation("lightColors"); - _lightSpecularColorsUniform = uniformLocation("lightSpecularColors"); - _lightRangesUniform = uniformLocation("lightRanges"); - } - if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); #ifndef MAGNUM_TARGET_GLES2 - if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + if(flags >= Flag::UniformBuffers) { + _drawOffsetUniform = uniformLocation("drawOffset"); + } else #endif + { + _transformationMatrixUniform = uniformLocation("transformationMatrix"); + if(flags & Flag::TextureTransformation) + _textureMatrixUniform = uniformLocation("textureMatrix"); + _projectionMatrixUniform = uniformLocation("projectionMatrix"); + _ambientColorUniform = uniformLocation("ambientColor"); + if(lightCount) { + _normalMatrixUniform = uniformLocation("normalMatrix"); + _diffuseColorUniform = uniformLocation("diffuseColor"); + _specularColorUniform = uniformLocation("specularColor"); + _shininessUniform = uniformLocation("shininess"); + if(flags & Flag::NormalTexture) + _normalTextureScaleUniform = uniformLocation("normalTextureScale"); + _lightPositionsUniform = uniformLocation("lightPositions"); + _lightColorsUniform = uniformLocation("lightColors"); + _lightSpecularColorsUniform = uniformLocation("lightSpecularColors"); + _lightRangesUniform = uniformLocation("lightRanges"); + } + if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags & Flag::ObjectId) _objectIdUniform = uniformLocation("objectId"); + #endif + } } #ifndef MAGNUM_TARGET_GLES @@ -248,57 +328,100 @@ PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): _flags{flags} if(flags & Flag::SpecularTexture) setUniform(uniformLocation("specularTexture"), SpecularTextureUnit); if(flags & Flag::NormalTexture) setUniform(uniformLocation("normalTexture"), NormalTextureUnit); } + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("Projection"), ProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Transformation"), TransformationBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"), MaterialBufferBinding); + if(flags & Flag::TextureTransformation) + setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + if(lightCount) + setUniformBlockBinding(uniformBlockIndex("Light"), LightBufferBinding); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - /* Default to fully opaque white so we can see the textures */ - if(flags & Flag::AmbientTexture) setAmbientColor(Magnum::Color4{1.0f}); - else setAmbientColor(Magnum::Color4{0.0f}); - setTransformationMatrix(Matrix4{Math::IdentityInit}); - setProjectionMatrix(Matrix4{Math::IdentityInit}); - if(lightCount) { - setDiffuseColor(Magnum::Color4{1.0f}); - setSpecularColor(Magnum::Color4{1.0f, 0.0f}); - setShininess(80.0f); - if(flags & Flag::NormalTexture) - setNormalTextureScale(1.0f); - setLightPositions(Containers::Array{DirectInit, lightCount, Vector4{0.0f, 0.0f, 1.0f, 0.0f}}); - Containers::Array colors{DirectInit, lightCount, Magnum::Color3{1.0f}}; - setLightColors(colors); - setLightSpecularColors(colors); - setLightRanges(Containers::Array{DirectInit, lightCount, Constants::inf()}); - /* Light position is zero by default */ - setNormalMatrix(Matrix3x3{Math::IdentityInit}); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + /* Default to fully opaque white so we can see the textures */ + if(flags & Flag::AmbientTexture) setAmbientColor(Magnum::Color4{1.0f}); + else setAmbientColor(Magnum::Color4{0.0f}); + setTransformationMatrix(Matrix4{Math::IdentityInit}); + setProjectionMatrix(Matrix4{Math::IdentityInit}); + if(lightCount) { + setDiffuseColor(Magnum::Color4{1.0f}); + setSpecularColor(Magnum::Color4{1.0f, 0.0f}); + setShininess(80.0f); + if(flags & Flag::NormalTexture) + setNormalTextureScale(1.0f); + setLightPositions(Containers::Array{DirectInit, lightCount, Vector4{0.0f, 0.0f, 1.0f, 0.0f}}); + Containers::Array colors{DirectInit, lightCount, Magnum::Color3{1.0f}}; + setLightColors(colors); + setLightSpecularColors(colors); + setLightRanges(Containers::Array{DirectInit, lightCount, Constants::inf()}); + /* Light position is zero by default */ + setNormalMatrix(Matrix3x3{Math::IdentityInit}); + } + if(flags & Flag::TextureTransformation) + setTextureMatrix(Matrix3{Math::IdentityInit}); + if(flags & Flag::AlphaMask) setAlphaMask(0.5f); + /* Object ID is zero by default */ } - if(flags & Flag::TextureTransformation) - setTextureMatrix(Matrix3{Math::IdentityInit}); - if(flags & Flag::AlphaMask) setAlphaMask(0.5f); - /* Object ID is zero by default */ #endif } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL::PhongGL(const Flags flags, const UnsignedInt lightCount): PhongGL{flags, lightCount, 1, 1} {} +#endif + PhongGL& PhongGL::setAmbientColor(const Magnum::Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setAmbientColor(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_ambientColorUniform, color); return *this; } PhongGL& PhongGL::setDiffuseColor(const Magnum::Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setDiffuseColor(): the shader was created with uniform buffers enabled", *this); + #endif if(_lightCount) setUniform(_diffuseColorUniform, color); return *this; } PhongGL& PhongGL::setSpecularColor(const Magnum::Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setSpecularColor(): the shader was created with uniform buffers enabled", *this); + #endif if(_lightCount) setUniform(_specularColorUniform, color); return *this; } PhongGL& PhongGL::setShininess(Float shininess) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setShininess(): the shader was created with uniform buffers enabled", *this); + #endif if(_lightCount) setUniform(_shininessUniform, shininess); return *this; } PhongGL& PhongGL::setNormalTextureScale(const Float scale) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setNormalTextureScale(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::NormalTexture, "Shaders::PhongGL::setNormalTextureScale(): the shader was not created with normal texture enabled", *this); if(_lightCount) setUniform(_normalTextureScaleUniform, scale); @@ -306,6 +429,10 @@ PhongGL& PhongGL::setNormalTextureScale(const Float scale) { } PhongGL& PhongGL::setAlphaMask(Float mask) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setAlphaMask(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::AlphaMask, "Shaders::PhongGL::setAlphaMask(): the shader was not created with alpha mask enabled", *this); setUniform(_alphaMaskUniform, mask); @@ -314,6 +441,8 @@ PhongGL& PhongGL::setAlphaMask(Float mask) { #ifndef MAGNUM_TARGET_GLES2 PhongGL& PhongGL::setObjectId(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setObjectId(): the shader was created with uniform buffers enabled", *this); CORRADE_ASSERT(_flags & Flag::ObjectId, "Shaders::PhongGL::setObjectId(): the shader was not created with object ID enabled", *this); setUniform(_objectIdUniform, id); @@ -322,21 +451,37 @@ PhongGL& PhongGL::setObjectId(UnsignedInt id) { #endif PhongGL& PhongGL::setTransformationMatrix(const Matrix4& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setTransformationMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationMatrixUniform, matrix); return *this; } PhongGL& PhongGL::setNormalMatrix(const Matrix3x3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setNormalMatrix(): the shader was created with uniform buffers enabled", *this); + #endif if(_lightCount) setUniform(_normalMatrixUniform, matrix); return *this; } PhongGL& PhongGL::setProjectionMatrix(const Matrix4& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_projectionMatrixUniform, matrix); return *this; } PhongGL& PhongGL::setTextureMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setTextureMatrix(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::TextureTransformation, "Shaders::PhongGL::setTextureMatrix(): the shader was not created with texture transformation enabled", *this); setUniform(_textureMatrixUniform, matrix); @@ -344,6 +489,10 @@ PhongGL& PhongGL::setTextureMatrix(const Matrix3& matrix) { } PhongGL& PhongGL::setLightPositions(const Containers::ArrayView positions) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightPositions(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_lightCount == positions.size(), "Shaders::PhongGL::setLightPositions(): expected" << _lightCount << "items but got" << positions.size(), *this); if(_lightCount) setUniform(_lightPositionsUniform, positions); @@ -373,6 +522,10 @@ PhongGL& PhongGL::setLightPositions(const std::initializer_list positio #endif PhongGL& PhongGL::setLightPosition(const UnsignedInt id, const Vector4& position) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightPosition(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(id < _lightCount, "Shaders::PhongGL::setLightPosition(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); setUniform(_lightPositionsUniform + id, position); @@ -391,6 +544,10 @@ PhongGL& PhongGL::setLightPosition(const Vector3& position) { #endif PhongGL& PhongGL::setLightColors(const Containers::ArrayView colors) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightColors(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_lightCount == colors.size(), "Shaders::PhongGL::setLightColors(): expected" << _lightCount << "items but got" << colors.size(), *this); if(_lightCount) setUniform(_lightColorsUniform, colors); @@ -418,6 +575,10 @@ PhongGL& PhongGL::setLightColors(const std::initializer_list col } PhongGL& PhongGL::setLightColor(const UnsignedInt id, const Magnum::Color3& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightColor(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(id < _lightCount, "Shaders::PhongGL::setLightColor(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); setUniform(_lightColorsUniform + id, color); @@ -436,6 +597,10 @@ PhongGL& PhongGL::setLightColor(const Magnum::Color4& color) { #endif PhongGL& PhongGL::setLightSpecularColors(const Containers::ArrayView colors) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightSpecularColors(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_lightCount == colors.size(), "Shaders::PhongGL::setLightSpecularColors(): expected" << _lightCount << "items but got" << colors.size(), *this); if(_lightCount) setUniform(_lightSpecularColorsUniform, colors); @@ -447,6 +612,10 @@ PhongGL& PhongGL::setLightSpecularColors(const std::initializer_list= Flag::UniformBuffers), + "Shaders::PhongGL::setLightSpecularColor(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(id < _lightCount, "Shaders::PhongGL::setLightSpecularColor(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); setUniform(_lightSpecularColorsUniform + id, color); @@ -454,6 +623,10 @@ PhongGL& PhongGL::setLightSpecularColor(const UnsignedInt id, const Magnum::Colo } PhongGL& PhongGL::setLightRanges(const Containers::ArrayView ranges) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightRanges(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_lightCount == ranges.size(), "Shaders::PhongGL::setLightRanges(): expected" << _lightCount << "items but got" << ranges.size(), *this); if(_lightCount) setUniform(_lightRangesUniform, ranges); @@ -465,12 +638,115 @@ PhongGL& PhongGL::setLightRanges(const std::initializer_list ranges) { } PhongGL& PhongGL::setLightRange(const UnsignedInt id, const Float range) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::PhongGL::setLightRange(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(id < _lightCount, "Shaders::PhongGL::setLightRange(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); setUniform(_lightRangesUniform + id, range); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +PhongGL& PhongGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::PhongGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + setUniform(_drawOffsetUniform, offset); + return *this; +} + +PhongGL& PhongGL::bindProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, ProjectionBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, ProjectionBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindTextureTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindTextureTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} + +PhongGL& PhongGL::bindLightBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, LightBufferBinding); + return *this; +} + +PhongGL& PhongGL::bindLightBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, LightBufferBinding, offset, size); + return *this; +} +#endif + PhongGL& PhongGL::bindAmbientTexture(GL::Texture2D& texture) { CORRADE_ASSERT(_flags & Flag::AmbientTexture, "Shaders::PhongGL::bindAmbientTexture(): the shader was not created with ambient texture enabled", *this); @@ -526,6 +802,9 @@ Debug& operator<<(Debug& debug, const PhongGL::Flag value) { #endif _c(InstancedTransformation) _c(InstancedTextureOffset) + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -548,7 +827,11 @@ Debug& operator<<(Debug& debug, const PhongGL::Flags value) { PhongGL::Flag::InstancedObjectId, /* Superset of ObjectId */ PhongGL::Flag::ObjectId, #endif - PhongGL::Flag::InstancedTransformation}); + PhongGL::Flag::InstancedTransformation, + #ifndef MAGNUM_TARGET_GLES2 + PhongGL::Flag::UniformBuffers + #endif + }); } }} diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 66547b5cd..cb67a8a02 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -509,9 +509,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { /** * Instanced object ID. Retrieves a per-instance / per-vertex * object ID from the @ref ObjectId attribute, outputting a sum of - * the per-vertex ID and ID coming from @ref setObjectId(). - * Implicitly enables @ref Flag::ObjectId. See - * @ref Shaders-PhongGL-object-id for more information. + * the per-vertex ID and ID coming from @ref setObjectId() or + * @ref PhongDrawUniform::objectId. Implicitly enables + * @ref Flag::ObjectId. See @ref Shaders-PhongGL-object-id for more + * information. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL @@ -526,9 +527,11 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * transformation and normal matrix from the * @ref TransformationMatrix / @ref NormalMatrix attributes and * uses them together with matrices coming from - * @ref setTransformationMatrix() and @ref setNormalMatrix() (first - * the per-instance, then the uniform matrix). See - * @ref Shaders-PhongGL-instancing for more information. + * @ref setTransformationMatrix() and @ref setNormalMatrix() or + * @ref TransformationUniform3D::transformationMatrix and + * @ref PhongDrawUniform::normalMatrix (first the per-instance, + * then the uniform matrix). See @ref Shaders-PhongGL-instancing + * for more information. * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} * @requires_gles30 Extension @gl_extension{ANGLE,instanced_arrays}, * @gl_extension{EXT,instanced_arrays} or @@ -542,7 +545,9 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { /** * Instanced texture offset. Retrieves a per-instance offset vector * from the @ref TextureOffset attribute and uses it together with - * the matrix coming from @ref setTextureMatrix() (first the + * the matrix coming from @ref setTextureMatrix() or + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset (first the * per-instance vector, then the uniform matrix). Instanced texture * scaling and rotation is not supported at the moment, you can * specify that only via the uniform @ref setTextureMatrix(). @@ -556,7 +561,24 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * in WebGL 1.0. * @m_since{2020,06} */ - InstancedTextureOffset = (1 << 10)|TextureTransformation + InstancedTextureOffset = (1 << 10)|TextureTransformation, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer(), + * @ref bindDrawBuffer(), @ref bindTextureTransformationBuffer(), + * @ref bindMaterialBuffer() and @ref bindLightBuffer() instead of + * direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 12 + #endif }; /** @@ -570,9 +592,54 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @brief Constructor * @param flags Flags * @param lightCount Count of light sources + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref PhongGL(Flags, UnsignedInt, UnsignedInt, UnsignedInt) with + * @p materialCount and @p drawCount set to @cpp 1 @ce. */ explicit PhongGL(Flags flags = {}, UnsignedInt lightCount = 1); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param lightCount Size of a @ref PhongLightUniform buffer bound + * with @ref bindLightBuffer() + * @param materialCount Size of a @ref PhongMaterialUniform buffer + * bound with @ref bindMaterialBuffer() + * @param drawCount Size of a @ref ProjectionUniform3D / + * @ref TransformationUniform3D / @ref PhongDrawUniform / + * @ref TextureTransformationUniform buffer bound with + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer(), + * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p lightCount, + * @p materialCount and @p drawCount describe the uniform buffer sizes + * as these are required to have a statically defined size. The draw + * offset is then set via @ref setDrawOffset() and the per-draw + * materials and lights are specified via + * @ref PhongDrawUniform::materialId, + * @ref PhongDrawUniform::lightOffset and + * @ref PhongDrawUniform::lightCount. + * + * If @p flags don't contain @ref Flag::UniformBuffers, + * @p materialCount and @p drawCount is ignored and the constructor + * behaves the same as @ref PhongGL(Flags, UnsignedInt). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit PhongGL(Flags flags, UnsignedInt lightCount, UnsignedInt materialCount, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -602,11 +669,46 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { /** @brief Flags */ Flags flags() const { return _flags; } - /** @brief Light count */ + /** + * @brief Light count + * + * If @ref Flag::UniformBuffers is set, this is the statically defined + * size of the @ref PhongLightUniform uniform buffer. + * @see @ref bindLightBuffer() + */ UnsignedInt lightCount() const { return _lightCount; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Material count + * @m_since_latest + * + * Statically defined size of the @ref PhongMaterialUniform uniform + * buffer. Has use only if @ref Flag::UniformBuffers is set. + * @see @ref bindMaterialBuffer() + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the @ref ProjectionUniform3D, + * @ref TransformationUniform3D, @ref PhongDrawUniform and + * @ref TextureTransformationUniform uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -618,6 +720,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * ambient texture, otherwise default value is @cpp 0x00000000_rgbaf @ce. * If @ref Flag::VertexColor is set, the color is multiplied with a * color coming from the @ref Color3 / @ref Color4 attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::ambientColor and call + * @ref bindMaterialBuffer() instead. * @see @ref bindAmbientTexture(), @ref Shaders-PhongGL-lights-ambient */ PhongGL& setAmbientColor(const Magnum::Color4& color); @@ -632,6 +738,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * as diffuse color doesn't contribute to the output in that case. * If @ref Flag::VertexColor is set, the color is multiplied with a * color coming from the @ref Color3 / @ref Color4 attribute. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::diffuseColor and call + * @ref bindMaterialBuffer() instead. * @see @ref bindDiffuseTexture() */ PhongGL& setDiffuseColor(const Magnum::Color4& color); @@ -649,6 +759,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * Expects that the shader was created with @ref Flag::NormalTexture * enabled. If @ref lightCount() is zero, this function is a no-op, as * normals don't contribute to the output in that case. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::normalTextureScale and call + * @ref bindDrawBuffer() instead. * @see @ref Shaders-PhongGL-normal-mapping, @ref bindNormalTexture(), * @ref Trade::MaterialAttribute::NormalTextureScale */ @@ -664,6 +778,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * the specular color to @cpp 0x00000000_rgbaf @ce. If * @ref lightCount() is zero, this function is a no-op, as specular * color doesn't contribute to the output in that case. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::specularColor and call + * @ref bindMaterialBuffer() instead. * @see @ref bindSpecularTexture() */ PhongGL& setSpecularColor(const Magnum::Color4& color); @@ -676,6 +794,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * Initial value is @cpp 80.0f @ce. If @ref lightCount() is zero, this * function is a no-op, as specular color doesn't contribute to the * output in that case. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::shininess and call + * @ref bindMaterialBuffer() instead. */ PhongGL& setShininess(Float shininess); @@ -690,6 +812,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * This corresponds to @m_class{m-doc-external} [glAlphaFunc()](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glAlphaFunc.xml) * in classic OpenGL. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongMaterialUniform::alphaMask and call + * @ref bindMaterialBuffer() instead. * @m_keywords{glAlphaFunc()} */ PhongGL& setAlphaMask(Float mask); @@ -704,6 +830,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @ref Shaders-PhongGL-object-id for more information. 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 PhongDrawUniform::objectId and call @ref bindDrawBuffer() + * instead. * @requires_gl30 Extension @gl_extension{EXT,gpu_shader4} * @requires_gles30 Object ID output requires integer support in * shaders, which is not available in OpenGL ES 2.0 or WebGL 1.0. @@ -720,6 +850,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @ref Flag::InstancedTransformation is set, the per-instance * transformation coming from the @ref TransformationMatrix attribute * is applied first, before this one. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationUniform3D::transformationMatrix and call + * @ref bindTransformationBuffer() instead. */ PhongGL& setTransformationMatrix(const Matrix4& matrix); @@ -735,6 +869,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * that case. If @ref Flag::InstancedTransformation is set, the * per-instance normal matrix coming from the @ref NormalMatrix * attribute is applied first, before this one. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongDrawUniform::normalMatrix and call + * @ref bindDrawBuffer() instead. * @see @ref Math::Matrix4::normalMatrix() */ PhongGL& setNormalMatrix(const Matrix3x3& matrix); @@ -746,6 +884,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * Initial value is an identity matrix (i.e., an orthographic * projection of the default @f$ [ -\boldsymbol{1} ; \boldsymbol{1} ] @f$ * cube). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref ProjectionUniform3D::projectionMatrix and call + * @ref bindProjectionBuffer() instead. */ PhongGL& setProjectionMatrix(const Matrix4& matrix); @@ -759,6 +901,11 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * identity matrix. If @ref Flag::InstancedTextureOffset is set, the * per-instance offset coming from the @ref TextureOffset atttribute is * applied first, before this matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset and call + * @ref bindTextureTransformationBuffer() instead. */ PhongGL& setTextureMatrix(const Matrix3& matrix); @@ -774,6 +921,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @p positions array is the same as @ref lightCount(). Initial values * are @cpp {0.0f, 0.0f, 1.0f, 0.0f} @ce --- a directional "fill" light * coming from the camera. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::position and call + * @ref bindLightBuffer() instead * @see @ref Shaders-PhongGL-lights, @ref setLightPosition() */ PhongGL& setLightPositions(Containers::ArrayView positions); @@ -813,6 +964,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * position. If updating more than one light, prefer the batch function * instead to reduce the count of GL API calls. Expects that @p id is * less than @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::position and call @ref bindLightBuffer() + * instead. */ PhongGL& setLightPosition(UnsignedInt id, const Vector4& position); @@ -844,6 +999,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * Initial values are @cpp 0xffffff_rgbf @ce. Expects that the size * of the @p colors array is the same as @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::color and call @ref bindLightBuffer() + * instead. * @see @ref Shaders-PhongGL-lights, @ref setLightColor() */ PhongGL& setLightColors(Containers::ArrayView colors); @@ -879,6 +1038,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * updating more than one light, prefer the batch function instead to * reduce the count of GL API calls. Expects that @p id is less than * @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::color and call @ref bindLightBuffer() + * instead. */ PhongGL& setLightColor(UnsignedInt id, const Magnum::Color3& color); @@ -910,6 +1073,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * highlights on certain lights. Initial values are * @cpp 0xffffff_rgbf @ce. Expects that the size of the @p colors array * is the same as @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::specularColor and call @ref bindLightBuffer() + * instead. * @see @ref Shaders-PhongGL-lights, @ref setLightColor() */ PhongGL& setLightSpecularColors(Containers::ArrayView colors); @@ -929,6 +1096,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * color. If updating more than one light, prefer the batch function * instead to reduce the count of GL API calls. Expects that @p id is * less than @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::specularColor and call + * @ref bindLightBuffer() instead. */ PhongGL& setLightSpecularColor(UnsignedInt id, const Magnum::Color3& color); @@ -939,6 +1110,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * * Initial values are @ref Constants::inf(). Expects that the size of * the @p ranges array is the same as @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::range and call @ref bindLightBuffer() + * instead. * @see @ref Shaders-PhongGL-lights, @ref setLightRange() */ PhongGL& setLightRanges(Containers::ArrayView ranges); @@ -958,6 +1133,10 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * updating more than one light, prefer the batch function instead to * reduce the count of GL API calls. Expects that @p id is less than * @ref lightCount(). + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref PhongLightUniform::range and call @ref bindLightBuffer() + * instead. */ PhongGL& setLightRange(UnsignedInt id, Float range); @@ -965,6 +1144,158 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationUniform3D, + * @ref PhongDrawUniform and @ref TextureTransformationUniform buffers + * bound with @ref bindTransformationBuffer(), @ref bindDrawBuffer() + * and @ref bindTextureTransformationBuffer() should be used for + * current draw. Expects that @ref Flag::UniformBuffers is set and + * @p offset is less than @ref drawCount(). Initial value is @cpp 0 @ce. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain at least one instance of + * @ref ProjectionUniform3D. At the very least you need to call also + * @ref bindTransformationBuffer(), @ref bindDrawBuffer() and + * @ref bindMaterialBuffer(), usually @ref bindLightBuffer() as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationUniform3D. At the very least you need to call + * also @ref bindProjectionBuffer(), @ref bindDrawBuffer() and + * @ref bindMaterialBuffer(), usually @ref bindLightBuffer() as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref PhongDrawUniform. At the very least you need to call also + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer() and + * @ref bindMaterialBuffer(), usually @ref bindLightBuffer() as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a texture transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that both @ref Flag::UniformBuffers and + * @ref Flag::TextureTransformation is set. The buffer is expected to + * contain @ref drawCount() instances of + * @ref TextureTransformationUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindTextureTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a material uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref PhongMaterialUniform. At the very least you need to call also + * @ref bindProjectionBuffer(), @ref bindTransformationBuffer() and + * @ref bindDrawBuffer(), usually @ref bindLightBuffer() as well. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindMaterialBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a light uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref lightCount() instances of + * @ref PhongLightUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + PhongGL& bindLightBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + PhongGL& bindLightBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ @@ -1050,6 +1381,9 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { Flags _flags; UnsignedInt _lightCount{}; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _materialCount{}, _drawCount{}; + #endif Int _transformationMatrixUniform{0}, _projectionMatrixUniform{1}, _normalMatrixUniform{2}, @@ -1067,6 +1401,11 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { _lightColorsUniform, /* 11 + lightCount, set in the constructor */ _lightSpecularColorsUniform, /* 11 + 2*lightCount */ _lightRangesUniform; /* 11 + 3*lightCount */ + #ifndef MAGNUM_TARGET_GLES2 + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; + #endif }; /** @debugoperatorclassenum{PhongGL,PhongGL::Flag} */ diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index cbdaa1e40..c99df3824 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -23,6 +23,22 @@ # DEALINGS IN THE SOFTWARE. # +corrade_add_test(ShadersDistanceFieldVectorTest DistanceFieldVectorTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersFlatTest FlatTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersGenericTest GenericTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersMeshVisualizerTest MeshVisualizerTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersPhongTest PhongTest.cpp LIBRARIES MagnumShaders) +corrade_add_test(ShadersVectorTest VectorTest.cpp LIBRARIES MagnumShaders) + +set_target_properties( + ShadersDistanceFieldVectorTest + ShadersFlatTest + ShadersGenericTest + ShadersMeshVisualizerTest + ShadersPhongTest + ShadersVectorTest + PROPERTIES FOLDER "Magnum/Shaders/Test") + # There's an underscore between GL and Test to disambiguate from GLTest, which # is a common suffix used to mark tests that need a GL context. Ugly, I know. corrade_add_test(ShadersDistanceFieldVectorGL_Test DistanceFieldVectorGL_Test.cpp LIBRARIES MagnumShaders) @@ -94,7 +110,9 @@ if(BUILD_GL_TESTS) VectorTestFiles/smooth0.2-2D.tga VectorTestFiles/smooth0.2-3D.tga VectorTestFiles/outline2D.tga - VectorTestFiles/outline3D.tga) + VectorTestFiles/outline3D.tga + VectorTestFiles/multidraw2D-distancefield.tga + VectorTestFiles/multidraw3D-distancefield.tga) target_include_directories(ShadersDistanceFieldVectorGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -133,7 +151,11 @@ if(BUILD_GL_TESTS) FlatTestFiles/textured2D-alpha-mask0.5.tga FlatTestFiles/textured3D-alpha-mask0.5.tga FlatTestFiles/vertexColor2D.tga - FlatTestFiles/vertexColor3D.tga) + FlatTestFiles/vertexColor3D.tga + FlatTestFiles/multidraw2D.tga + FlatTestFiles/multidraw3D.tga + FlatTestFiles/multidraw-textured2D.tga + FlatTestFiles/multidraw-textured3D.tga) target_include_directories(ShadersFlatGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -189,7 +211,14 @@ if(BUILD_GL_TESTS) MeshVisualizerTestFiles/wireframe-wide2D.tga MeshVisualizerTestFiles/wireframe-wide3D.tga MeshVisualizerTestFiles/wireframe2D.tga - MeshVisualizerTestFiles/wireframe3D.tga) + MeshVisualizerTestFiles/wireframe3D.tga + MeshVisualizerTestFiles/multidraw-wireframe2D.tga + MeshVisualizerTestFiles/multidraw-wireframe3D.tga + MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga + MeshVisualizerTestFiles/multidraw-wireframe-nogeo2D.tga + MeshVisualizerTestFiles/multidraw-wireframe-nogeo3D.tga + MeshVisualizerTestFiles/multidraw-vertexid2D.tga + MeshVisualizerTestFiles/multidraw-vertexid3D.tga) target_include_directories(ShadersMeshVisualizerGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -251,6 +280,8 @@ if(BUILD_GL_TESTS) PhongTestFiles/light-point-range1.5.tga PhongTestFiles/light-point-specular-color.tga PhongTestFiles/light-point.tga + PhongTestFiles/multidraw.tga + PhongTestFiles/multidraw-textured.tga # For zero lights test (equivalency to Flat3D) FlatTestFiles/textured3D-alpha-mask0.5.tga) @@ -280,7 +311,9 @@ if(BUILD_GL_TESTS) VectorTestFiles/defaults.tga VectorTestFiles/vector2D.tga - VectorTestFiles/vector3D.tga) + VectorTestFiles/vector3D.tga + VectorTestFiles/multidraw2D.tga + VectorTestFiles/multidraw3D.tga) target_include_directories(ShadersVectorGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) @@ -306,7 +339,9 @@ if(BUILD_GL_TESTS) FlatTestFiles/defaults.tga VertexColorTestFiles/vertexColor2D.tga - VertexColorTestFiles/vertexColor3D.tga) + VertexColorTestFiles/vertexColor3D.tga + VertexColorTestFiles/multidraw2D.tga + VertexColorTestFiles/multidraw3D.tga) target_include_directories(ShadersVertexColorGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) if(BUILD_PLUGINS_STATIC) if(WITH_ANYIMAGEIMPORTER) diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index ba84c7d4a..fd92030f2 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -52,6 +52,22 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Circle.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Primitives/UVSphere.h" +#include "Magnum/Shaders/DistanceFieldVector.h" +#include "Magnum/Shaders/Generic.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -60,17 +76,43 @@ struct DistanceFieldVectorGLTest: GL::OpenGLTester { explicit DistanceFieldVectorGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffers(); + #endif + template void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructMoveUniformBuffers(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffersInvalid(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + #endif template void setTextureMatrixNotEnabled(); + #ifndef MAGNUM_TARGET_GLES2 + template void bindTextureTransformBufferNotEnabled(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - void renderDefaults2D(); - void renderDefaults3D(); - void render2D(); - void render3D(); + template void renderDefaults2D(); + template void renderDefaults3D(); + template void render2D(); + template void render3D(); + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -105,6 +147,31 @@ constexpr struct { {"texture transformation", DistanceFieldVectorGL2D::Flag::TextureTransformation} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + DistanceFieldVectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1, 1}, + {"", DistanceFieldVectorGL2D::Flag::UniformBuffers, 1, 1}, + {"texture transformation", DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation, 1, 1}, + {"multiple materials, draws", DistanceFieldVectorGL2D::Flag::UniformBuffers, 64, 128}, +}; + +constexpr struct { + const char* name; + DistanceFieldVectorGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData[]{ + {"zero draws", DistanceFieldVectorGL2D::Flag::UniformBuffers, 1, 0, + "draw count can't be zero"}, + {"zero materials", DistanceFieldVectorGL2D::Flag::UniformBuffers, 0, 1, + "material count can't be zero"}, +}; +#endif + const struct { const char* name; DistanceFieldVectorGL2D::Flags flags; @@ -127,30 +194,106 @@ const struct { "outline2D.tga", "outline3D.tga", false} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + 1, 1, 16, 0.0f, 0.0f}, + {"draw offset", "multidraw2D-distancefield.tga", "multidraw3D-distancefield.tga", + 2, 3, 1, 0.0f, 0.0f}, +}; +#endif + DistanceFieldVectorGLTest::DistanceFieldVectorGLTest() { addInstancedTests({ &DistanceFieldVectorGLTest::construct<2>, &DistanceFieldVectorGLTest::construct<3>}, Containers::arraySize(ConstructData)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &DistanceFieldVectorGLTest::constructUniformBuffers<2>, + &DistanceFieldVectorGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + addTests({ &DistanceFieldVectorGLTest::constructMove<2>, &DistanceFieldVectorGLTest::constructMove<3>, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::constructMoveUniformBuffers<2>, + &DistanceFieldVectorGLTest::constructMoveUniformBuffers<3>, + #endif + }); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &DistanceFieldVectorGLTest::constructUniformBuffersInvalid<2>, + &DistanceFieldVectorGLTest::constructUniformBuffersInvalid<3>}, + Containers::arraySize(ConstructUniformBuffersInvalidData)); + #endif + + addTests({ + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::setUniformUniformBuffersEnabled<2>, + &DistanceFieldVectorGLTest::setUniformUniformBuffersEnabled<3>, + &DistanceFieldVectorGLTest::bindBufferUniformBuffersNotEnabled<2>, + &DistanceFieldVectorGLTest::bindBufferUniformBuffersNotEnabled<3>, + #endif &DistanceFieldVectorGLTest::setTextureMatrixNotEnabled<2>, - &DistanceFieldVectorGLTest::setTextureMatrixNotEnabled<3>}); + &DistanceFieldVectorGLTest::setTextureMatrixNotEnabled<3>, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled<2>, + &DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled<3>, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::setWrongDrawOffset<2>, + &DistanceFieldVectorGLTest::setWrongDrawOffset<3> + #endif + }); - addTests({&DistanceFieldVectorGLTest::renderDefaults2D, - &DistanceFieldVectorGLTest::renderDefaults3D}, + addTests({ + &DistanceFieldVectorGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::renderDefaults2D, + #endif + &DistanceFieldVectorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::renderDefaults3D, + #endif + }, &DistanceFieldVectorGLTest::renderSetup, &DistanceFieldVectorGLTest::renderTeardown); - addInstancedTests({&DistanceFieldVectorGLTest::render2D, - &DistanceFieldVectorGLTest::render3D}, + addInstancedTests({ + &DistanceFieldVectorGLTest::render2D, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::render2D, + #endif + &DistanceFieldVectorGLTest::render3D, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldVectorGLTest::render3D, + #endif + }, Containers::arraySize(RenderData), &DistanceFieldVectorGLTest::renderSetup, &DistanceFieldVectorGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&DistanceFieldVectorGLTest::renderMulti2D, + &DistanceFieldVectorGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), + &DistanceFieldVectorGLTest::renderSetup, + &DistanceFieldVectorGLTest::renderTeardown); + #endif + /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -194,6 +337,34 @@ template void DistanceFieldVectorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::constructUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & DistanceFieldVectorGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + DistanceFieldVectorGL shader{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.materialCount(), data.materialCount); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void DistanceFieldVectorGLTest::constructMove() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -215,6 +386,128 @@ template void DistanceFieldVectorGLTest::constructMove() CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + DistanceFieldVectorGL a{DistanceFieldVectorGL::Flag::UniformBuffers, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + DistanceFieldVectorGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), DistanceFieldVectorGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + DistanceFieldVectorGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), DistanceFieldVectorGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::constructUniformBuffersInvalid() { + auto&& data = ConstructUniformBuffersInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + DistanceFieldVectorGL{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::DistanceFieldVectorGL: {}\n", data.message)); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::setUniformUniformBuffersEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + DistanceFieldVectorGL shader{DistanceFieldVectorGL::Flag::UniformBuffers}; + shader.setTransformationProjectionMatrix({}) + .setTextureMatrix({}) + .setColor({}) + .setOutlineColor({}) + .setOutlineRange({}, {}) + .setSmoothness({}); + CORRADE_COMPARE(out.str(), + "Shaders::DistanceFieldVectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setColor(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setOutlineColor(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setOutlineRange(): the shader was created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setSmoothness(): the shader was created with uniform buffers enabled\n"); +} + +template void DistanceFieldVectorGLTest::bindBufferUniformBuffersNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + DistanceFieldVectorGL shader; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::DistanceFieldVectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::DistanceFieldVectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} +#endif + template void DistanceFieldVectorGLTest::setTextureMatrixNotEnabled() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -232,6 +525,54 @@ template void DistanceFieldVectorGLTest::setTextureMatri "Shaders::DistanceFieldVectorGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); } +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::bindTextureTransformBufferNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer{GL::Buffer::TargetHint::Uniform}; + DistanceFieldVectorGL shader{DistanceFieldVectorGL::Flag::UniformBuffers}; + shader.bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16); + CORRADE_COMPARE(out.str(), + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n" + "Shaders::DistanceFieldVectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void DistanceFieldVectorGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + DistanceFieldVectorGL{DistanceFieldVectorGL::Flag::UniformBuffers, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::DistanceFieldVectorGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void DistanceFieldVectorGLTest::renderSetup() { @@ -267,7 +608,18 @@ constexpr GL::TextureFormat TextureFormatR = #endif ; -void DistanceFieldVectorGLTest::renderDefaults2D() { +template void DistanceFieldVectorGLTest::renderDefaults2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == DistanceFieldVectorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -293,9 +645,30 @@ void DistanceFieldVectorGLTest::renderDefaults2D() { .setSubImage(0, {}, *image); #endif - DistanceFieldVectorGL2D{} - .bindVectorTexture(texture) - .draw(square); + DistanceFieldVectorGL2D shader{flag}; + shader.bindVectorTexture(texture); + + if(flag == DistanceFieldVectorGL2D::Flag{}) { + shader.draw(square); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == DistanceFieldVectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorMaterialUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(square); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -323,7 +696,18 @@ void DistanceFieldVectorGLTest::renderDefaults2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void DistanceFieldVectorGLTest::renderDefaults3D() { +template void DistanceFieldVectorGLTest::renderDefaults3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == DistanceFieldVectorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -349,9 +733,30 @@ void DistanceFieldVectorGLTest::renderDefaults3D() { .setSubImage(0, {}, *image); #endif - DistanceFieldVectorGL3D{} - .bindVectorTexture(texture) - .draw(plane); + DistanceFieldVectorGL3D shader{flag}; + shader.bindVectorTexture(texture); + + if(flag == DistanceFieldVectorGL3D::Flag{}) { + shader.draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == DistanceFieldVectorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorMaterialUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -379,10 +784,21 @@ void DistanceFieldVectorGLTest::renderDefaults3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void DistanceFieldVectorGLTest::render2D() { +template void DistanceFieldVectorGLTest::render2D() { auto&& data = RenderData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == DistanceFieldVectorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -408,21 +824,52 @@ void DistanceFieldVectorGLTest::render2D() { .setSubImage(0, {}, *image); #endif - DistanceFieldVectorGL2D shader{data.flags}; - shader - /** @todo implement background color */ - .setColor(data.color) - .setOutlineColor(data.outlineColor) - .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) - .setSmoothness(data.smoothness) - .bindVectorTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); - else shader.setTransformationProjectionMatrix( - Matrix3::projection({2.1f, 2.1f})); - - shader.draw(square); + DistanceFieldVectorGL2D shader{data.flags|flag}; + shader.bindVectorTexture(texture); + + if(flag == DistanceFieldVectorGL2D::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + else shader.setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})); + shader.setColor(data.color) + .setOutlineColor(data.outlineColor) + .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) + .setSmoothness(data.smoothness) + .draw(square); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == DistanceFieldVectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + data.textureTransformation == Matrix3{} ? + Matrix3::projection({2.1f, 2.1f}) : Matrix3{} + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorMaterialUniform{} + .setColor(data.color) + .setOutlineColor(data.outlineColor) + .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) + .setSmoothness(data.smoothness) + }}; + GL::Buffer textureTransformationlUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + if(data.flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationlUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(square); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -445,10 +892,21 @@ void DistanceFieldVectorGLTest::render2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void DistanceFieldVectorGLTest::render3D() { +template void DistanceFieldVectorGLTest::render3D() { auto&& data = RenderData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == DistanceFieldVectorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -474,24 +932,58 @@ void DistanceFieldVectorGLTest::render3D() { .setSubImage(0, {}, *image); #endif - DistanceFieldVectorGL3D shader{data.flags}; - shader - /** @todo implement background color */ - .setColor(data.color) - .setOutlineColor(data.outlineColor) - .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) - .setSmoothness(data.smoothness) - .bindVectorTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); - else shader.setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationZ(15.0_degf)); - - shader.draw(plane); + DistanceFieldVectorGL3D shader{data.flags|flag}; + shader.bindVectorTexture(texture); + + if(flag == DistanceFieldVectorGL3D::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + else shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationZ(15.0_degf)); + shader.setColor(data.color) + .setOutlineColor(data.outlineColor) + .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) + .setSmoothness(data.smoothness) + .draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == DistanceFieldVectorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + data.textureTransformation == Matrix3{} ? + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationZ(15.0_degf) : Matrix4{} + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + DistanceFieldVectorMaterialUniform{} + .setColor(data.color) + .setOutlineColor(data.outlineColor) + .setOutlineRange(data.outlineRangeStart, data.outlineRangeEnd) + .setSmoothness(data.smoothness) + }}; + GL::Buffer textureTransformationlUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + if(data.flags & DistanceFieldVectorGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationlUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -514,6 +1006,369 @@ void DistanceFieldVectorGLTest::render3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldVectorGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/vector-distancefield.tga")) && (image = importer->image2D(0))); + GL::Texture2D vector; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32, + Primitives::Circle2DFlag::TextureCoordinates)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid( + Primitives::SquareFlag::TextureCoordinates)); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3, + Primitives::Circle2DFlag::TextureCoordinates)); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + GL::MeshView circle{mesh}; + circle.setCount(circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(squareData.indexCount()) + .setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(triangleData.indexCount()) + .setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = DistanceFieldVectorMaterialUniform{} + .setColor(0x00ff00_rgbf); + materialData[1*data.uniformIncrement] = DistanceFieldVectorMaterialUniform{} + .setColor(0x990000_rgbf) + .setOutlineColor(0xff0000_rgbf) + .setOutlineRange(0.6f, 0.4f); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation({0.5f, 0.5f})* + Matrix3::rotation(180.0_degf)* + Matrix3::translation({-0.5f, -0.5f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation(Vector2::xAxis(1.0f))* + Matrix3::scaling(Vector2::xScale(-1.0f)) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix(Matrix3{}); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[2*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + DistanceFieldVectorGL2D shader{DistanceFieldVectorGL2D::Flag::UniformBuffers|DistanceFieldVectorGL2D::Flag::TextureTransformation, data.materialCount, data.drawCount}; + shader.bindVectorTexture(vector); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(circle); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(triangle); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindTextureTransformationBuffer(textureTransformationUniform); + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + + /* + - Circle lower left, green, upside down + - Square lower right, dark red with red outline, mirrored + - Triangle up center, green + */ + 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, "VectorTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +void DistanceFieldVectorGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/vector-distancefield.tga")) && (image = importer->image2D(0))); + GL::Texture2D vector; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, + Primitives::UVSphereFlag::TextureCoordinates); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid( + Primitives::PlaneFlag::TextureCoordinates)); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, + Primitives::ConeFlag::TextureCoordinates); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = DistanceFieldVectorMaterialUniform{} + .setColor(0x00ff00_rgbf); + materialData[1*data.uniformIncrement] = DistanceFieldVectorMaterialUniform{} + .setColor(0x990000_rgbf) + .setOutlineColor(0xff0000_rgbf) + .setOutlineRange(0.6f, 0.4f); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f})* + Matrix4::rotationY(180.0_degf) /* so the texture is visible */ + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f})* + Matrix4::rotationY(180.0_degf) /* so the texture is visible */ + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation({0.5f, 0.5f})* + Matrix3::rotation(180.0_degf)* + Matrix3::translation({-0.5f, -0.5f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation(Vector2::xAxis(1.0f))* + Matrix3::scaling(Vector2::xScale(-1.0f)) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix(Matrix3{}); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[2*data.uniformIncrement] = DistanceFieldVectorDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + DistanceFieldVectorGL3D shader{DistanceFieldVectorGL3D::Flag::UniformBuffers|DistanceFieldVectorGL3D::Flag::TextureTransformation, data.materialCount, data.drawCount}; + shader.bindVectorTexture(vector); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(sphere); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(plane); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(DistanceFieldVectorMaterialUniform), + sizeof(DistanceFieldVectorMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(DistanceFieldVectorDrawUniform), + sizeof(DistanceFieldVectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(cone); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindTextureTransformationBuffer(textureTransformationUniform); + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + + /* + - Sphere lower left, green, upside down + - Plane lower right, dark red with red outline, mirrored + - Cone up center, green + */ + 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, "VectorTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::DistanceFieldVectorGLTest) diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorTest.cpp new file mode 100644 index 000000000..bdb22c184 --- /dev/null +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorTest.cpp @@ -0,0 +1,195 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Shaders/DistanceFieldVector.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct DistanceFieldVectorTest: TestSuite::Tester { + explicit DistanceFieldVectorTest(); + + template void uniformSize(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); + void drawUniformMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); +}; + +DistanceFieldVectorTest::DistanceFieldVectorTest() { + addTests({&DistanceFieldVectorTest::uniformSize, + &DistanceFieldVectorTest::uniformSize, + + &DistanceFieldVectorTest::drawUniformConstructDefault, + &DistanceFieldVectorTest::drawUniformConstructNoInit, + &DistanceFieldVectorTest::drawUniformSetters, + &DistanceFieldVectorTest::drawUniformMaterialIdPacking, + + &DistanceFieldVectorTest::materialUniformConstructDefault, + &DistanceFieldVectorTest::materialUniformConstructNoInit, + &DistanceFieldVectorTest::materialUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "DistanceFieldVectorDrawUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "DistanceFieldVectorMaterialUniform"; } +}; + +template void DistanceFieldVectorTest::uniformSize() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); +} + +void DistanceFieldVectorTest::drawUniformConstructDefault() { + DistanceFieldVectorDrawUniform a; + DistanceFieldVectorDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + + constexpr DistanceFieldVectorDrawUniform ca; + constexpr DistanceFieldVectorDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void DistanceFieldVectorTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + DistanceFieldVectorDrawUniform a; + a.materialId = 76; + + new(&a) DistanceFieldVectorDrawUniform{NoInit}; + CORRADE_COMPARE(a.materialId, 76); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void DistanceFieldVectorTest::drawUniformSetters() { + DistanceFieldVectorDrawUniform a; + a.setMaterialId(76); + CORRADE_COMPARE(a.materialId, 76); +} + +void DistanceFieldVectorTest::drawUniformMaterialIdPacking() { + DistanceFieldVectorDrawUniform a; + a.setMaterialId(13765); + /* materialId should be right at the beginning, in the low 16 bits on both + LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[0] & 0xffff, 13765); +} + +void DistanceFieldVectorTest::materialUniformConstructDefault() { + DistanceFieldVectorMaterialUniform a; + DistanceFieldVectorMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.outlineColor, 0x00000000_rgbaf); + CORRADE_COMPARE(b.outlineColor, 0x00000000_rgbaf); + CORRADE_COMPARE(a.outlineStart, 0.5f); + CORRADE_COMPARE(b.outlineStart, 0.5f); + CORRADE_COMPARE(a.outlineEnd, 1.0f); + CORRADE_COMPARE(b.outlineEnd, 1.0f); + CORRADE_COMPARE(a.smoothness, 0.04f); + CORRADE_COMPARE(b.smoothness, 0.04f); + + constexpr DistanceFieldVectorMaterialUniform ca; + constexpr DistanceFieldVectorMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.outlineColor, 0x00000000_rgbaf); + CORRADE_COMPARE(cb.outlineColor, 0x00000000_rgbaf); + CORRADE_COMPARE(ca.outlineStart, 0.5f); + CORRADE_COMPARE(cb.outlineStart, 0.5f); + CORRADE_COMPARE(ca.outlineEnd, 1.0f); + CORRADE_COMPARE(cb.outlineEnd, 1.0f); + CORRADE_COMPARE(ca.smoothness, 0.04f); + CORRADE_COMPARE(cb.smoothness, 0.04f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void DistanceFieldVectorTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + DistanceFieldVectorMaterialUniform a; + a.color = 0x354565fc_rgbaf; + a.outlineEnd = 0.37f; + + new(&a) DistanceFieldVectorMaterialUniform{NoInit}; + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.outlineEnd, 0.37f); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void DistanceFieldVectorTest::materialUniformSetters() { + DistanceFieldVectorMaterialUniform a; + a.setColor(0x354565fc_rgbaf) + .setOutlineColor(0x9876facd_rgbaf) + .setOutlineRange(0.6f, 0.1f) + .setSmoothness(0.37f); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.outlineColor, 0x9876facd_rgbaf); + CORRADE_COMPARE(a.outlineStart, 0.6f); + CORRADE_COMPARE(a.outlineEnd, 0.1f); + CORRADE_COMPARE(a.smoothness, 0.37f); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::DistanceFieldVectorTest) diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index e6bfa3207..35882e81c 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -56,6 +56,17 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Flat.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -64,49 +75,73 @@ struct FlatGLTest: GL::OpenGLTester { explicit FlatGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffers(); + #endif template void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructMoveUniformBuffers(); + #endif template void constructTextureTransformationNotTextured(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffersZeroDraws(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + #endif template void bindTextureNotEnabled(); template void setAlphaMaskNotEnabled(); template void setTextureMatrixNotEnabled(); #ifndef MAGNUM_TARGET_GLES2 + template void bindTextureTransformBufferNotEnabled(); + #endif + #ifndef MAGNUM_TARGET_GLES2 template void setObjectIdNotEnabled(); #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - void renderDefaults2D(); - void renderDefaults3D(); - void renderColored2D(); - void renderColored3D(); - void renderSinglePixelTextured2D(); - void renderSinglePixelTextured3D(); - void renderTextured2D(); - void renderTextured3D(); + template void renderDefaults2D(); + template void renderDefaults3D(); + template void renderColored2D(); + template void renderColored3D(); + template void renderSinglePixelTextured2D(); + template void renderSinglePixelTextured3D(); + template void renderTextured2D(); + template void renderTextured3D(); - template void renderVertexColor2D(); - template void renderVertexColor3D(); + template void renderVertexColor2D(); + template void renderVertexColor3D(); void renderAlphaSetup(); void renderAlphaTeardown(); - void renderAlpha2D(); - void renderAlpha3D(); + template void renderAlpha2D(); + template void renderAlpha3D(); #ifndef MAGNUM_TARGET_GLES2 void renderObjectIdSetup(); void renderObjectIdTeardown(); - void renderObjectId2D(); - void renderObjectId3D(); + template void renderObjectId2D(); + template void renderObjectId3D(); #endif - void renderInstanced2D(); - void renderInstanced3D(); + template void renderInstanced2D(); + template void renderInstanced3D(); + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -156,6 +191,21 @@ constexpr struct { {"instanced texture offset", FlatGL2D::Flag::Textured|FlatGL2D::Flag::InstancedTextureOffset} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + FlatGL2D::Flags flags; + UnsignedInt drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1}, + {"", FlatGL2D::Flag::UniformBuffers, 1}, + {"multiple draws", FlatGL2D::Flag::UniformBuffers, 128}, + {"texture transformation", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::Textured|FlatGL2D::Flag::TextureTransformation, 1}, + {"alpha mask", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::AlphaMask, 1}, + {"object ID", FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId, 1} +}; +#endif + const struct { const char* name; FlatGL2D::Flags flags; @@ -208,19 +258,64 @@ constexpr struct { }; #endif +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + FlatGL2D::Flags flags; + UnsignedInt drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset, colored", "multidraw2D.tga", "multidraw3D.tga", + {}, 1, 16, 0.0f, 0.0f}, + {"bind with offset, textured", "multidraw-textured2D.tga", "multidraw-textured3D.tga", + FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured, + 1, 16, 0.0f, 0.0f}, + {"draw offset, colored", "multidraw2D.tga", "multidraw3D.tga", + {}, 3, 1, 0.0f, 0.0f}, + {"draw offset, textured", "multidraw-textured2D.tga", "multidraw-textured3D.tga", + FlatGL2D::Flag::TextureTransformation|FlatGL2D::Flag::Textured, + 3, 1, 0.0f, 0.0f} +}; +#endif + FlatGLTest::FlatGLTest() { addInstancedTests({ &FlatGLTest::construct<2>, &FlatGLTest::construct<3>}, Containers::arraySize(ConstructData)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &FlatGLTest::constructUniformBuffers<2>, + &FlatGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + addTests({ &FlatGLTest::constructMove<2>, &FlatGLTest::constructMove<3>, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::constructMoveUniformBuffers<2>, + &FlatGLTest::constructMoveUniformBuffers<3>, + #endif + &FlatGLTest::constructTextureTransformationNotTextured<2>, &FlatGLTest::constructTextureTransformationNotTextured<3>, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::constructUniformBuffersZeroDraws<2>, + &FlatGLTest::constructUniformBuffersZeroDraws<3>, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::setUniformUniformBuffersEnabled<2>, + &FlatGLTest::setUniformUniformBuffersEnabled<3>, + &FlatGLTest::bindBufferUniformBuffersNotEnabled<2>, + &FlatGLTest::bindBufferUniformBuffersNotEnabled<3>, + #endif &FlatGLTest::bindTextureNotEnabled<2>, &FlatGLTest::bindTextureNotEnabled<3>, &FlatGLTest::setAlphaMaskNotEnabled<2>, @@ -228,52 +323,129 @@ FlatGLTest::FlatGLTest() { &FlatGLTest::setTextureMatrixNotEnabled<2>, &FlatGLTest::setTextureMatrixNotEnabled<3>, #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::bindTextureTransformBufferNotEnabled<2>, + &FlatGLTest::bindTextureTransformBufferNotEnabled<3>, + #endif + #ifndef MAGNUM_TARGET_GLES2 &FlatGLTest::setObjectIdNotEnabled<2>, - &FlatGLTest::setObjectIdNotEnabled<3> + &FlatGLTest::setObjectIdNotEnabled<3>, #endif - }); - - addTests({&FlatGLTest::renderDefaults2D, - &FlatGLTest::renderDefaults3D, - &FlatGLTest::renderColored2D, - &FlatGLTest::renderColored3D, - &FlatGLTest::renderSinglePixelTextured2D, - &FlatGLTest::renderSinglePixelTextured3D}, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::setWrongDrawOffset<2>, + &FlatGLTest::setWrongDrawOffset<3>, + #endif + }); + + addTests({ + &FlatGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderDefaults2D, + #endif + &FlatGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderDefaults3D, + #endif + &FlatGLTest::renderColored2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderColored2D, + #endif + &FlatGLTest::renderColored3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderColored3D, + #endif + &FlatGLTest::renderSinglePixelTextured2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderSinglePixelTextured2D, + #endif + &FlatGLTest::renderSinglePixelTextured3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderSinglePixelTextured3D + #endif + }, &FlatGLTest::renderSetup, &FlatGLTest::renderTeardown); - addInstancedTests({&FlatGLTest::renderTextured2D, - &FlatGLTest::renderTextured3D}, + addInstancedTests({ + &FlatGLTest::renderTextured2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderTextured2D, + #endif + &FlatGLTest::renderTextured3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderTextured3D + #endif + }, Containers::arraySize(RenderTexturedData), &FlatGLTest::renderSetup, &FlatGLTest::renderTeardown); - addTests({&FlatGLTest::renderVertexColor2D, - &FlatGLTest::renderVertexColor2D, - &FlatGLTest::renderVertexColor3D, - &FlatGLTest::renderVertexColor3D}, + addTests({ + &FlatGLTest::renderVertexColor2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderVertexColor2D, + #endif + &FlatGLTest::renderVertexColor2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderVertexColor2D, + #endif + &FlatGLTest::renderVertexColor3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderVertexColor3D, + #endif + &FlatGLTest::renderVertexColor3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderVertexColor3D + #endif + }, &FlatGLTest::renderSetup, &FlatGLTest::renderTeardown); - addInstancedTests({&FlatGLTest::renderAlpha2D, - &FlatGLTest::renderAlpha3D}, + addInstancedTests({ + &FlatGLTest::renderAlpha2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderAlpha2D, + #endif + &FlatGLTest::renderAlpha3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderAlpha3D + #endif + }, Containers::arraySize(RenderAlphaData), &FlatGLTest::renderAlphaSetup, &FlatGLTest::renderAlphaTeardown); #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&FlatGLTest::renderObjectId2D, - &FlatGLTest::renderObjectId3D}, + addInstancedTests({ + &FlatGLTest::renderObjectId2D, + &FlatGLTest::renderObjectId2D, + &FlatGLTest::renderObjectId3D, + &FlatGLTest::renderObjectId3D}, Containers::arraySize(RenderObjectIdData), &FlatGLTest::renderObjectIdSetup, &FlatGLTest::renderObjectIdTeardown); #endif - addTests({&FlatGLTest::renderInstanced2D, - &FlatGLTest::renderInstanced3D}, + addTests({ + &FlatGLTest::renderInstanced2D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderInstanced2D, + #endif + &FlatGLTest::renderInstanced3D, + #ifndef MAGNUM_TARGET_GLES2 + &FlatGLTest::renderInstanced3D + #endif + }, &FlatGLTest::renderSetup, &FlatGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&FlatGLTest::renderMulti2D, + &FlatGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), + &FlatGLTest::renderObjectIdSetup, + &FlatGLTest::renderObjectIdTeardown); + #endif + /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -322,6 +494,35 @@ template void FlatGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::constructUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & FlatGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if((data.flags & FlatGL2D::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + FlatGL shader{data.flags, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void FlatGLTest::constructMove() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -343,6 +544,36 @@ template void FlatGLTest::constructMove() { CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + FlatGL a{FlatGL::Flag::UniformBuffers, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + FlatGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), FlatGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + FlatGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), FlatGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + template void FlatGLTest::constructTextureTransformationNotTextured() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -357,6 +588,87 @@ template void FlatGLTest::constructTextureTransformation "Shaders::FlatGL: texture transformation enabled but the shader is not textured\n"); } +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::constructUniformBuffersZeroDraws() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + FlatGL{FlatGL::Flag::UniformBuffers, 0}; + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL: draw count can't be zero\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::setUniformUniformBuffersEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + FlatGL shader{FlatGL::Flag::UniformBuffers}; + shader.setTransformationProjectionMatrix({}) + .setTextureMatrix({}) + .setColor({}) + .setAlphaMask({}) + .setObjectId({}); + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setColor(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setAlphaMask(): the shader was created with uniform buffers enabled\n" + "Shaders::FlatGL::setObjectId(): the shader was created with uniform buffers enabled\n"); +} + +template void FlatGLTest::bindBufferUniformBuffersNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + FlatGL shader; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::FlatGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} +#endif + template void FlatGLTest::bindTextureNotEnabled() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -408,6 +720,32 @@ template void FlatGLTest::setTextureMatrixNotEnabled() { "Shaders::FlatGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); } +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::bindTextureTransformBufferNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer{GL::Buffer::TargetHint::Uniform}; + FlatGL shader{FlatGL::Flag::UniformBuffers}; + shader.bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16); + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n" + "Shaders::FlatGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 template void FlatGLTest::setObjectIdNotEnabled() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -427,6 +765,28 @@ template void FlatGLTest::setObjectIdNotEnabled() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +template void FlatGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + FlatGL{FlatGL::Flag::UniformBuffers, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::FlatGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void FlatGLTest::renderSetup() { @@ -454,11 +814,40 @@ void FlatGLTest::renderTeardown() { _color = GL::Renderbuffer{NoCreate}; } -void FlatGLTest::renderDefaults2D() { +template void FlatGLTest::renderDefaults2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32)); - FlatGL2D{} - .draw(circle); + FlatGL2D shader{flag}; + + if(flag == FlatGL2D::Flag{}) { + shader.draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -474,11 +863,40 @@ void FlatGLTest::renderDefaults2D() { (DebugTools::CompareImageToFile{_manager, 238.0f, 0.2975f})); } -void FlatGLTest::renderDefaults3D() { +template void FlatGLTest::renderDefaults3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - FlatGL3D{} - .draw(sphere); + FlatGL3D shader{flag}; + + if(flag == FlatGL3D::Flag{}) { + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -494,13 +912,45 @@ void FlatGLTest::renderDefaults3D() { (DebugTools::CompareImageToFile{_manager, 238.0f, 0.2975f})); } -void FlatGLTest::renderColored2D() { +template void FlatGLTest::renderColored2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32)); - FlatGL2D{} - .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .draw(circle); + FlatGL2D shader{flag}; + + if(flag == FlatGL2D::Flag{}) { + shader + .setColor(0x9999ff_rgbf) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -521,17 +971,54 @@ void FlatGLTest::renderColored2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderColored3D() { +template void FlatGLTest::renderColored3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - FlatGL3D{} - .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .draw(sphere); + FlatGL3D shader{flag}; + + if(flag == FlatGL3D::Flag{}) { + shader + .setColor(0x9999ff_rgbf) + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -568,7 +1055,18 @@ constexpr GL::TextureFormat TextureFormatRGBA = #endif ; -void FlatGLTest::renderSinglePixelTextured2D() { +template void FlatGLTest::renderSinglePixelTextured2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates)); @@ -581,10 +1079,28 @@ void FlatGLTest::renderSinglePixelTextured2D() { .setStorage(1, TextureFormatRGBA, Vector2i{1}) .setSubImage(0, {}, diffuseImage); - FlatGL2D{FlatGL3D::Flag::Textured} - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .bindTexture(texture) - .draw(circle); + FlatGL2D shader{FlatGL2D::Flag::Textured|flag}; + shader.bindTexture(texture); + + if(flag == FlatGL2D::Flag{}) { + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -606,7 +1122,18 @@ void FlatGLTest::renderSinglePixelTextured2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderSinglePixelTextured3D() { +template void FlatGLTest::renderSinglePixelTextured3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); @@ -619,14 +1146,38 @@ void FlatGLTest::renderSinglePixelTextured3D() { .setStorage(1, TextureFormatRGBA, Vector2i{1}) .setSubImage(0, {}, diffuseImage); - FlatGL3D{FlatGL3D::Flag::Textured} - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .bindTexture(texture) - .draw(sphere); + FlatGL3D shader{FlatGL3D::Flag::Textured|flag}; + shader.bindTexture(texture); + + if(flag == FlatGL3D::Flag{}) { + shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -648,10 +1199,21 @@ void FlatGLTest::renderSinglePixelTextured3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderTextured2D() { +template void FlatGLTest::renderTextured2D() { auto&& data = RenderTexturedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -671,18 +1233,40 @@ void FlatGLTest::renderTextured2D() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - FlatGL2D shader{data.flags}; - shader - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - /* Colorized. Case without a color (where it should be white) is tested - in renderSinglePixelTextured() */ - .setColor(0x9999ff_rgbf) - .bindTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); + FlatGL2D shader{data.flags|flag}; + shader.bindTexture(texture); - shader.draw(circle); + if(flag == FlatGL2D::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + /* Colorized. Case without a color (where it should be white) is + tested in renderSinglePixelTextured2D() */ + .setColor(0x9999ff_rgbf) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -704,10 +1288,21 @@ void FlatGLTest::renderTextured2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderTextured3D() { +template void FlatGLTest::renderTextured3D() { auto&& data = RenderTexturedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -727,22 +1322,50 @@ void FlatGLTest::renderTextured3D() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - FlatGL3D shader{data.flags}; - shader - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(data.flip ? 15.0_degf : -15.0_degf)* - Matrix4::rotationX(data.flip ? -15.0_degf : 15.0_degf)) - /* Colorized. Case without a color (where it should be white) is tested - in renderSinglePixelTextured() */ - .setColor(0x9999ff_rgbf) - .bindTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); + FlatGL3D shader{data.flags|flag}; + shader.bindTexture(texture); - shader.draw(sphere); + if(flag == FlatGL3D::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + shader + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(data.flip ? 15.0_degf : -15.0_degf)* + Matrix4::rotationX(data.flip ? -15.0_degf : 15.0_degf)) + /* Colorized. Case without a color (where it should be white) is + tested in renderSinglePixelTextured3D() */ + .setColor(0x9999ff_rgbf) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(data.flip ? 15.0_degf : -15.0_degf)* + Matrix4::rotationX(data.flip ? -15.0_degf : 15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -764,8 +1387,20 @@ void FlatGLTest::renderTextured3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void FlatGLTest::renderVertexColor2D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void FlatGLTest::renderVertexColor2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -796,11 +1431,30 @@ template void FlatGLTest::renderVertexColor2D() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - FlatGL2D{FlatGL2D::Flag::Textured|FlatGL2D::Flag::VertexColor} - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .setColor(0x9999ff_rgbf) - .bindTexture(texture) - .draw(circle); + FlatGL2D shader{FlatGL2D::Flag::Textured|FlatGL2D::Flag::VertexColor|flag}; + shader.bindTexture(texture); + + if(flag == FlatGL2D::Flag{}) { + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .setColor(0x9999ff_rgbf) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -818,8 +1472,20 @@ template void FlatGLTest::renderVertexColor2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void FlatGLTest::renderVertexColor3D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void FlatGLTest::renderVertexColor3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -850,15 +1516,39 @@ template void FlatGLTest::renderVertexColor3D() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - FlatGL3D{FlatGL3D::Flag::Textured|FlatGL3D::Flag::VertexColor} - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setColor(0x9999ff_rgbf) - .bindTexture(texture) - .draw(sphere); + FlatGL3D shader{FlatGL3D::Flag::Textured|FlatGL3D::Flag::VertexColor|flag}; + shader.bindTexture(texture); + + if(flag == FlatGL2D::Flag{}) { + shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setColor(0x9999ff_rgbf) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -890,10 +1580,21 @@ void FlatGLTest::renderAlphaTeardown() { renderTeardown(); } -void FlatGLTest::renderAlpha2D() { +template void FlatGLTest::renderAlpha2D() { auto&& data = RenderAlphaData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -915,15 +1616,35 @@ void FlatGLTest::renderAlpha2D() { GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates)); - FlatGL2D shader{data.flags}; - shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .setColor(0x9999ff_rgbf) - .bindTexture(texture); - - if(data.flags & FlatGL3D::Flag::AlphaMask) - shader.setAlphaMask(data.threshold); + FlatGL2D shader{data.flags|flag}; + shader.bindTexture(texture); - shader.draw(circle); + if(flag == FlatGL2D::Flag{}) { + /* Test that the default is correct by not setting the threshold if + it's equal to the default */ + if(data.flags & FlatGL2D::Flag::AlphaMask && data.threshold != 0.5f) + shader.setAlphaMask(data.threshold); + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .setColor(0x9999ff_rgbf) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + .setAlphaMask(data.threshold) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -941,10 +1662,21 @@ void FlatGLTest::renderAlpha2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderAlpha3D() { +template void FlatGLTest::renderAlpha3D() { auto&& data = RenderAlphaData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -966,23 +1698,55 @@ void FlatGLTest::renderAlpha3D() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); - FlatGL3D shader{data.flags}; - shader.setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setColor(0x9999ff_rgbf) - .bindTexture(texture); - - if(data.flags & FlatGL3D::Flag::AlphaMask) - shader.setAlphaMask(data.threshold); + FlatGL3D shader{data.flags|flag}; + shader.bindTexture(texture); - /* For proper Z order draw back faces first and then front faces */ - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); - shader.draw(sphere); - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); - shader.draw(sphere); + if(flag == FlatGL2D::Flag{}) { + shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setColor(0x9999ff_rgbf); + + /* Test that the default is correct by not setting the threshold if + it's equal to the default */ + if(data.flags & FlatGL3D::Flag::AlphaMask && data.threshold != 0.5f) + shader.setAlphaMask(data.threshold); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + .setAlphaMask(data.threshold) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1039,10 +1803,21 @@ void FlatGLTest::renderObjectIdTeardown() { _framebuffer = GL::Framebuffer{NoCreate}; } -void FlatGLTest::renderObjectId2D() { +template void FlatGLTest::renderObjectId2D() { auto&& data = RenderObjectIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1058,11 +1833,27 @@ void FlatGLTest::renderObjectId2D() { GL::Buffer{Containers::arrayView({11002u, 48823u})}, 1, 0, FlatGL2D::ObjectId{}); - FlatGL2D{data.flags} - .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .setObjectId(data.uniformId) - .draw(circle); + FlatGL2D shader{data.flags|flag}; + + if(flag == FlatGL2D::Flag{}) { + shader.setColor(0x9999ff_rgbf) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .setObjectId(data.uniformId) + .draw(circle); + } else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + .setObjectId(data.uniformId) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1097,10 +1888,21 @@ void FlatGLTest::renderObjectId2D() { CORRADE_COMPARE(image.pixels()[40][46], data.expected); } -void FlatGLTest::renderObjectId3D() { +template void FlatGLTest::renderObjectId3D() { auto&& data = RenderObjectIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1116,15 +1918,36 @@ void FlatGLTest::renderObjectId3D() { GL::Buffer{Containers::arrayView({11002u, 48823u})}, 1, 0, FlatGL2D::ObjectId{}); - FlatGL3D{data.flags} - .setColor(0x9999ff_rgbf) - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setObjectId(data.uniformId) - .draw(sphere); + FlatGL3D shader{data.flags|flag}; + + if(flag == FlatGL3D::Flag{}) { + shader.setColor(0x9999ff_rgbf) + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setObjectId(data.uniformId) + .draw(sphere); + } else if(flag == FlatGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0x9999ff_rgbf) + .setObjectId(data.uniformId) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1163,7 +1986,18 @@ void FlatGLTest::renderObjectId3D() { } #endif -void FlatGLTest::renderInstanced2D() { +template void FlatGLTest::renderInstanced2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); @@ -1219,17 +2053,44 @@ void FlatGLTest::renderInstanced2D() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - FlatGL2D{FlatGL2D::Flag::Textured| + FlatGL2D shader{FlatGL2D::Flag::Textured| FlatGL2D::Flag::VertexColor| FlatGL2D::Flag::InstancedTransformation| - FlatGL2D::Flag::InstancedTextureOffset} - .setColor(0xffff99_rgbf) - .setTransformationProjectionMatrix( - Matrix3::projection({2.1f, 2.1f})* - Matrix3::scaling(Vector2{0.4f})) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) - .bindTexture(texture) - .draw(circle); + FlatGL2D::Flag::InstancedTextureOffset|flag}; + shader.bindTexture(texture); + + if(flag == FlatGL2D::Flag{}) { + shader.setColor(0xffff99_rgbf) + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})) + .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0xffff99_rgbf) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindTextureTransformationBuffer(textureTransformationUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1247,7 +2108,18 @@ void FlatGLTest::renderInstanced2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void FlatGLTest::renderInstanced3D() { +template void FlatGLTest::renderInstanced3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == FlatGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); @@ -1303,18 +2175,46 @@ void FlatGLTest::renderInstanced3D() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - FlatGL3D{FlatGL3D::Flag::Textured| + FlatGL3D shader{FlatGL3D::Flag::Textured| FlatGL3D::Flag::VertexColor| FlatGL3D::Flag::InstancedTransformation| - FlatGL3D::Flag::InstancedTextureOffset} - .setColor(0xffff99_rgbf) - .setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::scaling(Vector3{0.4f})) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) - .bindTexture(texture) - .draw(sphere); + FlatGL3D::Flag::InstancedTextureOffset|flag}; + shader.bindTexture(texture); + + if(flag == FlatGL3D::Flag{}) { + shader.setColor(0xffff99_rgbf) + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})) + .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == FlatGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + FlatDrawUniform{} + .setColor(0xffff99_rgbf) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindTextureTransformationBuffer(textureTransformationUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1332,6 +2232,421 @@ void FlatGLTest::renderInstanced3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void FlatGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + GL::Texture2D texture; + if(data.flags & FlatGL2D::Flag::Textured) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::RGB8, image->size()) + .setSubImage(0, {}, *image); + } + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32, + Primitives::Circle2DFlag::TextureCoordinates)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid( + Primitives::SquareFlag::TextureCoordinates)); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3, + Primitives::Circle2DFlag::TextureCoordinates)); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + GL::MeshView circle{mesh}; + circle.setCount(circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(squareData.indexCount()) + .setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(triangleData.indexCount()) + .setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.0f, 0.0f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({1.0f, 0.0f}) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.5f, 1.0f}) + ); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + drawData[0*data.uniformIncrement] = FlatDrawUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0xff0000_rgbf) + .setObjectId(1211); + drawData[1*data.uniformIncrement] = FlatDrawUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0x0000ff_rgbf) + .setObjectId(5627); + drawData[2*data.uniformIncrement] = FlatDrawUniform{} + .setColor(data.flags & FlatGL2D::Flag::Textured ? + 0xffffff_rgbf : 0xff0000_rgbf) + .setObjectId(36363); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + FlatGL2D shader{FlatGL2D::Flag::UniformBuffers|FlatGL2D::Flag::ObjectId|data.flags, data.drawCount}; + if(data.flags & FlatGL2D::Flag::Textured) + shader.bindTexture(texture); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(circle); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(square); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(triangle); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform); + if(data.flags & FlatGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + + 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."); + + /* + Colored case: + + - Circle should be lower left, red + - Square lower right, blue + - Triangle up center, red + + Textured case: + + - Circle should have bottom left numbers, so light 7881 + - Square bottom right, 1223 + - Triangle 6778 + */ + 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, "FlatTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); /* Circle */ + CORRADE_COMPARE(image.pixels()[24][56], 5627); /* Square */ + CORRADE_COMPARE(image.pixels()[56][40], 36363); /* Triangle */ + } +} + +void FlatGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + GL::Texture2D texture; + if(data.flags & FlatGL3D::Flag::Textured) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + texture.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::RGB8, image->size()) + .setSubImage(0, {}, *image); + } + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, + Primitives::UVSphereFlag::TextureCoordinates); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid( + Primitives::PlaneFlag::TextureCoordinates)); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, + Primitives::ConeFlag::TextureCoordinates); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f})* + /* To be consistent with Phong's output where it tests that the + normal matrix is applied properly */ + Matrix4::rotationX(90.0_degf) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.0f, 0.0f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({1.0f, 0.0f}) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.5f, 1.0f}) + ); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + drawData[0*data.uniformIncrement] = FlatDrawUniform{} + .setColor(data.flags & FlatGL3D::Flag::Textured ? + 0xffffff_rgbf : 0xff0000_rgbf) + .setObjectId(1211); + drawData[1*data.uniformIncrement] = FlatDrawUniform{} + .setColor(data.flags & FlatGL3D::Flag::Textured ? + 0xffffff_rgbf : 0x0000ff_rgbf) + .setObjectId(5627); + drawData[2*data.uniformIncrement] = FlatDrawUniform{} + .setColor(data.flags & FlatGL3D::Flag::Textured ? + 0xffffff_rgbf : 0xff0000_rgbf) + .setObjectId(36363); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + FlatGL3D shader{FlatGL3D::Flag::UniformBuffers|FlatGL3D::Flag::ObjectId|data.flags, data.drawCount}; + if(data.flags & FlatGL3D::Flag::Textured) + shader.bindTexture(texture); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(sphere); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(plane); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(FlatDrawUniform), + sizeof(FlatDrawUniform)); + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(cone); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform); + if(data.flags & FlatGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + + 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."); + + /* + Colored case: + + - Sphere should be lower left, red + - Plane lower right, blue + - Cone up center, red + + Textured case: + + - Sphere should have bottom left numbers, so light 7881, rotated (78 + visible) + - Plane bottom right, 1223 + - Cone 6778 + */ + 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, "FlatTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); /* Sphere */ + CORRADE_COMPARE(image.pixels()[24][56], 5627); /* Plane */ + CORRADE_COMPARE(image.pixels()[56][40], 36363); /* Circle */ + } +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::FlatGLTest) diff --git a/src/Magnum/Shaders/Test/FlatGL_Test.cpp b/src/Magnum/Shaders/Test/FlatGL_Test.cpp index 566ac3eb3..640e18d78 100644 --- a/src/Magnum/Shaders/Test/FlatGL_Test.cpp +++ b/src/Magnum/Shaders/Test/FlatGL_Test.cpp @@ -78,8 +78,8 @@ template void FlatGL_Test::constructCopy() { void FlatGL_Test::debugFlag() { std::ostringstream out; - Debug{&out} << FlatGL3D::Flag::Textured << FlatGL3D::Flag(0xf0); - CORRADE_COMPARE(out.str(), "Shaders::FlatGL::Flag::Textured Shaders::FlatGL::Flag(0xf0)\n"); + Debug{&out} << FlatGL3D::Flag::Textured << FlatGL3D::Flag(0xf00d); + CORRADE_COMPARE(out.str(), "Shaders::FlatGL::Flag::Textured Shaders::FlatGL::Flag(0xf00d)\n"); } void FlatGL_Test::debugFlags() { diff --git a/src/Magnum/Shaders/Test/FlatTest.cpp b/src/Magnum/Shaders/Test/FlatTest.cpp new file mode 100644 index 000000000..8365429ca --- /dev/null +++ b/src/Magnum/Shaders/Test/FlatTest.cpp @@ -0,0 +1,124 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Shaders/Flat.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct FlatTest: TestSuite::Tester { + explicit FlatTest(); + + template void uniformSize(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); +}; + +FlatTest::FlatTest() { + addTests({&FlatTest::uniformSize, + + &FlatTest::drawUniformConstructDefault, + &FlatTest::drawUniformConstructNoInit, + &FlatTest::drawUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "FlatDrawUniform"; } +}; + +template void FlatTest::uniformSize() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); +} + +void FlatTest::drawUniformConstructDefault() { + FlatDrawUniform a; + FlatDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.objectId, 0); + CORRADE_COMPARE(b.objectId, 0); + CORRADE_COMPARE(a.alphaMask, 0.5f); + CORRADE_COMPARE(b.alphaMask, 0.5f); + + constexpr FlatDrawUniform ca; + constexpr FlatDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.objectId, 0); + CORRADE_COMPARE(cb.objectId, 0); + CORRADE_COMPARE(ca.alphaMask, 0.5f); + CORRADE_COMPARE(cb.alphaMask, 0.5f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void FlatTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + FlatDrawUniform a; + a.color = 0x354565fc_rgbaf; + a.alphaMask = 0.7f; + + new(&a) FlatDrawUniform{NoInit}; + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.alphaMask, 0.7f); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void FlatTest::drawUniformSetters() { + FlatDrawUniform a; + a.setColor(0x354565fc_rgbaf) + .setObjectId(7) + .setAlphaMask(0.7f); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.alphaMask, 0.7f); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::FlatTest) diff --git a/src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured2D.tga b/src/Magnum/Shaders/Test/FlatTestFiles/multidraw-textured2D.tga new file mode 100644 index 0000000000000000000000000000000000000000..31d45ce3ef91c561239c55ca5e56926d811e0901 GIT binary patch literal 2501 zcmcIlTT>iG6yBL-waPc%{1?ma_RQYQ4=`$h0ST4F5HUbfrHG2bki><(4rI9+A`lD# z#1Mih3kaY{vVm-X03m9sEG{or>5IR@@9Ui*6I>rNHC=Nq-QRcmoYT_|$7>GjDRGoI zUUzI278d3kov_FE_V(ictc`U;?x_7O+~M9@NkDGLNWzzdkMr!l%g_z@4EQ+D?rVeI z0-v(8vx83pqZQNA=CmZ>759@zWHFgq;c{WxQs(}Z&h+Sw>#1l2suh^>cXnj*%7ubh z{YiXw76yQZ737H{Ue}4SKyibp=+*I7Erwu7e)2D#gW^=+5-@HGIM%6b*r}QUbdaHE8W^qcXoDW+KgVZY%dJ-TsmW8VJV$u zhW2)?@6;S?OIqSN#mKc#ZDpXb9tw*sfdSqDybd6+!uHmu|E2iAMIOnAI66FHb3c}s z;FEd!b7|<0`97dila)XVm9^<_9`mx1p0XrS4E6w%qj;w#LdVLS;qnrEin5Eau_AE< z>+*@r8~Wix+FOV8!y&B>rkJ@0wi_9g?5)dovdXDI^ zX;M4dtG`p{Gco0XMtsWiP$i~EYy`DW@?fg>Mw5R%90n{IWX5ZrhD#bn^=A0Qc;JIy zn2i>UJjf|V>l4#L+=gW3R+fg*HrKsxgMGDnEp)V-DXq$cj(^7_;Kk1J0L$um#5z#% z`ovu!zPB-O#VdfG=sDkg@eJO`$H(^;2VvpQhbPNb-ldnuv?Eci=7~uo|E9={Dh7Ll z4g6)YjwaIvavso{0$K!#vN3TH?-BkZok;h7(7cOyXIY0r-c6WjV1~S#Y(9CXnIQ5` zv-!G!!=QP2^c=vW&(9w+qR*{a+!>l7*;@fiLa)*KwuaK#bN z>>Vz~;Mq}xW}$Nf{FNIpUpLoKZ-YK))6>ugYlRbsHz$z_;c&;nl>rSFAvgt&eHLW&li=;uDLeD+bqoj=W+? zw%MxbY=P6-ysBv<9zhR@6?gz>IeCt)g1L0q!q`f2ZeiV|n?{{s#5tpe(w4TgrGqGA zV?x;moh)NGo!OXEemSTVO6ONT=u9;8ukd+J@1-~0x=46)&UxPFectz;^PW?asn(?b zwVT>aFPjQ=b#?#8M!^f7dfX(sivE9dGa=XyfYPuq;3(E9%#)ZWZH4tA%!gqStmE({ zDtzS9Cyo|d&|DOh($=N zfLWs;q!1g^V$E+~b_eo}41H=*~@1ON+7lR)_Ymlbv9nCgqdaO^lF2oFC+)T@rp|R^zO$HhM7xsf*gS{q41`dTeP27G<6<;P)CM1G<^DTMG_NO7XN(@h9gt% zzz&a1?imyH`a3WLVOAvCoKLUaNIY51V6A0jOK>-YU7ie6aT{1Yg@ykiPe;5n?Z&|J zSbuH6_a@~2Seo2k7)gH?xH5R=lOAv98?V7vk`5!@*lpCaBKobf3Qp@lk26p()>(U| z``~oq?0E3Q^QV3DW1nZ!=ho+v_a+BkC}@FT*SP!O7s|0){CxemtWQ=lnRxF?HlDvd z@-#Q|_oJ&;2w}kDg^D#$LGz!GA1yD?N}eq=4CDFm?j;?{mcB*iwm#+9O?H87usDh9Wdk$TVT={OZ;m2o0>P@q2xVkuS3~va`_Be`V%Yq ziY+Xlti@W4&2WL*EO#Z(Tg^)%-()1v`(*?a-b?Xy;W*r+X@ylXzXFp#2A27)KCtj# z>JOG}};Q1fMuURp2XdNfGbs z-+~#35vl@z0~gwHOB|MLKF)-yz>{!AOYAu?qcB2M;3>Fd*B)vio9{m%n%LL$Gjw=+ zL`Ta_cx2PsmNqoG491u8A=^UVfsYQQ_cfD^{}ML{z6D(SXwK1|2dt?wD1snyzyDRB zylsT0Vc{~&q=fe1#r{_S>F`y!LS&aG@h9@08gO}oz@4>q0w)?9(Vzbe$%?( zB{cfKX(`Uv@bM1wMH=(oeE6tU(D*Nl{WtA1=cu+w`?Y^j@1r-0-ZFagXf_J;w~IcP zuFuh|McNKAO~gL1lUv}savOY8PQh1Y{_CR5**z&|;G;6<_n_KOfX?1NvEpbbWiB&o~n~- zf4%C}X?-_MbIiD7-k@s#h2Aj6_zAk)S6!jka@Z)C&!;e-xAVsMd)P3TYaU>i!Cdor zfX!ip&b$TeBG~V{k7O(6^*d|-o4L2{e?G+Q#r@{qWcHFB*y#+G{n@=eC-aOvkL>;y zHi6~2O<^OL?`_X*hVR2X` + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Shaders/Generic.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct GenericTest: TestSuite::Tester { + explicit GenericTest(); + + template void uniformSize(); + + void projectionUniform2DConstructDefault(); + void projectionUniform2DConstructNoInit(); + void projectionUniform2DSetters(); + + void projectionUniform3DConstructDefault(); + void projectionUniform3DConstructNoInit(); + void projectionUniform3DSetters(); + + void transformationUniform2DConstructDefault(); + void transformationUniform2DConstructNoInit(); + void transformationUniform2DSetters(); + + void transformationUniform3DConstructDefault(); + void transformationUniform3DConstructNoInit(); + void transformationUniform3DSetters(); + + void textureTransformationUniformConstructDefault(); + void textureTransformationUniformConstructNoInit(); + void textureTransformationUniformSetters(); +}; + +GenericTest::GenericTest() { + addTests({&GenericTest::uniformSize, + &GenericTest::uniformSize, + &GenericTest::uniformSize, + &GenericTest::uniformSize, + &GenericTest::uniformSize, + + &GenericTest::projectionUniform2DConstructDefault, + &GenericTest::projectionUniform2DConstructNoInit, + &GenericTest::projectionUniform2DSetters, + + &GenericTest::projectionUniform3DConstructDefault, + &GenericTest::projectionUniform3DConstructNoInit, + &GenericTest::projectionUniform3DSetters, + + &GenericTest::transformationUniform2DConstructDefault, + &GenericTest::transformationUniform2DConstructNoInit, + &GenericTest::transformationUniform2DSetters, + + &GenericTest::transformationUniform3DConstructDefault, + &GenericTest::transformationUniform3DConstructNoInit, + &GenericTest::transformationUniform3DSetters, + + &GenericTest::textureTransformationUniformConstructDefault, + &GenericTest::textureTransformationUniformConstructNoInit, + &GenericTest::textureTransformationUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "ProjectionUniform2D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "ProjectionUniform3D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "TransformationUniform2D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "TransformationUniform3D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "TextureTransformationUniform"; } +}; + +template void GenericTest::uniformSize() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); +} + +void GenericTest::projectionUniform2DConstructDefault() { + ProjectionUniform2D a; + ProjectionUniform2D b{DefaultInit}; + CORRADE_COMPARE(a.projectionMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(b.projectionMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + + constexpr ProjectionUniform2D ca; + constexpr ProjectionUniform2D cb{DefaultInit}; + CORRADE_COMPARE(ca.projectionMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(cb.projectionMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::projectionUniform2DConstructNoInit() { + ProjectionUniform2D a; + a.projectionMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + + new(&a) ProjectionUniform2D{NoInit}; + CORRADE_COMPARE(a.projectionMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::projectionUniform2DSetters() { + ProjectionUniform2D a; + a.setProjectionMatrix(Matrix3::projection({2.5f, 3.0f})); + CORRADE_COMPARE(a.projectionMatrix, (Matrix3x4{ + Vector4{0.8f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.666667f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); +} + +void GenericTest::projectionUniform3DConstructDefault() { + ProjectionUniform3D a; + ProjectionUniform3D b{DefaultInit}; + CORRADE_COMPARE(a.projectionMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + CORRADE_COMPARE(b.projectionMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + + constexpr ProjectionUniform3D ca; + constexpr ProjectionUniform3D cb{DefaultInit}; + CORRADE_COMPARE(ca.projectionMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + CORRADE_COMPARE(cb.projectionMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::projectionUniform3DConstructNoInit() { + ProjectionUniform3D a; + a.projectionMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + + new(&a) ProjectionUniform3D{NoInit}; + CORRADE_COMPARE(a.projectionMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::projectionUniform3DSetters() { + ProjectionUniform3D a; + a.setProjectionMatrix(Matrix4::perspectiveProjection(35.0_degf, 1.5f, 0.1f, 100.0f)); + CORRADE_COMPARE(a.projectionMatrix, Matrix4::perspectiveProjection(35.0_degf, 1.5f, 0.1f, 100.0f)); +} + +void GenericTest::transformationUniform2DConstructDefault() { + TransformationUniform2D a; + TransformationUniform2D b{DefaultInit}; + CORRADE_COMPARE(a.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(b.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + + constexpr TransformationUniform2D ca; + constexpr TransformationUniform2D cb{DefaultInit}; + CORRADE_COMPARE(ca.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(cb.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::transformationUniform2DConstructNoInit() { + TransformationUniform2D a; + a.transformationMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + + new(&a) TransformationUniform2D{NoInit}; + CORRADE_COMPARE(a.transformationMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::transformationUniform2DSetters() { + TransformationUniform2D a; + a.setTransformationMatrix(Matrix3::translation({2.5f, 3.0f})); + CORRADE_COMPARE(a.transformationMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{2.5f, 3.0f, 1.0f, 0.0f} + })); +} + +void GenericTest::transformationUniform3DConstructDefault() { + TransformationUniform3D a; + TransformationUniform3D b{DefaultInit}; + CORRADE_COMPARE(a.transformationMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + CORRADE_COMPARE(b.transformationMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + + constexpr TransformationUniform3D ca; + constexpr TransformationUniform3D cb{DefaultInit}; + CORRADE_COMPARE(ca.transformationMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + CORRADE_COMPARE(cb.transformationMatrix, (Matrix4{ + {1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f, 0.0f, 1.0f} + })); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::transformationUniform3DConstructNoInit() { + TransformationUniform3D a; + a.transformationMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + + new(&a) TransformationUniform3D{NoInit}; + CORRADE_COMPARE(a.transformationMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::transformationUniform3DSetters() { + TransformationUniform3D a; + a.setTransformationMatrix(Matrix4::translation({3.5f, 2.0f, -1.0f})); + CORRADE_COMPARE(a.transformationMatrix, Matrix4::translation({3.5f, 2.0f, -1.0f})); +} + +void GenericTest::textureTransformationUniformConstructDefault() { + TextureTransformationUniform a; + TextureTransformationUniform b{DefaultInit}; + CORRADE_COMPARE(a.rotationScaling, (Matrix2x2{ + Vector2{1.0f, 0.0f}, + Vector2{0.0f, 1.0f} + })); + CORRADE_COMPARE(b.rotationScaling, (Matrix2x2{ + Vector2{1.0f, 0.0f}, + Vector2{0.0f, 1.0f} + })); + CORRADE_COMPARE(a.offset, (Vector2{0.0f, 0.0f})); + CORRADE_COMPARE(b.offset, (Vector2{0.0f, 0.0f})); + + constexpr TextureTransformationUniform ca; + constexpr TextureTransformationUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.rotationScaling, (Matrix2x2{ + Vector2{1.0f, 0.0f}, + Vector2{0.0f, 1.0f} + })); + CORRADE_COMPARE(cb.rotationScaling, (Matrix2x2{ + Vector2{1.0f, 0.0f}, + Vector2{0.0f, 1.0f} + })); + CORRADE_COMPARE(ca.offset, (Vector2{0.0f, 0.0f})); + CORRADE_COMPARE(cb.offset, (Vector2{0.0f, 0.0f})); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::textureTransformationUniformConstructNoInit() { + TextureTransformationUniform a; + a.rotationScaling[1] = {2.5f, -3.0f}; + a.offset = {2.7f, 0.3f}; + + new(&a) TextureTransformationUniform{NoInit}; + CORRADE_COMPARE(a.rotationScaling[1], (Vector2{2.5f, -3.0f})); + CORRADE_COMPARE(a.offset, (Vector2{2.7f, 0.3f})); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void GenericTest::textureTransformationUniformSetters() { + TextureTransformationUniform a; + a.setTextureMatrix(Matrix3::translation({2.6f, 0.3f})* + Matrix3::rotation(90.0_degf)); + CORRADE_COMPARE(a.rotationScaling, (Matrix2x2{ + Vector2{ 0.0f, 1.0f}, + Vector2{-1.0f, 0.0f} + })); + CORRADE_COMPARE(a.offset, (Vector2{2.6f, 0.3f})); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::GenericTest) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index 92f0c2f4a..717f11326 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -61,6 +61,17 @@ #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/MeshVisualizer.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -69,14 +80,38 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { explicit MeshVisualizerGLTest(); void construct2D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers2D(); + #endif void construct3D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers3D(); + #endif void construct2DInvalid(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers2DInvalid(); + #endif void construct3DInvalid(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers3DInvalid(); + #endif void constructMove2D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructMoveUniformBuffers2D(); + #endif void constructMove3D(); + #ifndef MAGNUM_TARGET_GLES2 + void constructMoveUniformBuffers3D(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + void setUniformUniformBuffersEnabled2D(); + void setUniformUniformBuffersEnabled3D(); + void bindBufferUniformBuffersNotEnabled2D(); + void bindBufferUniformBuffersNotEnabled3D(); + #endif void setWireframeNotEnabled2D(); void setWireframeNotEnabled3D(); #ifndef MAGNUM_TARGET_GLES2 @@ -86,34 +121,44 @@ struct MeshVisualizerGLTest: GL::OpenGLTester { #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) void setTangentBitangentNormalNotEnabled3D(); #endif + #ifndef MAGNUM_TARGET_GLES2 + void setWrongDrawOffset2D(); + void setWrongDrawOffset3D(); + #endif void renderSetup(); void renderTeardown(); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - void renderDefaultsWireframe2D(); - void renderDefaultsWireframe3D(); + template void renderDefaultsWireframe2D(); + template void renderDefaultsWireframe3D(); #endif #ifndef MAGNUM_TARGET_GLES2 - void renderDefaultsObjectId2D(); - void renderDefaultsObjectId3D(); - void renderDefaultsVertexId2D(); - void renderDefaultsVertexId3D(); - void renderDefaultsPrimitiveId2D(); - void renderDefaultsPrimitiveId3D(); + template void renderDefaultsObjectId2D(); + template void renderDefaultsObjectId3D(); + template void renderDefaultsVertexId2D(); + template void renderDefaultsVertexId3D(); + template void renderDefaultsPrimitiveId2D(); + template void renderDefaultsPrimitiveId3D(); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - void renderDefaultsTangentBitangentNormal(); + template void renderDefaultsTangentBitangentNormal(); #endif - void renderWireframe2D(); - void renderWireframe3D(); + template void renderWireframe2D(); + template void renderWireframe3D(); #ifndef MAGNUM_TARGET_GLES2 - void renderObjectVertexPrimitiveId2D(); - void renderObjectVertexPrimitiveId3D(); + template void renderObjectVertexPrimitiveId2D(); + template void renderObjectVertexPrimitiveId3D(); #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + /* This tests something that's irrelevant to UBOs */ void renderWireframe3DPerspective(); - void renderTangentBitangentNormal(); + template void renderTangentBitangentNormal(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); #endif private: @@ -146,6 +191,8 @@ constexpr struct { const char* name; MeshVisualizerGL2D::Flags flags; } ConstructData2D[] { + /* Whatever is added here should probably go also into + ConstructUniformBuffersData2D */ #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) {"wireframe", MeshVisualizerGL2D::Flag::Wireframe}, #endif @@ -160,10 +207,36 @@ constexpr struct { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizerGL2D::Flags flags; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData2D[] { + {"classic fallback", MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 1}, + {"", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 1}, + {"multiple materials, draws", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 64, 128}, + /* The rest is basically a copy of ConstructData2D with UniformBuffers + added */ + #ifndef MAGNUM_TARGET_WEBGL + {"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::InstancedObjectId, 1, 1}, + {"vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::VertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::PrimitiveId, 1, 1}, + #endif + {"primitive ID from vertex ID", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId, 1, 1} +}; +#endif + constexpr struct { const char* name; MeshVisualizerGL3D::Flags flags; } ConstructData3D[] { + /* Whatever is added here should probably go also into + ConstructUniformBuffersData3D */ #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) {"wireframe", MeshVisualizerGL3D::Flag::Wireframe}, #endif @@ -190,6 +263,42 @@ constexpr struct { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizerGL3D::Flags flags; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData3D[] { + {"classic fallback", MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 1}, + {"", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 1}, + {"multiple materials, draws", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 64, 128}, + /* The rest is basically a copy of ConstructData2D with UniformBuffers + added */ + #ifndef MAGNUM_TARGET_WEBGL + {"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::InstancedObjectId, 1, 1}, + {"vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::VertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"primitive ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::PrimitiveId, 1, 1}, + #endif + {"primitive ID from vertex ID", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId, 1, 1}, + #ifndef MAGNUM_TARGET_WEBGL + {"tangent direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection, 1, 1}, + {"bitangent direction from tangent", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection, 1, 1}, + {"bitangent direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::BitangentDirection, 1, 1}, + {"normal direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"tbn direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"tbn direction with bitangent from tangent", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"wireframe + vertex id", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId, 1, 1}, + {"wireframe + t/n direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"wireframe + object id + t/n direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::InstancedObjectId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, 1, 1}, + {"wireframe + vertex id + t/b direction", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::VertexId|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection, 1, 1} + #endif +}; +#endif + constexpr struct { const char* name; MeshVisualizerGL2D::Flags flags; @@ -213,6 +322,20 @@ constexpr struct { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizerGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData2D[] { + {"zero draws", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 1, 0, + "draw count can't be zero"}, + {"zero materials", MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 0, 1, + "material count can't be zero"} +}; +#endif + constexpr struct { const char* name; MeshVisualizerGL3D::Flags flags; @@ -244,6 +367,20 @@ constexpr struct { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + MeshVisualizerGL3D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData3D[] { + {"zero draws", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 1, 0, + "draw count can't be zero"}, + {"zero materials", MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 0, 1, + "material count can't be zero"}, +}; +#endif + #ifndef MAGNUM_TARGET_GLES2 constexpr struct { const char* name; @@ -401,42 +538,168 @@ constexpr struct { }; #endif +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected; + MeshVisualizerGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData2D[] { + #ifndef MAGNUM_TARGET_WEBGL + {"bind with offset, wireframe", "multidraw-wireframe2D.tga", + MeshVisualizerGL2D::Flag::Wireframe, + 1, 1, 16, 0.0f, 0.0f}, + #endif + {"bind with offset, w/o GS", "multidraw-wireframe-nogeo2D.tga", + MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, + 1, 1, 16, 0.0f, 0.0f}, + {"bind with offset, vertex ID", "multidraw-vertexid2D.tga", + MeshVisualizerGL2D::Flag::VertexId, + 1, 1, 16, 0.0f, 0.0f}, + #ifndef MAGNUM_TARGET_WEBGL + {"draw offset, wireframe", "multidraw-wireframe2D.tga", + MeshVisualizerGL2D::Flag::Wireframe, + 2, 3, 1, 0.0f, 0.0f}, + #endif + {"draw offset, wireframe w/o GS", "multidraw-wireframe-nogeo2D.tga", + MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, + 2, 3, 1, 0.0f, 0.0f}, + {"draw offset, vertex ID", "multidraw-vertexid2D.tga", + MeshVisualizerGL2D::Flag::VertexId, + 2, 3, 1, 0.0f, 0.0f} +}; + +constexpr struct { + const char* name; + const char* expected; + MeshVisualizerGL3D::Flags flags; + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData3D[] { + #ifndef MAGNUM_TARGET_WEBGL + {"bind with offset, wireframe", "multidraw-wireframe3D.tga", + MeshVisualizerGL3D::Flag::Wireframe, + 1, 1, 16, 0.0f, 0.0f}, + {"bind with offset, wireframe + TBN", "multidraw-wireframe-tbn3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, + 1, 1, 16, 0.0f, 0.0f}, + #endif + {"bind with offset, wireframe w/o GS", "multidraw-wireframe-nogeo3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, + 1, 1, 16, 0.0f, 0.0f}, + {"bind with offset, vertex ID", "multidraw-vertexid3D.tga", + MeshVisualizerGL3D::Flag::VertexId, + 1, 1, 16, 0.0f, 0.0f}, + #ifndef MAGNUM_TARGET_WEBGL + {"draw offset, wireframe", "multidraw-wireframe3D.tga", + MeshVisualizerGL3D::Flag::Wireframe, + 2, 3, 1, 0.0f, 0.0f}, + {"draw offset, wireframe + TBN", "multidraw-wireframe-tbn3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection, + 2, 3, 1, 0.0f, 0.0f}, + #endif + {"draw offset, wireframe w/o GS", "multidraw-wireframe-nogeo3D.tga", + MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, + 2, 3, 1, 0.0f, 0.0f}, + {"draw offset, vertex ID", "multidraw-vertexid3D.tga", + MeshVisualizerGL3D::Flag::VertexId, + 2, 3, 1, 0.0f, 0.0f} +}; +#endif + MeshVisualizerGLTest::MeshVisualizerGLTest() { addInstancedTests({&MeshVisualizerGLTest::construct2D}, Containers::arraySize(ConstructData2D)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers2D}, + Containers::arraySize(ConstructUniformBuffersData2D)); + #endif + addInstancedTests({&MeshVisualizerGLTest::construct3D}, Containers::arraySize(ConstructData3D)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers3D}, + Containers::arraySize(ConstructUniformBuffersData3D)); + #endif + addInstancedTests({&MeshVisualizerGLTest::construct2DInvalid}, Containers::arraySize(ConstructInvalidData2D)); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers2DInvalid}, + Containers::arraySize(ConstructUniformBuffersInvalidData2D)); + #endif + addInstancedTests({&MeshVisualizerGLTest::construct3DInvalid}, Containers::arraySize(ConstructInvalidData3D)); - addTests({&MeshVisualizerGLTest::constructMove2D, - &MeshVisualizerGLTest::constructMove3D, + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::constructUniformBuffers3DInvalid}, + Containers::arraySize(ConstructUniformBuffersInvalidData3D)); + #endif + + addTests({ + &MeshVisualizerGLTest::constructMove2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::constructMoveUniformBuffers2D, + #endif + &MeshVisualizerGLTest::constructMove3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::constructMoveUniformBuffers3D, + #endif - &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 - }); + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D, + &MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D, + &MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled2D, + &MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled3D, + #endif + &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 + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::setWrongDrawOffset2D, + &MeshVisualizerGLTest::setWrongDrawOffset3D + #endif + }); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - addTests({&MeshVisualizerGLTest::renderDefaultsWireframe2D, - &MeshVisualizerGLTest::renderDefaultsWireframe3D}, + addTests({ + &MeshVisualizerGLTest::renderDefaultsWireframe2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsWireframe2D, + #endif + &MeshVisualizerGLTest::renderDefaultsWireframe3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsWireframe3D, + #endif + }, &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&MeshVisualizerGLTest::renderDefaultsObjectId2D, - &MeshVisualizerGLTest::renderDefaultsObjectId3D}, + addInstancedTests({ + &MeshVisualizerGLTest::renderDefaultsObjectId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsObjectId2D, + #endif + &MeshVisualizerGLTest::renderDefaultsObjectId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsObjectId3D, + #endif + }, Containers::arraySize(ObjectIdDefaultsData), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); @@ -445,30 +708,63 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { #ifndef MAGNUM_TARGET_GLES2 addTests({ &MeshVisualizerGLTest::renderDefaultsVertexId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsVertexId2D, + #endif &MeshVisualizerGLTest::renderDefaultsVertexId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsVertexId3D, + #endif &MeshVisualizerGLTest::renderDefaultsPrimitiveId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsPrimitiveId2D, + #endif &MeshVisualizerGLTest::renderDefaultsPrimitiveId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsPrimitiveId3D, + #endif #ifndef MAGNUM_TARGET_WEBGL - &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal + &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal, + #endif #endif }, &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif - addInstancedTests({&MeshVisualizerGLTest::renderWireframe2D}, + addInstancedTests({ + &MeshVisualizerGLTest::renderWireframe2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderWireframe2D, + #endif + }, Containers::arraySize(WireframeData2D), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); - addInstancedTests({&MeshVisualizerGLTest::renderWireframe3D}, + addInstancedTests({ + &MeshVisualizerGLTest::renderWireframe3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderWireframe3D, + #endif + }, Containers::arraySize(WireframeData3D), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D, - &MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D}, + addInstancedTests({ + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D, + #endif + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D, + #endif + }, Containers::arraySize(ObjectVertexPrimitiveIdData), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); @@ -479,12 +775,29 @@ MeshVisualizerGLTest::MeshVisualizerGLTest() { &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); - addInstancedTests({&MeshVisualizerGLTest::renderTangentBitangentNormal}, + addInstancedTests({ + &MeshVisualizerGLTest::renderTangentBitangentNormal, + #ifndef MAGNUM_TARGET_GLES2 + &MeshVisualizerGLTest::renderTangentBitangentNormal, + #endif + }, Containers::arraySize(TangentBitangentNormalData), &MeshVisualizerGLTest::renderSetup, &MeshVisualizerGLTest::renderTeardown); #endif + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&MeshVisualizerGLTest::renderMulti2D}, + Containers::arraySize(RenderMultiData2D), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + + addInstancedTests({&MeshVisualizerGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData3D), + &MeshVisualizerGLTest::renderSetup, + &MeshVisualizerGLTest::renderTeardown); + #endif + /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -582,6 +895,72 @@ void MeshVisualizerGLTest::construct2D() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructUniformBuffers2D() { + auto&& data = ConstructUniformBuffersData2D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL2D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags & MeshVisualizerGL2D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL2D::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 + + #ifndef MAGNUM_TARGET_WEBGL + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if(data.flags & MeshVisualizerGL2D::Flag::UniformBuffers && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + MeshVisualizerGL2D shader{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + void MeshVisualizerGLTest::construct3D() { auto&& data = ConstructData3D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -641,6 +1020,72 @@ void MeshVisualizerGLTest::construct3D() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructUniformBuffers3D() { + auto&& data = ConstructUniformBuffersData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId && + #ifndef MAGNUM_TARGET_GLES + !GL::Context::current().isVersionSupported(GL::Version::GL300) + #else + !GL::Context::current().isVersionSupported(GL::Version::GLES300) + #endif + ) CORRADE_SKIP("gl_VertexID not supported."); + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId && !(data.flags >= MeshVisualizerGL3D::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 + + #ifndef MAGNUM_TARGET_WEBGL + if(((data.flags & MeshVisualizerGL3D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader)) || (data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection))) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + + #ifdef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + CORRADE_INFO("Using" << GL::Extensions::NV::shader_noperspective_interpolation::string()); + #endif + } + #endif + + #ifndef MAGNUM_TARGET_GLES + if(data.flags & MeshVisualizerGL3D::Flag::UniformBuffers && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + MeshVisualizerGL3D shader{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + void MeshVisualizerGLTest::construct2DInvalid() { auto&& data = ConstructInvalidData2D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -655,6 +1100,27 @@ void MeshVisualizerGLTest::construct2DInvalid() { CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL{}\n", data.message)); } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructUniformBuffers2DInvalid() { + auto&& data = ConstructUniformBuffersInvalidData2D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MeshVisualizerGL2D{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL2D: {}\n", data.message)); +} +#endif + void MeshVisualizerGLTest::construct3DInvalid() { auto&& data = ConstructInvalidData3D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -669,6 +1135,27 @@ void MeshVisualizerGLTest::construct3DInvalid() { CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL{}\n", data.message)); } +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructUniformBuffers3DInvalid() { + auto&& data = ConstructUniformBuffersInvalidData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MeshVisualizerGL3D{data.flags, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::MeshVisualizerGL3D: {}\n", data.message)); +} +#endif + void MeshVisualizerGLTest::constructMove2D() { MeshVisualizerGL2D a{MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}; const GLuint id = a.id(); @@ -688,24 +1175,219 @@ void MeshVisualizerGLTest::constructMove2D() { CORRADE_VERIFY(!b.id()); } -void MeshVisualizerGLTest::constructMove3D() { - MeshVisualizerGL3D a{MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}; - const GLuint id = a.id(); - CORRADE_VERIFY(id); +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructMoveUniformBuffers2D() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + MeshVisualizerGL2D a{MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + MeshVisualizerGL2D b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + MeshVisualizerGL2D c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +void MeshVisualizerGLTest::constructMove3D() { + MeshVisualizerGL3D a{MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + MeshVisualizerGL3D b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_VERIFY(!a.id()); + + MeshVisualizerGL3D c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_VERIFY(!b.id()); +} + +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::constructMoveUniformBuffers3D() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + MeshVisualizerGL3D a{MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + MeshVisualizerGL3D b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + MeshVisualizerGL3D c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::setUniformUniformBuffersEnabled2D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}; + shader.setTransformationProjectionMatrix({}) + /* setViewportSize() works on both UBOs and classic */ + .setColor({}) + .setWireframeColor({}) + .setWireframeWidth({}) + .setColorMapTransformation({}, {}) + .setSmoothness({}); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL2D::setTransformationProjectionMatrix(): 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" + "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::setSmoothness(): the shader was created with uniform buffers enabled\n"); +} + +void MeshVisualizerGLTest::setUniformUniformBuffersEnabled3D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}; + shader.setProjectionMatrix({}) + .setTransformationMatrix({}) + /* setViewportSize() works on both UBOs and classic */ + .setColor({}) + .setWireframeColor({}) + .setWireframeWidth({}) + .setColorMapTransformation({}, {}) + .setSmoothness({}); + 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::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" + "Shaders::MeshVisualizerGL::setColorMapTransformation(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setSmoothness(): the shader was created with uniform buffers enabled\n"); + + out.str({}); + + #ifndef MAGNUM_TARGET_WEBGL + shader + .setNormalMatrix({}) + .setLineWidth({}) + .setLineLength({}); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL3D::setNormalMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setLineWidth(): the shader was created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::setLineLength(): the shader was created with uniform buffers enabled\n"); + #endif +} + +void MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled2D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader}; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL2D::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} - MAGNUM_VERIFY_NO_GL_ERROR(); +void MeshVisualizerGLTest::bindBufferUniformBuffersNotEnabled3D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif - MeshVisualizerGL3D b{std::move(a)}; - CORRADE_COMPARE(b.id(), id); - CORRADE_COMPARE(b.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); - CORRADE_VERIFY(!a.id()); + std::ostringstream out; + Error redirectError{&out}; - MeshVisualizerGL3D c{NoCreate}; - c = std::move(b); - CORRADE_COMPARE(c.id(), id); - CORRADE_COMPARE(c.flags(), MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader); - CORRADE_VERIFY(!b.id()); + GL::Buffer buffer; + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader}; + shader.bindProjectionBuffer(buffer) + .bindProjectionBuffer(buffer, 0, 16) + .bindTransformationBuffer(buffer) + .bindTransformationBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL3D::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::MeshVisualizerGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); } +#endif void MeshVisualizerGLTest::setWireframeNotEnabled2D() { #ifdef CORRADE_NO_ASSERT @@ -843,6 +1525,44 @@ void MeshVisualizerGLTest::setTangentBitangentNormalNotEnabled3D() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::setWrongDrawOffset2D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MeshVisualizerGL2D{MeshVisualizerGL2D::Flag::UniformBuffers|MeshVisualizerGL2D::Flag::Wireframe|MeshVisualizerGL2D::Flag::NoGeometryShader, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} + +void MeshVisualizerGLTest::setWrongDrawOffset3D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::UniformBuffers|MeshVisualizerGL3D::Flag::Wireframe|MeshVisualizerGL3D::Flag::NoGeometryShader, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::MeshVisualizerGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void MeshVisualizerGLTest::renderSetup() { @@ -880,7 +1600,16 @@ void MeshVisualizerGLTest::renderTeardown() { } #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) -void MeshVisualizerGLTest::renderDefaultsWireframe2D() { +template void MeshVisualizerGLTest::renderDefaultsWireframe2D() { + 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::ARB::geometry_shader4::string() << "is not supported."); @@ -896,8 +1625,26 @@ void MeshVisualizerGLTest::renderDefaultsWireframe2D() { GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(16)); - MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::Wireframe}; - shader.draw(circle); + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::Wireframe|flag}; + + 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(); @@ -915,9 +1662,26 @@ void MeshVisualizerGLTest::renderDefaultsWireframe2D() { } /** @todo make this unnecessary */ - shader - .setViewportSize({80, 80}) - .draw(circle); + shader.setViewportSize({80, 80}); + + 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(); @@ -929,7 +1693,16 @@ void MeshVisualizerGLTest::renderDefaultsWireframe2D() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.082f})); } -void MeshVisualizerGLTest::renderDefaultsWireframe3D() { +template void MeshVisualizerGLTest::renderDefaultsWireframe3D() { + 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::ARB::geometry_shader4::string() << "is not supported."); @@ -945,8 +1718,30 @@ void MeshVisualizerGLTest::renderDefaultsWireframe3D() { GL::Mesh sphere = MeshTools::compile(Primitives::icosphereSolid(1)); - MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::Wireframe}; - shader.draw(sphere); + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::Wireframe|flag}; + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(sphere); + } 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(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -964,9 +1759,30 @@ void MeshVisualizerGLTest::renderDefaultsWireframe3D() { } /** @todo make this unnecessary */ - shader - .setViewportSize({80, 80}) - .draw(sphere); + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(sphere); + } 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(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -980,10 +1796,19 @@ void MeshVisualizerGLTest::renderDefaultsWireframe3D() { #endif #ifndef MAGNUM_TARGET_GLES2 -void MeshVisualizerGLTest::renderDefaultsObjectId2D() { +template void MeshVisualizerGLTest::renderDefaultsObjectId2D() { auto&& data = ObjectIdDefaultsData[testCaseInstanceId()]; setTestCaseDescription(data.name); + 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."); @@ -1011,9 +1836,27 @@ void MeshVisualizerGLTest::renderDefaultsObjectId2D() { Containers::arrayView(ids)} })); - MeshVisualizerGL2D{MeshVisualizerGL2D::Flag::InstancedObjectId} - .bindColorMapTexture(colorMapTexture) - .draw(circle); + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::InstancedObjectId|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(); @@ -1029,10 +1872,19 @@ void MeshVisualizerGLTest::renderDefaultsObjectId2D() { (DebugTools::CompareImageToFile{_manager, 150.67f, 0.45f})); } -void MeshVisualizerGLTest::renderDefaultsObjectId3D() { +template void MeshVisualizerGLTest::renderDefaultsObjectId3D() { auto&& data = ObjectIdDefaultsData[testCaseInstanceId()]; setTestCaseDescription(data.name); + 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."); @@ -1060,9 +1912,31 @@ void MeshVisualizerGLTest::renderDefaultsObjectId3D() { Containers::arrayView(ids)} })); - MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::InstancedObjectId} - .bindColorMapTexture(colorMapTexture) - .draw(icosphere); + 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(); @@ -1078,7 +1952,16 @@ void MeshVisualizerGLTest::renderDefaultsObjectId3D() { (DebugTools::CompareImageToFile{_manager, 150.67f, 0.165f})); } -void MeshVisualizerGLTest::renderDefaultsVertexId2D() { +template void MeshVisualizerGLTest::renderDefaultsVertexId2D() { + 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 + } + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1087,9 +1970,29 @@ void MeshVisualizerGLTest::renderDefaultsVertexId2D() { if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP("gl_VertexID not supported"); - MeshVisualizerGL2D{MeshVisualizerGL2D::Flag::VertexId} - .bindColorMapTexture(_colorMapTexture) - .draw(MeshTools::compile(Primitives::circle2DSolid(16))); + GL::Mesh circle = MeshTools::compile(Primitives::circle2DSolid(16)); + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::VertexId|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(); @@ -1100,7 +2003,16 @@ void MeshVisualizerGLTest::renderDefaultsVertexId2D() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.017f})); } -void MeshVisualizerGLTest::renderDefaultsVertexId3D() { +template void MeshVisualizerGLTest::renderDefaultsVertexId3D() { + 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 + } + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1109,9 +2021,33 @@ void MeshVisualizerGLTest::renderDefaultsVertexId3D() { if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP("gl_VertexID not supported"); - MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::VertexId} - .bindColorMapTexture(_colorMapTexture) - .draw(MeshTools::compile(Primitives::icosphereSolid(0))); + GL::Mesh icosphere = MeshTools::compile(Primitives::icosphereSolid(0)); + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::VertexId|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(); @@ -1122,7 +2058,16 @@ void MeshVisualizerGLTest::renderDefaultsVertexId3D() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.012f})); } -void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { +template void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { + 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 + } + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1160,9 +2105,29 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { if(flags >= MeshVisualizerGL2D::Flag::PrimitiveIdFromVertexId) circleData = MeshTools::duplicate(MeshTools::generateIndices(circleData)); - MeshVisualizerGL2D{flags} - .bindColorMapTexture(_colorMapTexture) - .draw(MeshTools::compile(circleData)); + GL::Mesh circle = MeshTools::compile(circleData); + + MeshVisualizerGL2D shader{flags|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(); @@ -1174,7 +2139,16 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId2D() { (DebugTools::CompareImageToFile{_manager, 76.67f, 0.23f})); } -void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { +template void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { + 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 + } + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1212,9 +2186,33 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { if(flags >= MeshVisualizerGL3D::Flag::PrimitiveIdFromVertexId) icosphereData = MeshTools::duplicate(icosphereData); - MeshVisualizerGL3D{flags} - .bindColorMapTexture(_colorMapTexture) - .draw(MeshTools::compile(icosphereData)); + GL::Mesh icosphere = MeshTools::compile(icosphereData); + + MeshVisualizerGL3D shader{flags|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(); @@ -1228,7 +2226,16 @@ void MeshVisualizerGLTest::renderDefaultsPrimitiveId3D() { #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) -void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { +template void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { + 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::ARB::geometry_shader4::string() << "is not supported."); @@ -1240,11 +2247,34 @@ void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(4, 8, Primitives::UVSphereFlag::Tangents)); - MeshVisualizerGL3D{MeshVisualizerGL3D::Flag::TangentDirection| + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::TangentDirection| MeshVisualizerGL3D::Flag::BitangentFromTangentDirection| - MeshVisualizerGL3D::Flag::NormalDirection} - .setViewportSize({80, 80}) /** @todo make this unnecessary */ - .draw(sphere); + MeshVisualizerGL3D::Flag::NormalDirection|flag}; + /** @todo make this unnecessary */ + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.draw(sphere); + } 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(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1263,10 +2293,21 @@ void MeshVisualizerGLTest::renderDefaultsTangentBitangentNormal() { } #endif -void MeshVisualizerGLTest::renderWireframe2D() { +template void MeshVisualizerGLTest::renderWireframe2D() { auto&& data = WireframeData2D[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) #ifndef MAGNUM_TARGET_GLES if(!(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) @@ -1305,14 +2346,42 @@ void MeshVisualizerGLTest::renderWireframe2D() { } } else circle = MeshTools::compile(circleData); - MeshVisualizerGL2D{data.flags|MeshVisualizerGL2D::Flag::Wireframe} - .setColor(0xffff99_rgbf) - .setWireframeColor(0x9999ff_rgbf) - .setWireframeWidth(data.width) - .setSmoothness(data.smoothness) - .setViewportSize({80, 80}) - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) - .draw(circle); + MeshVisualizerGL2D shader{data.flags|MeshVisualizerGL2D::Flag::Wireframe|flag}; + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL2D::Flag{}) { + shader + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .setWireframeWidth(data.width) + .setSmoothness(data.smoothness) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .setWireframeWidth(data.width) + .setSmoothness(data.smoothness) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1355,10 +2424,21 @@ void MeshVisualizerGLTest::renderWireframe2D() { } } -void MeshVisualizerGLTest::renderWireframe3D() { +template void MeshVisualizerGLTest::renderWireframe3D() { auto&& data = WireframeData3D[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) #ifndef MAGNUM_TARGET_GLES if(!(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) && !GL::Context::current().isExtensionSupported()) @@ -1395,18 +2475,55 @@ void MeshVisualizerGLTest::renderWireframe3D() { } } else sphere = MeshTools::compile(sphereData); - MeshVisualizerGL3D{data.flags|MeshVisualizerGL3D::Flag::Wireframe} - .setColor(0xffff99_rgbf) - .setWireframeColor(0x9999ff_rgbf) - .setWireframeWidth(data.width) - .setSmoothness(data.smoothness) - .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)) - .draw(sphere); + MeshVisualizerGL3D shader{data.flags|MeshVisualizerGL3D::Flag::Wireframe|flag}; + shader.setViewportSize({80, 80}); + + if(flag == MeshVisualizerGL3D::Flag{}) { + shader + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .setWireframeWidth(data.width) + .setSmoothness(data.smoothness) + .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)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerMaterialUniform{} + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf) + .setWireframeWidth(data.width) + .setSmoothness(data.smoothness) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1456,10 +2573,19 @@ void MeshVisualizerGLTest::renderWireframe3D() { } #ifndef MAGNUM_TARGET_GLES2 -void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { +template void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { auto&& data = ObjectVertexPrimitiveIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); + 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((data.flags2D & MeshVisualizerGL2D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1514,32 +2640,55 @@ void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { GL::Mesh circle = MeshTools::compile(circleData); - MeshVisualizerGL2D shader{data.flags2D}; + MeshVisualizerGL2D shader{data.flags2D|flag}; 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})) .bindColorMapTexture(_colorMapTexture); - /* OTOH the wireframe color should stay at full channels, not mixed */ - if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) - shader.setWireframeColor(0xffffff_rgbf); - - /* 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 */ - else - shader.setColorMapTransformation(0.5f, -1.0f/16.0f); - - shader.draw(circle); + if(flag == MeshVisualizerGL2D::Flag{}) { + /* Remove blue so it's clear the (wireframe) background and mapped ID + colors got mixed */ + shader.setColor(0xffff00_rgbf) + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})); + /* OTOH the wireframe color should stay at full channels, not mixed */ + if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) + shader.setWireframeColor(0xffffff_rgbf); + /* 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 */ + else + shader.setColorMapTransformation(0.5f, -1.0f/16.0f); + shader.draw(circle); + } else if(flag == MeshVisualizerGL2D::Flag::UniformBuffers) { + /* See above for comments */ + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform2D{} + }}; + MeshVisualizerMaterialUniform materialUniformData[1]; + materialUniformData->setColor(0xffff00_rgbf); + if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) + materialUniformData->setWireframeColor(0xffffff_rgbf); + if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) + materialUniformData->setColorMapTransformation(1.0f, -1.0f/17.0f); + else + materialUniformData->setColorMapTransformation(0.5f, -1.0f/16.0f); + GL::Buffer materialUniform{materialUniformData}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(circle); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1556,10 +2705,19 @@ void MeshVisualizerGLTest::renderObjectVertexPrimitiveId2D() { (DebugTools::CompareImageToFile{_manager, 4.0f, 0.141f})); } -void MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D() { +template void MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D() { auto&& data = ObjectVertexPrimitiveIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); + 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((data.flags3D & MeshVisualizerGL3D::Flag::InstancedObjectId) && !GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1607,37 +2765,70 @@ void MeshVisualizerGLTest::renderObjectVertexPrimitiveId3D() { data.flags3D & MeshVisualizerGL3D::Flag::NoGeometryShader) icosphereData = MeshTools::duplicate(icosphereData); - GL::Mesh circle = MeshTools::compile(icosphereData); + GL::Mesh icosphere = MeshTools::compile(icosphereData); - MeshVisualizerGL3D shader{data.flags3D}; + MeshVisualizerGL3D shader{data.flags3D|flag}; 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)) .bindColorMapTexture(_colorMapTexture); - /* OTOH the wireframe color should stay at full channels, not mixed */ - if(data.flags2D & MeshVisualizerGL2D::Flag::Wireframe) - shader.setWireframeColor(0xffffff_rgbf); - - /* 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.flags2D & MeshVisualizerGL2D::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 */ - else - shader.setColorMapTransformation(0.5f, -1.0f/40.0f); - - shader.draw(circle); + if(flag == MeshVisualizerGL3D::Flag{}) { + /* Remove blue so it's clear the wireframe background and mapped ID + colors got mixed */ + shader.setColor(0xffff00_rgbf) + .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)); + /* OTOH the wireframe color should stay at full channels, not mixed */ + if(data.flags2D & MeshVisualizerGL2D::Flag::Wireframe) + shader.setWireframeColor(0xffffff_rgbf); + /* 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.flags2D & MeshVisualizerGL2D::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 */ + else + shader.setColorMapTransformation(0.5f, -1.0f/40.0f); + shader.draw(icosphere); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + /* See above for comments */ + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + }}; + MeshVisualizerMaterialUniform materialUniformData[1]; + materialUniformData->setColor(0xffff00_rgbf); + if(data.flags3D & MeshVisualizerGL3D::Flag::Wireframe) + materialUniformData->setWireframeColor(0xffffff_rgbf); + if(data.flags2D & MeshVisualizerGL2D::Flag::VertexId) + materialUniformData->setColorMapTransformation(1.0f, -1.0f/42.0f); + else + materialUniformData->setColorMapTransformation(0.5f, -1.0f/40.0f); + GL::Buffer materialUniform{materialUniformData}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(icosphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1707,10 +2898,19 @@ void MeshVisualizerGLTest::renderWireframe3DPerspective() { (DebugTools::CompareImageToFile{_manager, 0.667f, 0.002f})); } -void MeshVisualizerGLTest::renderTangentBitangentNormal() { +template void MeshVisualizerGLTest::renderTangentBitangentNormal() { auto&& data = TangentBitangentNormalData[testCaseInstanceId()]; setTestCaseDescription(data.name); + 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::ARB::geometry_shader4::string() << "is not supported."); @@ -1808,25 +3008,58 @@ void MeshVisualizerGLTest::renderTangentBitangentNormal() { .draw(mesh); } - MeshVisualizerGL3D shader{data.flags}; - shader - /** @todo make this unnecessary */ - .setViewportSize({80, 80}) - .setTransformationMatrix(transformation) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setNormalMatrix(transformation.normalMatrix()*data.multiply) - .setSmoothness(data.smoothness) - .setLineLength(data.lineLength) - .setLineWidth(data.lineWidth); - - if(data.flags & MeshVisualizerGL3D::Flag::Wireframe) shader - .setColor(0xffff99_rgbf) - .setWireframeColor(0x9999ff_rgbf); - if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId) shader - .bindColorMapTexture(_colorMapTexture) - .setColorMapTransformation(1.0f/512.0f, 0.5f); + MeshVisualizerGL3D shader{data.flags|flag}; + /** @todo make this unnecessary */ + shader.setViewportSize({80, 80}); + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId) + shader.bindColorMapTexture(_colorMapTexture); - shader.draw(mesh); + if(flag == MeshVisualizerGL3D::Flag{}) { + shader.setTransformationMatrix(transformation) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setNormalMatrix(transformation.normalMatrix()*data.multiply) + .setSmoothness(data.smoothness) + .setLineLength(data.lineLength) + .setLineWidth(data.lineWidth); + if(data.flags & MeshVisualizerGL3D::Flag::Wireframe) + shader + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf); + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId) + shader.setColorMapTransformation(1.0f/512.0f, 0.5f); + shader.draw(mesh); + } else if(flag == MeshVisualizerGL3D::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(transformation) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + MeshVisualizerDrawUniform3D{} + .setNormalMatrix(transformation.normalMatrix()*data.multiply) + }}; + MeshVisualizerMaterialUniform materialUniformData[1]; + (*materialUniformData) + .setSmoothness(data.smoothness) + .setLineLength(data.lineLength) + .setLineWidth(data.lineWidth); + if(data.flags & MeshVisualizerGL3D::Flag::Wireframe) + (*materialUniformData) + .setColor(0xffff99_rgbf) + .setWireframeColor(0x9999ff_rgbf); + if(data.flags & MeshVisualizerGL3D::Flag::PrimitiveId) + materialUniformData->setColorMapTransformation(1.0f/512.0f, 0.5f); + GL::Buffer materialUniform{materialUniformData}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(mesh); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1852,6 +3085,372 @@ void MeshVisualizerGLTest::renderTangentBitangentNormal() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +void MeshVisualizerGLTest::renderMulti2D() { + auto&& data = RenderMultiData2D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #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_WEBGL + if((data.flags & MeshVisualizerGL2D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader)) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + } + #endif + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(8)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid()); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3)); + /* For a GS-less wireframe we have to deindex the meshes */ + if(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) + for(Trade::MeshData* i: {&circleData, &squareData, &triangleData}) + *i = MeshTools::duplicate(*i); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + GL::MeshView circle{mesh}; + circle.setCount(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader ? + circleData.vertexCount() : circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader ? + squareData.vertexCount() : squareData.indexCount()); + if(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) + square.setBaseVertex(circleData.vertexCount()); + else + square.setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader ? + triangleData.vertexCount() : triangleData.indexCount()); + if(data.flags & MeshVisualizerGL2D::Flag::NoGeometryShader) + triangle.setBaseVertex(circleData.vertexCount() + squareData.vertexCount()); + else triangle.setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xffffcc_rgbf) + .setWireframeColor(0xcc0000_rgbf) + .setColorMapTransformation(0.5f/circleData.vertexCount(), 1.0f/circleData.vertexCount()); + materialData[1*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xccffff_rgbf) + .setWireframeColor(0x0000cc_rgbf) + .setWireframeWidth(2.5f) + .setColorMapTransformation(0.5f/triangleData.vertexCount(), 1.0f/triangleData.vertexCount()); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[2*data.uniformIncrement] = MeshVisualizerDrawUniform2D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + MeshVisualizerGL2D shader{MeshVisualizerGL2D::Flag::UniformBuffers|data.flags, data.materialCount, data.drawCount}; + shader.setViewportSize(Vector2{RenderSize}); + if(data.flags & MeshVisualizerGL2D::Flag::VertexId) + shader.bindColorMapTexture(_colorMapTexture); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(circle); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(square); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform2D), + sizeof(MeshVisualizerDrawUniform2D)); + shader.draw(triangle); + + /* Otherwise using the draw offset */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform); + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + }; + + 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."); + + /* + Wireframe case: + + - Circle should be lower left, pink with red wireframe + - Square lower right, cyan with thick blue wireframe + - Triangle up center, cyan with thick blue wireframe + + Vertex ID case: + + - Circle and triangle should have both almost the full color map + range, one tinted pink, one cyan + - Square tinted cyan, with just two colors + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "MeshVisualizerTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +void MeshVisualizerGLTest::renderMulti3D() { + auto&& data = RenderMultiData3D[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #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_WEBGL + if(((data.flags & MeshVisualizerGL3D::Flag::Wireframe) && !(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader)) || (data.flags & (MeshVisualizerGL3D::Flag::TangentDirection|MeshVisualizerGL3D::Flag::BitangentDirection|MeshVisualizerGL3D::Flag::BitangentFromTangentDirection|MeshVisualizerGL3D::Flag::NormalDirection))) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::geometry_shader4::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::geometry_shader::string() << "is not supported."); + #endif + } + #endif + + Trade::MeshData sphereData = MeshTools::interleave(Primitives::icosphereSolid(0), { + /* The icosphere doesn't have tangents and we don't use them, but + concatenate() will ignore the tangents of others if the first mesh + doesn't have them, so add a bogus data at least */ + Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, VertexFormat::Vector4, nullptr} + }); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid(Primitives::PlaneFlag::Tangents)); + Trade::MeshData coneData = Primitives::coneSolid(1, 8, 1.0f, Primitives::ConeFlag::Tangents); + /* For a GS-less wireframe we have to deindex the meshes */ + if(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) + for(Trade::MeshData* i: {&sphereData, &planeData, &coneData}) + *i = MeshTools::duplicate(*i); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader ? + sphereData.vertexCount() : sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader ? + planeData.vertexCount() : planeData.indexCount()); + if(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) + plane.setBaseVertex(sphereData.vertexCount()); + else + plane.setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader ? + coneData.vertexCount() : coneData.indexCount()); + if(data.flags & MeshVisualizerGL3D::Flag::NoGeometryShader) + cone.setBaseVertex(sphereData.vertexCount() + planeData.vertexCount()); + else cone.setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xffffcc_rgbf) + .setWireframeColor(0xcc0000_rgbf) + .setLineLength(0.0f) /* no TBN */ + .setColorMapTransformation(0.5f/sphereData.vertexCount(), 1.0f/sphereData.vertexCount()); + materialData[1*data.uniformIncrement] = MeshVisualizerMaterialUniform{} + .setColor(0xccffff_rgbf) + .setWireframeColor(0x0000cc_rgbf) + .setLineLength(0.25f) + .setWireframeWidth(2.5f) + .setColorMapTransformation(0.5f/coneData.vertexCount(), 1.0f/coneData.vertexCount()); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationData{2*data.uniformIncrement + 1}; + transformationData[0*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f}) + ); + transformationData[1*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationData[2*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f}) + ); + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, transformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material offsets are zero if we have single draw, as those are done with + UBO offset bindings instead. Also no need to supply a normal matrix. */ + drawData[0*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 0); + drawData[1*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + drawData[2*data.uniformIncrement] = MeshVisualizerDrawUniform3D{} + .setMaterialId(data.drawCount == 1 ? 0 : 1); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + MeshVisualizerGL3D shader{MeshVisualizerGL3D::Flag::UniformBuffers|data.flags, data.materialCount, data.drawCount}; + shader.setViewportSize(Vector2{RenderSize}) + .bindProjectionBuffer(projectionUniform); + if(data.flags & MeshVisualizerGL3D::Flag::VertexId) + shader.bindColorMapTexture(_colorMapTexture); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(sphere); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(plane); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(MeshVisualizerMaterialUniform), + sizeof(MeshVisualizerMaterialUniform)); + shader.bindTransformationBuffer(transformationUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(MeshVisualizerDrawUniform3D), + sizeof(MeshVisualizerDrawUniform3D)); + shader.draw(cone); + + /* Otherwise using the draw offset */ + } else { + shader.bindMaterialBuffer(materialUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform); + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + }; + + 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."); + + /* + Wireframe case: + + - Sphere should be lower left, pink with red wireframe (and no TBN) + - Plane lower right, cyan with thick blue wireframe and TBN + - Cone up center, cyan with thick blue wireframe and TBN + + Vertex ID case: + + - Sphere and cone should have both almost the full color map + range, one tinted pink, one cyan + - Plane tinted cyan, with just two colors + */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Directory::join({_testDir, "MeshVisualizerTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::MeshVisualizerGLTest) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp new file mode 100644 index 000000000..e9bad7799 --- /dev/null +++ b/src/Magnum/Shaders/Test/MeshVisualizerTest.cpp @@ -0,0 +1,302 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Shaders/MeshVisualizer.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct MeshVisualizerTest: TestSuite::Tester { + explicit MeshVisualizerTest(); + + template void uniformSize(); + + void drawUniform2DConstructDefault(); + void drawUniform2DConstructNoInit(); + void drawUniform2DSetters(); + void drawUniform2DMaterialIdPacking(); + + void drawUniform3DConstructDefault(); + void drawUniform3DConstructNoInit(); + void drawUniform3DSetters(); + void drawUniform3DMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); +}; + +MeshVisualizerTest::MeshVisualizerTest() { + addTests({&MeshVisualizerTest::uniformSize, + &MeshVisualizerTest::uniformSize, + &MeshVisualizerTest::uniformSize, + + &MeshVisualizerTest::drawUniform2DConstructDefault, + &MeshVisualizerTest::drawUniform2DConstructNoInit, + &MeshVisualizerTest::drawUniform2DSetters, + &MeshVisualizerTest::drawUniform2DMaterialIdPacking, + + &MeshVisualizerTest::drawUniform3DConstructDefault, + &MeshVisualizerTest::drawUniform3DConstructNoInit, + &MeshVisualizerTest::drawUniform3DSetters, + &MeshVisualizerTest::drawUniform3DMaterialIdPacking, + + &MeshVisualizerTest::materialUniformConstructDefault, + &MeshVisualizerTest::materialUniformConstructNoInit, + &MeshVisualizerTest::materialUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "MeshVisualizerDrawUniform2D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "MeshVisualizerDrawUniform3D"; } +}; +template<> struct UniformTraits { + static const char* name() { return "MeshVisualizerMaterialUniform"; } +}; + +template void MeshVisualizerTest::uniformSize() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment"); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment"); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768"); +} + +void MeshVisualizerTest::drawUniform2DConstructDefault() { + MeshVisualizerDrawUniform2D a; + MeshVisualizerDrawUniform2D b{DefaultInit}; + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + + constexpr MeshVisualizerDrawUniform2D ca; + constexpr MeshVisualizerDrawUniform2D cb{DefaultInit}; + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::drawUniform2DConstructNoInit() { + /* Testing only some fields, should be enough */ + MeshVisualizerDrawUniform2D a; + a.materialId = 73; + + new(&a) MeshVisualizerDrawUniform2D{NoInit}; + CORRADE_COMPARE(a.materialId, 73); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::drawUniform2DSetters() { + MeshVisualizerDrawUniform2D a; + a.setMaterialId(73); + CORRADE_COMPARE(a.materialId, 73); +} + +void MeshVisualizerTest::drawUniform2DMaterialIdPacking() { + MeshVisualizerDrawUniform2D a; + a.setMaterialId(13765); + /* materialId should be right at the beginning, in the low 16 bits on both + LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[0] & 0xffff, 13765); +} + +void MeshVisualizerTest::drawUniform3DConstructDefault() { + MeshVisualizerDrawUniform3D a; + MeshVisualizerDrawUniform3D b{DefaultInit}; + CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(b.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + + constexpr MeshVisualizerDrawUniform3D ca; + constexpr MeshVisualizerDrawUniform3D cb{DefaultInit}; + CORRADE_COMPARE(ca.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(cb.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::drawUniform3DConstructNoInit() { + /* Testing only some fields, should be enough */ + MeshVisualizerDrawUniform3D a; + a.normalMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + a.materialId = 5; + + new(&a) MeshVisualizerDrawUniform3D{NoInit}; + CORRADE_COMPARE(a.normalMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + CORRADE_COMPARE(a.materialId, 5); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::drawUniform3DSetters() { + MeshVisualizerDrawUniform3D a; + a.setNormalMatrix(Matrix4::rotationX(90.0_degf).normalMatrix()) + .setMaterialId(5); + CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f}, + Vector4{0.0f, -1.0f, 0.0f, 0.0f} + })); + CORRADE_COMPARE(a.materialId, 5); +} + +void MeshVisualizerTest::drawUniform3DMaterialIdPacking() { + MeshVisualizerDrawUniform3D a; + a.setMaterialId(13765); + /* The normalMatrix field is 3x4 floats, materialId should be right after + in the low 16 bits on both LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[12] & 0xffff, 13765); +} + +void MeshVisualizerTest::materialUniformConstructDefault() { + MeshVisualizerMaterialUniform a; + MeshVisualizerMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.wireframeColor, 0x000000ff_rgbaf); + CORRADE_COMPARE(b.wireframeColor, 0x000000ff_rgbaf); + CORRADE_COMPARE(a.wireframeWidth, 1.0f); + CORRADE_COMPARE(b.wireframeWidth, 1.0f); + CORRADE_COMPARE(a.colorMapOffset, 1.0f/512.0f); + CORRADE_COMPARE(b.colorMapOffset, 1.0f/512.0f); + CORRADE_COMPARE(a.colorMapScale, 1.0f/256.0f); + CORRADE_COMPARE(b.colorMapScale, 1.0f/256.0f); + CORRADE_COMPARE(a.lineWidth, 1.0f); + CORRADE_COMPARE(b.lineWidth, 1.0f); + CORRADE_COMPARE(a.lineLength, 1.0f); + CORRADE_COMPARE(b.lineLength, 1.0f); + CORRADE_COMPARE(a.smoothness, 2.0f); + CORRADE_COMPARE(b.smoothness, 2.0f); + + constexpr MeshVisualizerMaterialUniform ca; + constexpr MeshVisualizerMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.wireframeColor, 0x000000ff_rgbaf); + CORRADE_COMPARE(cb.wireframeColor, 0x000000ff_rgbaf); + CORRADE_COMPARE(ca.wireframeWidth, 1.0f); + CORRADE_COMPARE(cb.wireframeWidth, 1.0f); + CORRADE_COMPARE(ca.colorMapOffset, 1.0f/512.0f); + CORRADE_COMPARE(cb.colorMapOffset, 1.0f/512.0f); + CORRADE_COMPARE(ca.colorMapScale, 1.0f/256.0f); + CORRADE_COMPARE(cb.colorMapScale, 1.0f/256.0f); + CORRADE_COMPARE(ca.lineWidth, 1.0f); + CORRADE_COMPARE(cb.lineWidth, 1.0f); + CORRADE_COMPARE(ca.lineLength, 1.0f); + CORRADE_COMPARE(cb.lineLength, 1.0f); + CORRADE_COMPARE(ca.smoothness, 2.0f); + CORRADE_COMPARE(cb.smoothness, 2.0f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + MeshVisualizerMaterialUniform a; + a.color = 0x354565fc_rgbaf; + a.lineWidth = 0.765f; + + new(&a) MeshVisualizerMaterialUniform{NoInit}; + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.lineWidth, 0.765f); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void MeshVisualizerTest::materialUniformSetters() { + MeshVisualizerMaterialUniform a; + a.setColor(0x354565fc_rgbaf) + .setWireframeColor(0x9876fadc_rgbaf) + .setWireframeWidth(3.5f) + .setColorMapTransformation(35.5f, 0.5f) + .setLineWidth(3.0f) + .setLineLength(4.0f) + .setSmoothness(5.0f); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.wireframeColor, 0x9876fadc_rgbaf); + CORRADE_COMPARE(a.wireframeWidth, 3.5f); + CORRADE_COMPARE(a.colorMapOffset, 35.5f); + CORRADE_COMPARE(a.colorMapScale, 0.5f); + CORRADE_COMPARE(a.lineWidth, 3.0f); + CORRADE_COMPARE(a.lineLength, 4.0f); + CORRADE_COMPARE(a.smoothness, 5.0f); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::MeshVisualizerTest) diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid2D.tga new file mode 100644 index 0000000000000000000000000000000000000000..fc8195fd8ee3878d24891fe5dc00caa5b6f17279 GIT binary patch literal 6639 zcmcIpXH-(}pVGYsqBSRE#mh@bn2Aw3xLFy?o57fAcdBs)RPxus%XSKCCAtPu4e{|?*>#bk|A z2fLF&W2tr1D8G5+uRfYttJUKJmF~ORg`q9n$VS`fI#&Eg)0D~qnXedy&lnFL4ScR( zR_2=5+!@+*)3*JJz4`)=Jn2mRB~b4bb?gu~ZjyZTm%Lu4M{xk(V#1EsaFSHKG$k*y zotM|jD{kbKe&!W7VZ}a{9Cnm6->PCPZ~cDOC+t2*(OOvp+ARgO}UJ zD{A81ujf6gM?Q6<%&D`=)yMInr{!hPkox$3;!OC)eP~{g^C~ za-V_B2kA7%J(#_CB@Mg>b-c0<+!vLcidXD+&xj9?h`M`3Ljlp8)t6{X!v#y+^0&Nu z_0Wj8sj4jf6^ZwciP}=4zL03jCR)?k z?a3TvJWqAWNpp@*o^;fl#|rL37wE++Z04q@M#PX-VRU@-nL-;_i&xMdW*{5u@b1Qy z2{*GVpA!xF?9K$P>awFc%7r{7B9D%w_Ipr&%E*9;*MGXi1T-Cln|-4bpfbL&ut;A+Nw z{*GpEO9oGU$(1@ZiCptr=M=vVsekL}jm`Wm4cy?*c6(~9{`_pWrgiiJ)tvF<25fUs zUj)rLuV3-9+mXS*>t6Dz?nE&p(jH{-8zT*5^e))@(2>`e3nB~16LK_h?KyuSSWuK|F-#9(| z0>1tX-!QiC0^W@Hq(IC$YtDeu00naFsJA~-X?pRA>E#FJ@e0<>e5=$<+r)gU3r|ds zzBLW0wO-dE0i_(&(cl@_jx%IPh#Co{hxpkS?4P0xWP2UbE18=c?^JMESRCbA5GP1aA91Z@$f4seZL%SkCM1hr6U{e_1G;F?7io~Ix{91KmfVQw+|VKd$s=AduiUo)-ohA> zBFw9Jzx0Q~T{2u0XGY~DI$V5g7E))kP&soXb&zOFrO%_BHrrDq4)0!LtGg2ly$XU4 zv3$e$L8rn_+E7G&5xXN1nbDKlh8(l1%^jf%qC`J7yOnK|ch#ljgnPlCvXoWw8_Q&= ztL6Ay5G%Y|G6ar7fO}Ef5IMyACph~@Tq4balzpbrQDDT3hjiM_EJ4*(19{{B^k_?S zR9_fP?L}r>+A$6(=Ww+JqV`6?(CnK$n7(+g^v(u(>@U&-V?86LduMHuV=*ar*s-^! z>s!PVNqd~dmvq&G-I0W7_M~>qRxeuF z?z!Xhh<~FwImbHpnsZ5nTj5Sw>Kb{}T&Er zyHy7IebJ*l$v`gA>7h+hT}U;4b0ZI=Itx-2{*P35|B%Db;1x^{>}P-Sj-D@#`&Ab2 zD@$B1OIaz0me`Y}hY!6WUz?=F&u$sW8C^Btsbi38CXuUAHUu^~9eh9J^b6+oV#~~1 zoZ|DYii6$-J7lnL@(P+WFgk$8`_R~L0lU9E1K0!Pxm#rUJ7jbo+b2~V^44o0DN7R> z#tk|RwAF=#3d4!DNI4y~#J-P~IEZl+WTX;B7sZMQH^3yOB!>Hm? zvTP8p`B(Tw(PH2#x*)+oVT1q%QhJ;niIu`wVg6N@FR1f!4PlnMf zMY6Sly{BdpH7vijb_V;6J;*?GkMQXVCcV;KM*Xo=GsBY% zz&~-i#UA6SHCsAIqlJi$qF6;!Ggf!2=Tz$VogJd&cLNW#IxHrCa?@<#H%H)RjBdks zGb!JFZO(Md^9AF4z11H|?}^kZG*fUtTDPb7JaS=J3kTiE+Vr819}G-L`cGT#Kw%j8hFZ{_1h#sC9z&P(c-~%6Z*> z6Xie^&f71y7vSJ@vy%^`r}* zEwQcb+KA3z@zdYL%AI0WG^e7#Ed325yvc5bda{(tiz*Ua^K<*^j1x>?+8cknG}y_pV73rq(b`3)7meTOPpm+t2YiZ-9A49KID?uH z6Rs0AD5PD8E-{QuMmT?1BZ=|ik7C#v4c=q){l*1D*;i%!w|-*Hei5wn^xoj4Dw+d} z$T?aLoD{uEbj`_wq!W5+2U{J}D;OW{vZ$l{%HPG2W1Nk9_!yZUFfu<(%k00y$YhgI zZ-3)&myPOgH`9VxVaMI>=y1f<8Q+^cQIJ1N*hSjFBhBormyEjGR^(wJ>=Pz-HtxCH zhyfu1M!nbKGuK2g-JDo%%h~GS5GHYk8IvXma%Tx2E)ZcqMCsLJL^KXBc+O}{wt+5X zi^L($j>f%K8Ta&K_L?}w%zmj2Cy3{8SmYGt;gUFBkTX;G_k2;guef@Z1VZ#??{~JI z_HRqVa8-!7biOE%>oC#O-p87=iOV}EbUNqma$}qTWVk`=Vwe6D&~rwE*8K2U0kOjry`sIqa%($km80ub@a$7 vXCP`iF0)&1*uv4crB6HEi=N_^Rdv=7Eb7yg@qO(tDp;XYz}IqK*R}rwx6`(6 literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-vertexid3D.tga new file mode 100644 index 0000000000000000000000000000000000000000..e225aca568339d3086bf4bf33bba56e091baab62 GIT binary patch literal 5818 zcmb`LcT^ME9>)!9K~$6;LKUP<8YP`ongn}U%i7lhSP&~>M-&BA5J4%TD4>Xlf?%Nt z0@4$^?uu&zv4YY|LXw&Hn=rDx^WHn>{SnT&=Z4AseeY+!^Si%0xkH9%4EgytXULo( zw6y-;b`{1_#x1<3wYXGgS*h+HWqPac8~Buy*H;*BtfXwNGTvTo5?F1zyT*Kf zjYV*+Wk{`6n8^B=$R<)`7cE9ki>dKqTB4YdB(_f$J6seyri(%GD71d%1;tv6O0<`j z>MSqQU3FjIyWC)HIe9|`WmBcm)+&>LYSUfSW_xSK2h~~}s-18|WEC#5i4fUFiIEsF zHC9YJBc`7fGtZ0dQ^gKxKb22{x+YzAEv~_^e82Fxm)pLHE-^FQ3%7VZIPG4NPfe*K z2e%>%<*OYP7m)7{VDvF)l(F-QRd}PIh~H%tw6RoaZp=qY)gfW#Dq;R6uWTRT>6spf z-Q9A0g`#7gm06A%Pbo*6%vbfz<6|)p(2w(5@i^7FL*Sf%UJL2TKk>z+HcVKy$Ezk( z_~4W~P!tDvU0LCIOz4)r+_Unaur`gCQ)3qWk>b}o*$q2Pgi|!<6sgcALNyY4ZKUfS zSRjh<6vudoFZ^0{ja8aQ18W|fajia#yO#X>>DzuDHjeuVFKpVB6=-NZ$l$YfAdUDhk6M&8QW_IokIZJE`i7vV*5J!4~ zWtl65S>D3haADPTr>jqmAU+%#W%P_fP1PAk&>e?fZ%>EAwMWKaM0^7oaxJ9AW@jf0 zQfWosf_0K9L2dMtpYd;RR-2dKWQvm}LNXR^^}4vkvm!`Xdr>d|IK5DX4ibR)>M{uE z;z4ydW__C=429^t1vMT1jBy)q}jL$K_xv0%|tf?8Xt>xR3y`7 znMiLQtuKp>CJ52J%VbNobvuVOTPD;i)3ey7CY=lUShm;ewx4i-bZY)+)p!tAsAgfEqUq4S-^|vy*-NXSPBv zi&C6L$31_Nj`GS^p{)cd{^-fm(d|t6WvIzea*!4g^5q4s{vG}07slfjMpYN%VJq!z z1Jd*a*kPD67BL(3_mR%n*UpSm=Ip&fJJ?1b4L~at8fBC)Z?x)c9Zc#gna)NNk>*E0 z#CXxf5Vte#_s|PuNS4Cp5{g_`FiIqhCqHQK-@!J9^yWJ#Qe3dG`3m1%9Zrx2dF7U{ zw4g-&B-QZDQ=zQ{DZbtfrlNt6jPzHZsJG;H8K_kfIz9$9IU*@g2461$27487$@ z3f12x(e~-mT+%jbcC+TBTk6IxysL~uTbd7ZO(WdcNo>2-*tnU`v z4q$rRR-vH;CSKe1ywKf9PXXhvuXdRVg8-@eiAq^#y7n!y_N|m<8B`;Dx|gAL6Hh|3yLi4QE?v(2k6{xX{gS@L3BEv z*gi0Y@x0kK9o5~`JKW_xpplI4{9;$D?%ptNb*Jeu1)h(OT99wgpn!T{2ViRo{cZ&! z%6(DY^`;8FBhc_yFKzIG`mv5tBsC3_kDAjw!nJ<*#J9r*ui?p#ng7FZZy&5{{IS9! z=_zo=qE`0J^Y3vR;Bv_wsXEsNacO}CNM|Y366+MyAW*8mRif+PqqDvnmdCi29q`#A zPWeFVJ|XQDPH55sg*OpSQJ<8fLca)A{Iz);Q=SI<20dQ}4-1MN)D_z_6acNxp_XS* zi&&JJt$?BpwVENzaK{b~5dE5?t~#d%5sh`jLXh@yTK!wvyL$TTMta?6#-HDysNq}3 zgEmHO2fl$+b^|q4avK7bFY+UtZ>Z2C0!R592bQ6|{36s+co%@(mMXakn*h>X1p6#t z!qP*2h>_;UNN3pqiy&KFb?y)>SD~kj;AgAwM*mHjmo-9l*o5-n-@oy2&HQtj$;(Ij zwRJjWZ*@xE>l8KU7B=eUebl@4SwHuSLH0MpYfY4^%|_|pO)s^WrLuG}GtL^eSKL z{5O5$%im2ewwNckS|qhiNNl%`@31B5hmmwaKZK-xkTfocGi#f=+VHhA4DAAG_$>I{C}(;6La*$d&h1Zn{qPyh#+QE#7PpD(ushXBJK4pE=yo7! z1(U|^SCa1{joLxd*rqmevzq!wwGr#phWY+7bhSX+X08#kht5cKbs}at1HExkgAVwU z_QN!_b^L`k%X95kXF6$heUk^QP#flLq%p-+*Ji4L9WXC3qpq=}`B~G0nanhIR`o*sS0k>U2#u2d z-Q;Y$bwrOtcrUA;Ge|mdZ=dU~K92yIaI0+UCix6M`RreYuAymi-1TgylkMgiBg@RG z-j+0f8~P3^1DKCE+F$;uj8iL>f%#M?6$tl9U3TMyp4((oHp{1Pl+RkPnCq*UzXtt% zCAws$q1^%#WVt!jcLHsbEj^IR2x2lr9ql7I4zU8qbCVnaxY3UXs<`r5Xk?%T*)8&E z{_>d{6m!-p=B+^&u0$6vN0%){|5${sUV!`!nV;slOKlbkNO zu`-3Y2_KZ2IH^+eu!IM)e(RO*!e#;IJu5ijP}ujh3K;c7UYQVkqijxL{$ZJvs40|ifXCn&g~dtK4} zE{X#a6$h_-DMed_u7ZowV#KbW)o^j(Cf~}d_B-TZs*-=oaQeq4!ykE!rxRLjD3m=Wab#Nl34)AzkJDiEwaGvZKw=aStjpRt8 zIFji94F4X$Z{5j9PjdP)XoSvr=(SzZAZ}kIM|y%IImzygVfUP7b;q*0<5^t^Ff}9q zOx?U4kL}pY?>@kjhVW!*bb>;uAdny7%fkCZSe=Qij&rQGBv#u6R%`O$K*=GVER-id z#+9csn~Tf|g$vM+gQ@$+vwozqzF%TBr?HyS+25|fgMg?$WfXdX3l3m$tk!%Bh{0!8 z#Fb*xMkH+V9``;Uh@hckI$eS&#%iDRF?~?Dg+f3g7Qki{mRySOYrSWscB<{ z(YqSMtZ#k;@dRnhZ_1-!nrJzU#PwCKOUjH4u`(l%SxeU!n!7|Uxr?7%6(}v~DZPPq z$$_>)me*)Z6GDrOM;~9f-wBhOv2;?LNNn)u$~!h4tPh-2$gUCtMFT)!P8DZoZyXIk zbPQ|v7XGHv@-p8&q$m}b5KA1ub&w~VQTC7XL??DkJ$*rO{Han$~(3=W}CxRq~HM*>p zk@1$2kuni|VW*K!G)R$4zVf7>f3}=Rq*EgCDn#&5*;AsZi$px_MC>e7DUb@6)@+%v zuuM)N$|;3yatcQ1ai}|}6|hCmN~C}iM^6xrVbud+zy~o5!u?5@+x*KTg9klvimw<5 zqf_uW6>wBMR4Lesa)5IT9DsU6U4ct--1*9maUpl)xY15=W97J1a5ZU%>5ZpF4}jrVEV>xRJchl8~;z_$w<32~j0k zy5T-p$Z;tK>bMj)39gWmXnbf^Feg-cJ1)yV4h(QVCAyFpXG+^qr~xfW$T+9al&VT- zG+Z=EKvV5^W6<^rgp^?5EMa8LLwFG1@2hh$C>{j8IrXd4JC)!QXAbQd9TeD9U_2D~ zsX|IJ3OH6_A=HE`joqPhOJPk?AF24Lv!H^dVBw1ifmK_e^ya`e?KISx2nBN2#il;} zv*k>v9;-8<8m?gCjhJJWaL^F?J;D!HU{k?FQq5i1Lx77QbF;f=(V8mIkhD+gQ0g?O zt4U~(6J0UPVBnC>0gM14%DShdj_d6dj^#cb9+)r3rzWkAPmNuH?}ACnKJC!$g;q#d zGH}XSv4V?3)VhRP9j_Q1Xf|ZzCS7}@IiQ3&7Lybv84M9<-)NtJ!auUy zp3oFjJsuIV7iojYF&EMtD_)DnV`cChC^U$BfgEN7^ky_k^eel4d_a{#p}-SmZzI8B zmnM3>^a2i}!fyn0TQokjBgPe#76D=vXS>77Zdl=^R&Yc=5QHBja5Q=j8Uynl{UF(k zfs$jRxhSdjb4G111@fGk=S><7KAps(9UXSeye_LBKKq^!H}%-e@_Qz4WI)Nx$r*00 z*ghs6x#H;Vk}w!4UpnVO{F=fEqfML^het#K0|c7>O3EEEIJE14D2QSpbR?RwLNon;@soz=%1QinuIPbT>^>qz0NK^g9b z+Jv?2_f%Ki-}R{*i>FO8yXqnPvnWsWU>P#CJJx4-xD`%(sWsy;d`u6OZ!bAr6a7X0 z*cZ{&p^z#n5oJ=cnmVe^|2O+}6UW{A>Z6|a>oz^BLD@M7cI`UE!oa_!pWsVlr=7TCY`pxe(zgoi`OOuz91q@yg?)#*(lQqySWH z-jD^L@mxaA^8*l}hTo};3jyfqyz%6pskl2-~tt zOyRb|lSOa5Y$EzTWjIKeQG7Rvoz_KWI2(eHfxP))k+>ht0AN-m2Tb zp(u0$XyDNHWa+tQ4|+Ct$e^-CGctX>aAbS#R37lYa{>lo(^ejYDk8ifsv>H&M!NSu DOo`Iz literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-nogeo3D.tga new file mode 100644 index 0000000000000000000000000000000000000000..00291cd9fdd1063349c2f454f4ecfeffa6d5f6a8 GIT binary patch literal 4183 zcma);2~?EV6~|}!W>}R~agB%z5H*!RqIL<9#8^{fNNi%$8WY8&X?jeW)-9(=47lP7 zNi}KXZlV=ErzeeiV$!;usu5;jh8c!MSp^XU9EAe14E_JV_vtso4Cj>dy)%68-Ftuc zzIX3^pGFg?x&N1{N!1L~6oiF^{l6mzvO2S*w?7&X1Kbq z^!1rO!O%hktC<*C7=vRI98bSg9vo5-G^oJfXAX_9%zvwT-jXUj&n~RMGiyOPp75a- zNYl0vb}L_fwx8*2Pt!Xf7&0c-4$}ca`4LgphbGukmsSlQQ>fFK5|c|e9BDq&+--tV zU*DB(SIYcy#kwD?w-@Dj1F}2;+aVtY)6U90H@*X4=Sb%`RtWVx{AH3)|yH;zOHmfZAlrzuO=gtH2 zoU>=m-li$jiy<>=obAnzZyd3Q1mmkP&0|Spc zH3RQ{`+sP|Ar1;Hz{W%H^D}otBbgSPFmw-~=}*`!Qqy`;QW`~u0CGe z0Y*B8-W|La6T0skyZ@BcFyWCRJ&piBKbdpY3!5<@c8e#LNi0c5GqQ5&2X&|GyFg%g z9@ur5Fy{95Ax31se_%eM)=NyEjTmaMYX_um_M|fFBzTYWTAtm269j; z@9p*VqsQBDx?a3>1M)I`^I+%AHFd{JxVc`u^_VoE{`OpKUAX)@QW_3=A=eLx+~kSm z#u|_jNF$Ub%H;eVM(OiN{XyM#xoy$P>*x}o4jpMlbMOnuhm8#w>JD!h5WmqIA0$cU zgeOY$dNT_0m%pn%aHTD;w|_3vp+LcZZDppjUz-KK<<89-{qdwXi0GG_0_e}@pg^YG-QM|HLYsLH_i*Kz3|%T zXPz;;%*zLEw{IVKME!jLdcm@qO*t(;oqHAa8W7Bq%{t)}jSKX}@7IovXPb!_W}UmJ za`W+)vn@O?H0HQR9nrCNxZ&jkv&^>-%uF{Q*i%AqqF#6$su ziK`6P8a77UKyp#MlUWbv(1^U4Y#zRuY&iXyfNcDG3pNqYzixHUSy%zHsE3dU3tT`m zxgy_1a=W>F&o%RfYv?OrdUnvQ12WlL-I%-ugOMuVHFP7*SM6*J8e9Th|ouVT!NCoxjpgR)ni3$ zV|c!<=v?th1D4p6hgxte?Y_|Z;%}?L_9F2<_wy!sVkGz&Im|jDx)3FT8+R>+5w~jE z2X*M6I7Hk!@kzYV+2zzQ_*v3FJygs5!qTD{jf}BnV(4%@Mt|67?XPA>rTCF1?&Jqz)JZ*C6lS!J=ClfK`UoC#+ z*O|(+`irH+(wRx?Fod-FYoyl9q~hsQEJMPIXG{SAoLzK|IG(=c1C=e;qb>mxTviFllC#A0X z)1^;6Rxmh7L`agX1DYc#K0hd+`4C$rbyOPPq~@>I>(qXSa3a5S)adQF9Mzo+S|eXX7#8PzDcgmOI2^Y04YJC-hNVSV@KFx zMz;O1UsYp?DsTF>+=E!QjUTOWWF1=3XzD&(s_G=7vhVBEee?#F1&6*JzzDWRMHD|i z`CiRQNZ}EUK>XiTdkPK4Iy@3`v@gp;-Rx($wdce8@(Ka3P5aiE{fxpsB@t(WqS@C`b#i}d`MN4F;^8}l4X|iJ0c`EzCNt1sF!{4N3ZKH%0Z(Vt;Ww~e3 zk|Lu;nYLSx2vhC0qxRS_s?3*=Gn#Z-l%+6obNhRERkXRPgK$amqfpngT_q<9Wg7nq qLqhh5Him!jP8E3`Vlq;Z=lo#|9rh75q)2fl9f!eR_x3)}|NjF7^3CA@ literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe-tbn3D.tga new file mode 100644 index 0000000000000000000000000000000000000000..c5c208c058ad2725bc77ff2eb49fc43f52b94542 GIT binary patch literal 5096 zcmaKv32>C<8OJxfn}a0AMmwX<)Umdx#E#*RG6<+e@EDK^5z#tTiXf*tw6-FHNH=#N zf-w*T6-WWoT3ghrsL+D8O(6ynkc1@%Im|&WVqy-+wf#Nc`|Y>i?zc;3-+7Pc|NQ^Y zdp++Ln=RVr{hMo>YrEc75+5HQs=S9S%ww}r?esO%XqqfLe3o>7%bPw+hRI^HdD193 zx>fHcdl5E|?PScTJW3c%MzeH_lWwuiR&sT0lWSS$-#6t_Opw=s#r-~~<>kEAdC%59 z@WlBk4^<>Bt$lS@`}()r*6nOtyS;VIw$_(&Tfod)S_^t3B?8j3cO@FULtS5bJjJh5 z?9lrH*a9|s@hgq_$9tyFt6K0}JsOZMT+@&^#htvj<3GK_#U4*GB`oW&7j^PT{F~EO zTKTtF_&4Jc`_Pl;-#FMc;ofu0Tuq1D29?Br`-aor?HG5r``0gi`!{z#3NDKM(}sbc z-**l#S)M2*)O9G&sZKY`C3rPs28`ijG0iL@t2E5L&9j5lqp3X{cdR8jKj5ZWQiDp|wv+;F2v#xQ{%!<6j-O!1O!z`P^K*X-Xo>@=TOi!u;#7fNh zO$~mT6uY76f;%GUR5w{vH;F0-@BYWD_nqiPKn{hUYAOTaU$hLa&c5*DgfqY0d?9sz zhgXt=$cce8Ii%!9U6LDumu$ZrWXI$OD;`@__hI!V*kxXRl4&AYH0;^-Mb|IpR?U98 zX8hf5oa9>C4at;{l35l>k#*dB`SY~js z(RY+R`)cEX3YnipES*6o^V@f_cggxjpccKfka`&wJy}oZ+$FV>W}M^debqe#d(d5s zL~?&gFLB+HSoXvV^^dKrn>M==Z^?wdg-s8sEfz^M{(Bk2&35ItlL)wk`9(cnkI0?m zXFj_U(5SD6J@1zGvBbBIEqnaA`W;8Rxj2b;on4XIWQjuN8T=5-+W1EyhxoeZvzp&O z+rMaS!)@csNt&NG4+MljmjDV*^>JZu8&|$yRo$CMx(oXy6XqW5nmGL&nb#7AY;W)| zwR(wzRO`i`O-(5Lj&T+Wh5p+Yfh>t-)xA z<;$)io{B&2X~*6FqR9;oE-ts;UtE%Pj7nd!+#`|`la|%7xCs->A6-_Lcc@EgYIkD) z$=;v*tnA)jR#`M9Q3cnOr%mBB^QzF~L! zgTFeDo|ZC=WReYQbkLNE!SBS&(-*_502x2B3D4m3nau|(FLA68WfN6a^Ik#H9OuBI zD6h__po$i`_)y?f&_)85^fI9$MBvosPnLuH_f3wO#O^ege6ayc#NrJ z3p8TY4X|v(x(|OjiY5mcc0ujHiHaXlY^2D?Ema0Z>@L0b;cyJ#%GJ3dsTEhrRng!4 z^hdDPEq|1q$)wm7FRgTndC3!9fTMvAnlywifI5Y~Nm&v?xG`ZBDMJ#U5I8GmO{H>A8+W

H9Rasfq!dMP% z3^O22P4q=u^JnCQ!%-3!+fg8QE2KYWKtPqg`0w)R*E~IY`1^n3FAr>>zBYy#kfATy z;_qU_e)^^xx{vO>k~d~RV3oRf@232K#bl&m?IO=`5&rxF<}QQ7MF4=Ql^0d<73H|{ zI}`fPz8}gH6991h@DR^El(>`8Q70n8Teq$W<%kIYFwmkUUZNLES{iIUWDS!+Ooo#9 zwXpj$nE zh62z~R`K>)PhEelveNKb_k(cKRVFnq9-+pyRU<`7#&>bLwVJ++i&`>mSyic}FDEaP zwq#Q(Y08jHEtR2gE;vK{q!?=`Y{_)()XE^Qi?SPVY_c;_yg6GSVvmwqb5uS+RG)^N z740TrlIWcZ0$erpWN%9Uc6^o18@i?kKEm)-?y4)%YwJW;a)%Im*;SiCZA^C1JO_t? crXiD{;?L^jy(GS;(7@<1ncZmX(-FV_12ggWp#T5? literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe2D.tga b/src/Magnum/Shaders/Test/MeshVisualizerTestFiles/multidraw-wireframe2D.tga new file mode 100644 index 0000000000000000000000000000000000000000..cbc3a6d912c4c06281322330eeba171fceea8900 GIT binary patch literal 4494 zcmaJ_X>b%(5Z;-cJ#uPLKqwDTk*g5ol2Q;$4n?p4DG?FK4~=38Qi?#SD29+bAqQB& z075_rh*7x<6etP`%(+QOLP*GA639WaBqSl%X5-iMW_NaWW<$-Z%zM+_-}n0U>+VIT z^VT{3Ez&L0J)yG%1O#Y}M63%_X59+hRI@td?t~e|nD4;gvvj(=A&(c1dp;Q>6f5x< z<^_QjVW~}LYPuo6Hlp@KMc0ohjc+b1$F~FwA%EBqrdXf2q4V}%Nf;dT@o5`IEQbHM zX*WOq{?48&tykK5v-a%BRVkM}sBA~>>W!Q>4Vu%8fW=1Ds+{xy%HQiHZmq48y;w|CA z;~t>nHKFx5Dj3EV-O<(OW^zS`5}tL@brJy|nSf^waViesP&XhR5iZQ429scXkt2v^ zvghFk%teVqIqlDmDS$oErA|7*z%+Ss%63gcJZ=X&bTZ5_N`5Tl*gBS-A&qx8$B3@G z-erG(O_hqy*8b?kYSQ6wQvpH&AaU_=<>Dmb#)3kWi$B$@q7c#_g%}luFc{#Gby%_x ztul{zIax3YZusK0ywKP)bKz}Rpo8X}hqA3d3elPrIAlHSC`|T(iQzMFb`e=PgpP0jL53?{X&A1Q0s#b9(8y;^Bp(_)==qZ#gR3ph# zW@j`oPqDLNw}(8*AHDH##P~uLF!{qHFnLl5OrGXY0AZ0LLy;gEuzOw;189AGJrtEb zrStcQOp&8T=23T1E&)u}Lf$fq35L%iMqNtI)pfrzkIJ8t1qHuH7IbZtEJ&F{xduGt z2XCj>83imc7I@JhBBqq+KkmbbGHJ&2$Aa>Qngx%J0fikvo-(w-NkyBDn8mMk^ezsn zR1u=g=+P~dQ`Lmbh;pjNi zEPEgJ0}$*@g+m>nS&gFr5nEgITj+SuXrQB}VV zr$yxrevICO7TF$LhRYx39c;S)ScL2WN#38y_qJa7Z}k$q=MD<6{zg?5kG<=9 z%lV%wR=#ZsATem{m$W3#c3XPu)x8CyhvRGMf*D=aXP`xyMX*^fHXaBrne%eC(IDxN z?_@o@E#q$Rr`du&$IDco@HKQOT??J%$7>_k7LFgm@b*f&UYNp1z=Kdnaa!?f6R~o0 z>ez>QsgkMzA=Q>~plaO*`A-gJ+Pk&1A$~7;v&GLRi|1-K1-6_`48stbPsNulon!I$ zVX8k%sxo2d;pwUWyX?K$kg+`C8;!wX2N2!mLEL7&RIzG4#IsGt7z~G5#eQJ6>0n6i z0P=Cw7t5LshlXS5D}xs24jQ1OqtNTBI8icp8UzYo9MP7Z0y9*1g^r@%?`#P|kT6vZ zC)hLVqSoPHvW-G*#M-{zOsG+961wZoH|+Yhc=`ku-VX1}PH4|OSTtcI442HF+F5oS zI&|8G-uA2akH?lRnU&*9kqT%0B00nnULDt}zg(Wh;XfDr8dFGzp5BbMfA?3dS(-O= zAk%V$mlVX!rxWs@qO&YtF|ViTB2i?{aH>yxlrG--78`W5-l q(EnE8tK-(vm8a-68;d56Vpq^{P#GCV#ACeY8(|y?2aWOVu=$qHo!V2CYHgHMT#hb1_iMJ zK_oFI#v}$66H5{TNK;{1niN?&;vz+0m(98VE!+Ms6K3E1|L;BLp8MW^_r8xnU?=GL z^ALCl1`4vBoSgpexCX36hCq;IZySYi9FJs@z$DD>>rf@}Y5M6z1cn8?aD-r-!f^cZ zbnX7gx@B9+=B<7{WkI2*ciG{~4Tmn)`<<^p5LLJDY~9|7T40uWmjO*-1qf-GKiLHr z7Felxs8(m3!Vn_9E_^t^cAE~$uqsGCt*e{KAvS)AIc;8B z%O}k(4E?z&wX>TUw+B^g^lX_Ppe7|xnv}HJ6J9%NoHY3Rm+4B1s4q!VvVQPN=@(r; z%1w&t|5nifQ*3IBM$t~IXihYgCmIT_XrrHe;+(?Bd(8+(Dk6Q^qcB)qPH9=VwsfXv zF+f;}C7+docML9myLN}xyz89% zd00(ALL*Po->l?uo+RyL(WLo>E4G*4EO~?IG%o|mK$1*Xsn6b3eB@EQXkDq>G#N<2 z7`j;=H7gmylcZfI8ag`H`-nWbt_#s=QU;R2WcanL0|@8wQl40VS+QfL#fFW^+j3Zb zqmbsu6AOY-jQL&3YTbND4k%AA(5zlCPmk79?6J9Q;_L#H-lJwEqMLk+){sodZbe?h zUGi4%s#vkDeCnbiu!Ask3marsn>>b%hP3Tb*^;;hGy)W1TzbpnZmLQA^!F|$Wcyg9 zzM9j9B_83Lw`xa4^n+#;j?3iytpQcMENGslH!-b^VHUKB2kZ=~zMkK{;Xvg`Hz~U2 zo$5}b6!3@y5}&l8U`LLXuH0UJ?tXJhJ9WZ{B*lc81!%pzH0br3PJ^}tKMg0SqmcgX z=stbx)hy5A*rGUjoU-R-UkGj{4ZVe2#Ssp5sI4Zu5Z2;Y{ z(5QkxhROG8_O;{dd)gGNeYj|G|d0xIrI!$W+hkEJXU?9=nb|NMDY??SL-Q3v^nepo+6z(wk8#Ma!%8i6eY=N zJLB}j{VndaNmF)5-)~;LzI4*O!oWmL-2($J4teNN)6-Qo9jrEdJp(cNY!L51TNj_v zI(mXM>}C_f(KKxSe5u63p~CSlV^UgBMiIBEVt6X_sbFoS!k2J$m!bOB@2yOE(~Tw( zfMJQ+-F%RG;jZH~SVKgn-lEH&K5~qAgzkffzNakQrpuOYDht67;-#?GW*Fo&f>`RO zr;BinBCsR$48DRy?9;^19}PbR)c-P!((r(^$grSKymJ^Lb_+b*kr?MN#Myo9p1uDL zjOD(|u`0(fBmjrtDuxLTxB&u|!V-r*`NTzwr{a=3Vn|C`9A-dmSj<)xY}{yULbw3} zRLB^bIMKLTB5Y1S#|)uGRlrs?_lZP()Hn>;Ea3(SP!L!)lnF&)*(`L2gly{Aj?!NM zHy1_-H$YrA90Poca~^|MBGbw;%g&}D=14Cw#Bc)yD9eL#AU>CeblfNeFIhQ9*>oQ< zs2>|ohwIUB14MZ+2KW@`0tV#)$CE*CEAE`>C59MofB-#dbmEfemU^ zI{5e*RTmjm=>TYzNtL*y>?j@Y+@62k91|`8w5r66#c*UByY9?Bb40jUk!ZAtMS2dX z?ykCIewr|{!UsshiNnA`m3e!r+Eet`0go_h=RxRi zF{pr^4IkW+8mkY+BaAE$irIsNQkBG4ABjg8b;x+2=|4o7yQ@A7j|@YRdN5eR`b&q< z1o5LAJ0fb-uHb5g#}cpE41gQvC3cVZ$-w^1iyNIc0SGZ3nTy$pVa|RPJ_&X;UAcQu L=&FBEn7jWEZ=xHP literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 21c8adada..fb8f96411 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -57,6 +57,17 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Phong.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -65,52 +76,79 @@ struct PhongGLTest: GL::OpenGLTester { explicit PhongGLTest(); void construct(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffers(); + #endif void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + void constructMoveUniformBuffers(); + #endif void constructTextureTransformationNotTextured(); + #ifndef MAGNUM_TARGET_GLES2 + void constructUniformBuffersInvalid(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + void setUniformUniformBuffersEnabled(); + void bindBufferUniformBuffersNotEnabled(); + #endif void bindTexturesNotEnabled(); void setAlphaMaskNotEnabled(); void setTextureMatrixNotEnabled(); #ifndef MAGNUM_TARGET_GLES2 + void bindTextureTransformBufferNotEnabled(); + #endif + #ifndef MAGNUM_TARGET_GLES2 void setObjectIdNotEnabled(); #endif void setWrongLightCount(); void setWrongLightId(); + #ifndef MAGNUM_TARGET_GLES2 + void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - void renderDefaults(); - void renderColored(); - void renderSinglePixelTextured(); + template void renderDefaults(); + template void renderColored(); + template void renderSinglePixelTextured(); - void renderTextured(); - void renderTexturedNormal(); + template void renderTextured(); + template void renderTexturedNormal(); - template void renderVertexColor(); + template void renderVertexColor(); - void renderShininess(); + template void renderShininess(); void renderAlphaSetup(); void renderAlphaTeardown(); - void renderAlpha(); + template void renderAlpha(); #ifndef MAGNUM_TARGET_GLES2 void renderObjectIdSetup(); void renderObjectIdTeardown(); - void renderObjectId(); + template void renderObjectId(); #endif - void renderLights(); + template void renderLights(); + + /* This tests something that's irrelevant to UBOs */ void renderLightsSetOneByOne(); + /* This tests just the algorithm, not affected by UBOs */ void renderLowLightAngle(); - void renderZeroLights(); - void renderInstanced(); + template void renderZeroLights(); + + template void renderInstanced(); + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -172,6 +210,37 @@ constexpr struct { {"instanced normal texture offset", PhongGL::Flag::NormalTexture|PhongGL::Flag::InstancedTextureOffset, 3} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + PhongGL::Flags flags; + UnsignedInt lightCount, materialCount, drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1, 1, 1}, + {"", PhongGL::Flag::UniformBuffers, 1, 1, 1}, + {"multiple lights, materials, draws", PhongGL::Flag::UniformBuffers, 30, 64, 128}, + {"zero lights", PhongGL::Flag::UniformBuffers, 0, 64, 128}, + {"ambient + diffuse + specular texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 1, 1, 1}, + {"ambient + diffuse + specular texture + texture transformation", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|PhongGL::Flag::TextureTransformation, 1, 1, 1}, + {"normal texture", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NormalTexture, 1, 1, 1}, + {"normal texture + separate bitangents", PhongGL::Flag::UniformBuffers|PhongGL::Flag::NormalTexture|PhongGL::Flag::Bitangent, 1, 1, 1}, + {"alpha mask", PhongGL::Flag::UniformBuffers|PhongGL::Flag::AlphaMask, 1, 1, 1}, + {"object ID", PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectId, 1, 1, 1} +}; + +constexpr struct { + const char* name; + PhongGL::Flags flags; + UnsignedInt lightCount, materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData[]{ + {"zero draws", PhongGL::Flag::UniformBuffers, 1, 1, 0, + "draw count can't be zero"}, + {"zero materials", PhongGL::Flag::UniformBuffers, 1, 0, 1, + "material count can't be zero"}, +}; +#endif + using namespace Math::Literals; const struct { @@ -483,69 +552,171 @@ constexpr struct { } }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected; + PhongGL::Flags flags; + UnsignedInt lightCount, materialCount, drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset, colored", "multidraw.tga", + {}, + 2, 1, 1, 16, 0.0f, 0.0f}, + {"bind with offset, textured", "multidraw-textured.tga", + PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture, + 2, 1, 1, 16, 0.0f, 0.0f}, + {"draw offset, colored", "multidraw.tga", + {}, + 4, 2, 3, 1, 0.0f, 0.0f}, + {"draw offset, textured", "multidraw-textured.tga", + PhongGL::Flag::TextureTransformation|PhongGL::Flag::DiffuseTexture, + 4, 2, 3, 1, 0.0f, 0.0f} +}; +#endif + PhongGLTest::PhongGLTest() { - addInstancedTests({&PhongGLTest::construct}, Containers::arraySize(ConstructData)); + addInstancedTests({&PhongGLTest::construct}, + Containers::arraySize(ConstructData)); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&PhongGLTest::constructUniformBuffers}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif - addTests({&PhongGLTest::constructMove, + addTests({ + &PhongGLTest::constructMove, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::constructMoveUniformBuffers, + #endif - &PhongGLTest::constructTextureTransformationNotTextured, + &PhongGLTest::constructTextureTransformationNotTextured}); - &PhongGLTest::bindTexturesNotEnabled, - &PhongGLTest::setAlphaMaskNotEnabled, - &PhongGLTest::setTextureMatrixNotEnabled, - #ifndef MAGNUM_TARGET_GLES2 - &PhongGLTest::setObjectIdNotEnabled, - #endif - &PhongGLTest::setWrongLightCount, - &PhongGLTest::setWrongLightId}); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &PhongGLTest::constructUniformBuffersInvalid}, + Containers::arraySize(ConstructUniformBuffersInvalidData)); + #endif + + addTests({ + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::setUniformUniformBuffersEnabled, + &PhongGLTest::bindBufferUniformBuffersNotEnabled, + #endif + &PhongGLTest::bindTexturesNotEnabled, + &PhongGLTest::setAlphaMaskNotEnabled, + &PhongGLTest::setTextureMatrixNotEnabled, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::bindTextureTransformBufferNotEnabled, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::setObjectIdNotEnabled, + #endif + &PhongGLTest::setWrongLightCount, + &PhongGLTest::setWrongLightId, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::setWrongDrawOffset + #endif + }); - addTests({&PhongGLTest::renderDefaults}, + addTests({ + &PhongGLTest::renderDefaults, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderDefaults + #endif + }, &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderColored}, + addInstancedTests({ + &PhongGLTest::renderColored, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderColored + #endif + }, Containers::arraySize(RenderColoredData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderSinglePixelTextured}, + addInstancedTests({ + &PhongGLTest::renderSinglePixelTextured, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderSinglePixelTextured + #endif + }, Containers::arraySize(RenderSinglePixelTexturedData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderTextured}, + addInstancedTests({ + &PhongGLTest::renderTextured, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderTextured + #endif + }, Containers::arraySize(RenderTexturedData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderTexturedNormal}, + addInstancedTests({ + &PhongGLTest::renderTexturedNormal, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderTexturedNormal + #endif + }, Containers::arraySize(RenderTexturedNormalData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addTests({&PhongGLTest::renderVertexColor, - &PhongGLTest::renderVertexColor}, + addTests({ + &PhongGLTest::renderVertexColor, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderVertexColor, + #endif + &PhongGLTest::renderVertexColor, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderVertexColor, + #endif + }, &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderShininess}, + addInstancedTests({ + &PhongGLTest::renderShininess, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderShininess, + #endif + }, Containers::arraySize(RenderShininessData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addInstancedTests({&PhongGLTest::renderAlpha}, + addInstancedTests({ + &PhongGLTest::renderAlpha, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderAlpha + #endif + }, Containers::arraySize(RenderAlphaData), &PhongGLTest::renderAlphaSetup, &PhongGLTest::renderAlphaTeardown); #ifndef MAGNUM_TARGET_GLES2 - addInstancedTests({&PhongGLTest::renderObjectId}, + addInstancedTests({ + &PhongGLTest::renderObjectId, + &PhongGLTest::renderObjectId}, Containers::arraySize(RenderObjectIdData), &PhongGLTest::renderObjectIdSetup, &PhongGLTest::renderObjectIdTeardown); #endif - addInstancedTests({&PhongGLTest::renderLights}, + addInstancedTests({ + &PhongGLTest::renderLights, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderLights, + #endif + }, Containers::arraySize(RenderLightsData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); @@ -555,7 +726,12 @@ PhongGLTest::PhongGLTest() { &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); - addTests({&PhongGLTest::renderZeroLights}, + addTests({ + &PhongGLTest::renderZeroLights, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderZeroLights + #endif + }, #ifndef MAGNUM_TARGET_GLES2 &PhongGLTest::renderObjectIdSetup, &PhongGLTest::renderObjectIdTeardown @@ -565,11 +741,23 @@ PhongGLTest::PhongGLTest() { #endif ); - addInstancedTests({&PhongGLTest::renderInstanced}, + addInstancedTests({ + &PhongGLTest::renderInstanced, + #ifndef MAGNUM_TARGET_GLES2 + &PhongGLTest::renderInstanced, + #endif + }, Containers::arraySize(RenderInstancedData), &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&PhongGLTest::renderMulti}, + Containers::arraySize(RenderMultiData), + &PhongGLTest::renderObjectIdSetup, + &PhongGLTest::renderObjectIdTeardown); + #endif + /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -617,6 +805,35 @@ void PhongGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::constructUniformBuffers() { + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & PhongGL::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + if((data.flags & PhongGL::Flag::ObjectId) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + PhongGL shader{data.flags, data.lightCount, data.materialCount, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.lightCount(), data.lightCount); + CORRADE_COMPARE(shader.materialCount(), data.materialCount); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + void PhongGLTest::constructMove() { PhongGL a{PhongGL::Flag::AlphaMask, 3}; const GLuint id = a.id(); @@ -638,6 +855,38 @@ void PhongGLTest::constructMove() { CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::constructMoveUniformBuffers() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + PhongGL a{PhongGL::Flag::UniformBuffers, 3, 2, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + PhongGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), PhongGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.lightCount(), 3); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + PhongGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), PhongGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.lightCount(), 3); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + void PhongGLTest::constructTextureTransformationNotTextured() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -650,6 +899,124 @@ void PhongGLTest::constructTextureTransformationNotTextured() { "Shaders::PhongGL: texture transformation enabled but the shader is not textured\n"); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::constructUniformBuffersInvalid() { + auto&& data = ConstructUniformBuffersInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + PhongGL{data.flags, data.lightCount, data.materialCount, data.drawCount}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::PhongGL: {}\n", data.message)); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::setUniformUniformBuffersEnabled() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + PhongGL shader{PhongGL::Flag::UniformBuffers}; + shader.setAmbientColor({}) + .setDiffuseColor({}) + .setNormalTextureScale({}) + .setSpecularColor({}) + .setShininess({}) + .setAlphaMask({}) + .setObjectId({}) + .setTransformationMatrix({}) + .setNormalMatrix({}) + .setProjectionMatrix({}) + .setTextureMatrix({}) + .setLightPositions(std::initializer_list{}) + .setLightPosition(0, Vector4{}) + .setLightColors(std::initializer_list{}) + .setLightColor(0, Color3{}) + .setLightSpecularColors({}) + .setLightSpecularColor(0, {}) + .setLightRanges({}) + .setLightRange(0, {}); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setAmbientColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setDiffuseColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setNormalTextureScale(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setSpecularColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setShininess(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setAlphaMask(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setObjectId(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setTransformationMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setNormalMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightPositions(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightPosition(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightColors(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightSpecularColors(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightSpecularColor(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightRanges(): the shader was created with uniform buffers enabled\n" + "Shaders::PhongGL::setLightRange(): the shader was created with uniform buffers enabled\n"); +} + +void PhongGLTest::bindBufferUniformBuffersNotEnabled() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + PhongGL shader; + shader.bindProjectionBuffer(buffer) + .bindProjectionBuffer(buffer, 0, 16) + .bindTransformationBuffer(buffer) + .bindTransformationBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16) + .bindMaterialBuffer(buffer) + .bindMaterialBuffer(buffer, 0, 16) + .bindLightBuffer(buffer) + .bindLightBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::bindLightBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::PhongGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} +#endif + void PhongGLTest::bindTexturesNotEnabled() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -706,6 +1073,30 @@ void PhongGLTest::setTextureMatrixNotEnabled() { "Shaders::PhongGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::bindTextureTransformBufferNotEnabled() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer{GL::Buffer::TargetHint::Uniform}; + PhongGL shader{PhongGL::Flag::UniformBuffers}; + shader.bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n" + "Shaders::PhongGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n"); +} +#endif + #ifndef MAGNUM_TARGET_GLES2 void PhongGLTest::setObjectIdNotEnabled() { #ifdef CORRADE_NO_ASSERT @@ -757,6 +1148,26 @@ void PhongGLTest::setWrongLightId() { "Shaders::PhongGL::setLightRange(): light ID 3 is out of bounds for 3 lights\n"); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::setWrongDrawOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + PhongGL{PhongGL::Flag::UniformBuffers, 1, 2, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::PhongGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void PhongGLTest::renderSetup() { @@ -785,11 +1196,52 @@ void PhongGLTest::renderTeardown() { _color = GL::Renderbuffer{NoCreate}; } -void PhongGLTest::renderDefaults() { +template void PhongGLTest::renderDefaults() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - PhongGL{} - .draw(sphere); + PhongGL shader{flag}; + + if(flag == PhongGL::Flag{}) { + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::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, { + PhongDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -812,24 +1264,79 @@ void PhongGLTest::renderDefaults() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderColored() { +template void PhongGLTest::renderColored() { auto&& data = RenderColoredData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - PhongGL{{}, 2} - .setLightColors({data.lightColor1, data.lightColor2}) - .setLightPositions({{data.lightPosition1, -3.0f, 2.0f, 0.0f}, - {data.lightPosition2, -3.0f, 2.0f, 0.0f}}) - .setAmbientColor(0x330033_rgbf) - .setDiffuseColor(0xccffcc_rgbf) - .setSpecularColor(0x6666ff_rgbf) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(data.rotation)) - .setNormalMatrix(Matrix4::rotationY(data.rotation).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .draw(sphere); + PhongGL shader{flag, 2}; + + if(flag == PhongGL::Flag{}) { + shader + .setLightColors({data.lightColor1, data.lightColor2}) + .setLightPositions({{data.lightPosition1, -3.0f, 2.0f, 0.0f}, + {data.lightPosition2, -3.0f, 2.0f, 0.0f}}) + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(data.rotation)) + .setNormalMatrix(Matrix4::rotationY(data.rotation).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); + + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(data.rotation)) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + .setNormalMatrix(Matrix4::rotationY(data.rotation).normalMatrix()) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({data.lightPosition1, -3.0f, 2.0f, 0.0f}) + .setColor(data.lightColor1), + PhongLightUniform{} + .setPosition({data.lightPosition2, -3.0f, 2.0f, 0.0f}) + .setColor(data.lightColor2) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -867,10 +1374,21 @@ constexpr GL::TextureFormat TextureFormatRGBA = #endif ; -void PhongGLTest::renderSinglePixelTextured() { +template void PhongGLTest::renderSinglePixelTextured() { auto&& data = RenderSinglePixelTexturedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); @@ -901,13 +1419,7 @@ void PhongGLTest::renderSinglePixelTextured() { .setStorage(1, TextureFormatRGBA, Vector2i{1}) .setSubImage(0, {}, specularImage); - PhongGL shader{PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture, 2}; - shader.setLightColors({0x993366_rgbf, 0x669933_rgbf}) - .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)); - + PhongGL shader{PhongGL::Flag::AmbientTexture|PhongGL::Flag::DiffuseTexture|PhongGL::Flag::SpecularTexture|flag, 2}; if(data.multiBind) shader.bindTextures(&ambient, &diffuse, &specular, nullptr); else shader @@ -915,7 +1427,51 @@ void PhongGLTest::renderSinglePixelTextured() { .bindDiffuseTexture(diffuse) .bindSpecularTexture(specular); - shader.draw(sphere); + if(flag == PhongGL::Flag{}) { + shader.setLightColors({0x993366_rgbf, 0x669933_rgbf}) + .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{} + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{} + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + /* Has to be set because the default is black regardless of + whether the texture is present or not (it has no way to + know) */ + .setAmbientColor(0xffffff_rgbf) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x993366_rgbf), + PhongLightUniform{} + .setPosition({ 3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x669933_rgbf) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -938,10 +1494,21 @@ void PhongGLTest::renderSinglePixelTextured() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderTextured() { +template void PhongGLTest::renderTextured() { auto&& data = RenderTexturedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -949,10 +1516,7 @@ void PhongGLTest::renderTextured() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); - PhongGL shader{data.flags, 2}; - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); + PhongGL shader{data.flags|flag, 2}; Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); @@ -966,11 +1530,7 @@ void PhongGLTest::renderTextured() { .setWrapping(GL::SamplerWrapping::ClampToEdge) .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - shader - .bindAmbientTexture(ambient) - /* Colorized. Case without a color (where it should be white) is - tested in renderSinglePixelTextured() */ - .setAmbientColor(0xff9999_rgbf); + shader.bindAmbientTexture(ambient); } /* If no diffuse texture is present, dial down the default diffuse color @@ -984,12 +1544,8 @@ void PhongGLTest::renderTextured() { .setWrapping(GL::SamplerWrapping::ClampToEdge) .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - shader - .bindDiffuseTexture(diffuse) - /* Colorized. Case without a color (where it should be white) is - tested in renderSinglePixelTextured() */ - .setDiffuseColor(0x9999ff_rgbf); - } else shader.setDiffuseColor(0x333333_rgbf); + shader.bindDiffuseTexture(diffuse); + } GL::Texture2D specular; if(data.flags & PhongGL::Flag::SpecularTexture) { @@ -1000,25 +1556,90 @@ void PhongGLTest::renderTextured() { .setWrapping(GL::SamplerWrapping::ClampToEdge) .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - shader - .bindSpecularTexture(specular) + shader.bindSpecularTexture(specular); + } + + if(flag == PhongGL::Flag{}) { + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + if(data.flags & PhongGL::Flag::AmbientTexture) + /* Colorized. Case without a color (where it should be white) is + tested in renderSinglePixelTextured() */ + shader.setAmbientColor(0xff9999_rgbf); + if(data.flags & PhongGL::Flag::DiffuseTexture) + /* Colorized. Case without a color (where it should be white) is + tested in renderSinglePixelTextured() */ + shader.setDiffuseColor(0x9999ff_rgbf); + else shader.setDiffuseColor(0x333333_rgbf); + if(data.flags & PhongGL::Flag::SpecularTexture) /* Colorized. Case without a color (where it should be white) is tested in renderSinglePixelTextured() */ - .setSpecularColor(0x99ff99_rgbf); + shader.setSpecularColor(0x99ff99_rgbf); + + /* Using default (white) light colors to have the texture data visible + better */ + shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); } - - /* Using default (white) light colors to have the texture data visible - better */ - shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .draw(sphere); + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition({-3.0f, -3.0f, 2.0f, 0.0f}), + PhongLightUniform{}.setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + }}; + + PhongMaterialUniform materialUniformData[1]; + if(data.flags & PhongGL::Flag::AmbientTexture) + materialUniformData->setAmbientColor(0xff9999_rgbf); + if(data.flags & PhongGL::Flag::DiffuseTexture) + materialUniformData->setDiffuseColor(0x9999ff_rgbf); + else + materialUniformData->setDiffuseColor(0x333333_rgbf); + if(data.flags & PhongGL::Flag::SpecularTexture) + materialUniformData->setSpecularColor(0x99ff99_rgbf); + GL::Buffer materialUniform{materialUniformData}; + + if(data.textureTransformation != Matrix3{}) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1037,10 +1658,21 @@ void PhongGLTest::renderTextured() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderTexturedNormal() { +template void PhongGLTest::renderTexturedNormal() { auto&& data = RenderTexturedNormalData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1081,30 +1713,71 @@ void PhongGLTest::renderTexturedNormal() { /* Rotating the view a few times (together with light positions). If the tangent transformation in the shader is correct, it should result in exactly the same images. */ - PhongGL shader{PhongGL::Flag::NormalTexture|data.flags, 2}; - shader.setLightPositions({ - Matrix4::rotationZ(data.rotation)*Vector4{-3.0f, -3.0f, 2.0f, 0.0f}, - Matrix4::rotationZ(data.rotation)*Vector4{ 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.35f))* - Matrix4::rotationZ(data.rotation)* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setNormalMatrix((Matrix4::rotationZ(data.rotation)* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setDiffuseColor(0x999999_rgbf); - - /* Verify the default is working properly */ - if(data.scale != 1.0f) - shader.setNormalTextureScale(data.scale); - + PhongGL shader{PhongGL::Flag::NormalTexture|data.flags|flag, 2}; if(data.multiBind) shader.bindTextures(nullptr, nullptr, nullptr, &normal); else shader.bindNormalTexture(normal); - shader.draw(plane); + if(flag == PhongGL::Flag{}) { + /* Verify the default is working properly */ + if(data.scale != 1.0f) + shader.setNormalTextureScale(data.scale); + + shader.setLightPositions({ + Matrix4::rotationZ(data.rotation)*Vector4{-3.0f, -3.0f, 2.0f, 0.0f}, + Matrix4::rotationZ(data.rotation)*Vector4{ 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.35f))* + Matrix4::rotationZ(data.rotation)* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setNormalMatrix((Matrix4::rotationZ(data.rotation)* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setDiffuseColor(0x999999_rgbf) + .draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.35f))* + Matrix4::rotationZ(data.rotation)* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationZ(data.rotation)* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setDiffuseColor(0x999999_rgbf) + .setNormalTextureScale(data.scale) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition(Matrix4::rotationZ(data.rotation)*Vector4{-3.0f, -3.0f, 2.0f, 0.0f}), + PhongLightUniform{}.setPosition(Matrix4::rotationZ(data.rotation)*Vector4{3.0f, -3.0f, 2.0f, 0.0f}) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1137,8 +1810,20 @@ void PhongGLTest::renderTexturedNormal() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void PhongGLTest::renderVertexColor() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void PhongGLTest::renderVertexColor() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -1171,20 +1856,62 @@ template void PhongGLTest::renderVertexColor() { .setStorage(1, TextureFormatRGB, image->size()) .setSubImage(0, {}, *image); - PhongGL{PhongGL::Flag::DiffuseTexture|PhongGL::Flag::VertexColor, 2} - .setLightPositions({{-3.0f, -3.0f, 0.0f, 0.0f}, - { 3.0f, -3.0f, 0.0f, 0.0f}}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setAmbientColor(0x111111_rgbf) - .setDiffuseColor(0x9999ff_rgbf) - .bindDiffuseTexture(diffuse) - .draw(sphere); + PhongGL shader{PhongGL::Flag::DiffuseTexture|PhongGL::Flag::VertexColor|flag, 2}; + shader.bindDiffuseTexture(diffuse); + + if(flag == PhongGL::Flag{}) { + shader + .setLightPositions({{-3.0f, -3.0f, 0.0f, 0.0f}, + { 3.0f, -3.0f, 0.0f, 0.0f}}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setAmbientColor(0x111111_rgbf) + .setDiffuseColor(0x9999ff_rgbf) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition({-3.0f, -3.0f, 0.0f, 0.0f}), + PhongLightUniform{}.setPosition({ 3.0f, -3.0f, 0.0f, 0.0f}) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x111111_rgbf) + .setDiffuseColor(0x9999ff_rgbf) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1203,20 +1930,68 @@ template void PhongGLTest::renderVertexColor() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderShininess() { +template void PhongGLTest::renderShininess() { auto&& data = RenderShininessData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32)); - PhongGL{} - .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}}) - .setDiffuseColor(0xff3333_rgbf) - .setSpecularColor(data.specular) - .setShininess(data.shininess) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .draw(sphere); + PhongGL shader{flag}; + if(flag == PhongGL::Flag{}) { + shader + .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}}) + .setDiffuseColor(0xff3333_rgbf) + .setSpecularColor(data.specular) + .setShininess(data.shininess) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f)) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setDiffuseColor(0xff3333_rgbf) + .setSpecularColor(data.specular) + .setShininess(data.shininess) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1302,10 +2077,21 @@ void PhongGLTest::renderAlphaTeardown() { renderTeardown(); } -void PhongGLTest::renderAlpha() { +template void PhongGLTest::renderAlpha() { auto&& data = RenderAlphaData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -1343,31 +2129,79 @@ void PhongGLTest::renderAlpha() { GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); - PhongGL shader{data.flags, 2}; - shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setAmbientColor(data.ambientColor) - .setDiffuseColor(data.diffuseColor) - .setSpecularColor(0xffffff00_rgbaf) - .bindTextures(&ambient, &diffuse, nullptr, nullptr); - - /* Test that the default is correct by not setting the threshold if it's - equal to the default */ - if(data.flags & PhongGL::Flag::AlphaMask && data.threshold != 0.5f) - shader.setAlphaMask(data.threshold); - - /* For proper Z order draw back faces first and then front faces */ - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); - shader.draw(sphere); - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); - shader.draw(sphere); + PhongGL shader{data.flags|flag, 2}; + shader.bindTextures(&ambient, &diffuse, nullptr, nullptr); + + if(flag == PhongGL::Flag{}) { + shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)) + .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setAmbientColor(data.ambientColor) + .setDiffuseColor(data.diffuseColor) + .setSpecularColor(0xffffff00_rgbaf); + + /* Test that the default is correct by not setting the threshold if + it's equal to the default */ + if(data.flags & PhongGL::Flag::AlphaMask && data.threshold != 0.5f) + shader.setAlphaMask(data.threshold); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition({-3.0f, -3.0f, 2.0f, 0.0f}), + PhongLightUniform{}.setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(data.ambientColor) + .setDiffuseColor(data.diffuseColor) + .setSpecularColor(0xffffff00_rgbaf) + .setAlphaMask(data.threshold) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1426,10 +2260,21 @@ void PhongGLTest::renderObjectIdTeardown() { _framebuffer = GL::Framebuffer{NoCreate}; } -void PhongGLTest::renderObjectId() { +template void PhongGLTest::renderObjectId() { auto&& data = RenderObjectIdData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); @@ -1445,17 +2290,55 @@ void PhongGLTest::renderObjectId() { GL::Buffer{Containers::arrayView({11002u, 48823u})}, 1, 0, PhongGL::ObjectId{}); - PhongGL{data.flags, 2} - .setLightColors({0x993366_rgbf, 0x669933_rgbf}) - .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setAmbientColor(0x330033_rgbf) - .setDiffuseColor(0xccffcc_rgbf) - .setSpecularColor(0x6666ff_rgbf) - .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) - .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setObjectId(data.uniformId) - .draw(sphere); + PhongGL shader{data.flags|flag, 2}; + + if(flag == PhongGL::Flag{}) { + shader + .setLightColors({0x993366_rgbf, 0x669933_rgbf}) + .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + .setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f))) + .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setObjectId(data.uniformId) + .draw(sphere); + } else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f)) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setObjectId(data.uniformId) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x993366_rgbf), + PhongLightUniform{} + .setPosition({3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x669933_rgbf) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x330033_rgbf) + .setDiffuseColor(0xccffcc_rgbf) + .setSpecularColor(0x6666ff_rgbf) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1493,29 +2376,79 @@ void PhongGLTest::renderObjectId() { } #endif -void PhongGLTest::renderLights() { +template void PhongGLTest::renderLights() { auto&& data = RenderLightsData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh plane = MeshTools::compile(Primitives::planeSolid()); Matrix4 transformation = Matrix4::translation({0.0f, 0.0f, -1.5f}); - PhongGL{{}, 1} - /* Set non-black ambient to catch accidental NaNs -- the render should - never be fully black */ - .setAmbientColor(0x222222_rgbf) - .setSpecularColor(data.specularColor) - .setLightPositions({data.position}) - .setLightColors({0xff8080_rgbf*data.intensity}) - .setLightSpecularColors({data.lightSpecularColor}) - .setLightRanges({data.range}) - .setShininess(60.0f) - .setTransformationMatrix(transformation) - .setNormalMatrix(transformation.normalMatrix()) - .setProjectionMatrix(Matrix4::perspectiveProjection(80.0_degf, 1.0f, 0.1f, 20.0f)) - .draw(plane); + PhongGL shader{flag, 1}; + if(flag == PhongGL::Flag{}) { + shader + /* Set non-black ambient to catch accidental NaNs -- the render + should never be fully black */ + .setAmbientColor(0x222222_rgbf) + .setSpecularColor(data.specularColor) + .setLightPositions({data.position}) + .setLightColors({0xff8080_rgbf*data.intensity}) + .setLightSpecularColors({data.lightSpecularColor}) + .setLightRanges({data.range}) + .setShininess(60.0f) + .setTransformationMatrix(transformation) + .setNormalMatrix(transformation.normalMatrix()) + .setProjectionMatrix(Matrix4::perspectiveProjection(80.0_degf, 1.0f, 0.1f, 20.0f)) + .draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(80.0_degf, 1.0f, 0.1f, 20.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix(transformation) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix(transformation.normalMatrix()) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{} + .setPosition({data.position}) + .setColor(0xff8080_rgbf*data.intensity) + .setSpecularColor(data.lightSpecularColor) + .setRange(data.range), + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x222222_rgbf) + .setSpecularColor(data.specularColor) + .setShininess(60.0f) + }}; + shader + .bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1635,13 +2568,22 @@ void PhongGLTest::renderLowLightAngle() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void PhongGLTest::renderZeroLights() { - CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Draw), GL::Framebuffer::Status::Complete); - +template void PhongGLTest::renderZeroLights() { if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates)); @@ -1655,7 +2597,7 @@ void PhongGLTest::renderZeroLights() { flags |= PhongGL::Flag::ObjectId; } #endif - PhongGL shader{flags, 0}; + PhongGL shader{flags|flag, 0}; Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); CORRADE_VERIFY(importer); @@ -1669,40 +2611,89 @@ void PhongGLTest::renderZeroLights() { .setStorage(1, TextureFormatRGBA, ambientImage->size()) .setSubImage(0, {}, *ambientImage); - shader - .bindAmbientTexture(ambient) - .setAmbientColor(0x9999ff_rgbf) - .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)) - /* Keep alpha mask at the default 0.5 to test the default */ - /* Passing a zero-sized light position / color array, shouldn't assert */ - .setLightPositions(Containers::ArrayView{}) - .setLightColors(Containers::ArrayView{}) - /* Using a bogus normal matrix -- it's not used so it should be okay. - Same for all other unused values, they should get ignored. */ - .setNormalMatrix(Matrix3x3{Math::ZeroInit}) - .setDiffuseColor(0xfa9922_rgbf) - .setSpecularColor(0xfa9922_rgbf) - .setShininess(0.2f) - .setNormalTextureScale(-0.3f); + shader.bindAmbientTexture(ambient); + + if(flag == PhongGL::Flag{}) { + shader + .setAmbientColor(0x9999ff_rgbf) + .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)) + /* Keep alpha mask at the default 0.5 to test the default */ + /* Passing a zero-sized light position / color array, shouldn't + assert */ + .setLightPositions(Containers::ArrayView{}) + .setLightColors(Containers::ArrayView{}) + /* Using a bogus normal matrix -- it's not used so it should be + okay. Same for all other unused values, they should get + ignored. */ + .setNormalMatrix(Matrix3x3{Math::ZeroInit}) + .setDiffuseColor(0xfa9922_rgbf) + .setSpecularColor(0xfa9922_rgbf) + .setShininess(0.2f) + .setNormalTextureScale(-0.3f); + + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + shader.setObjectId(65534); + } + #endif + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); + } #ifndef MAGNUM_TARGET_GLES2 - #ifndef MAGNUM_TARGET_GLES - if(GL::Context::current().isExtensionSupported()) - #endif - { - shader.setObjectId(65534); + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{} + /* Using a bogus normal matrix -- it's not used so it should be + okay. */ + .setNormalMatrix(Matrix3x3{Math::ZeroInit}) + .setObjectId(65534) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setAmbientColor(0x9999ff_rgbf) + /* Same for all other unused values, they should get ignored */ + .setDiffuseColor(0xfa9922_rgbf) + .setSpecularColor(0xfa9922_rgbf) + .setShininess(0.2f) + .setNormalTextureScale(-0.3f) + }}; + /* Not binding any light buffer as it's not needed */ + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + + /* For proper Z order draw back faces first and then front faces */ + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); + shader.draw(sphere); + GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); + shader.draw(sphere); } #endif - - /* For proper Z order draw back faces first and then front faces */ - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Front); - shader.draw(sphere); - GL::Renderer::setFaceCullingMode(GL::Renderer::PolygonFacing::Back); - shader.draw(sphere); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -1744,10 +2735,21 @@ void PhongGLTest::renderZeroLights() { #endif } -void PhongGLTest::renderInstanced() { +template void PhongGLTest::renderInstanced() { auto&& data = RenderInstancedData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == PhongGL::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + #ifndef MAGNUM_TARGET_GLES if(!GL::Context::current().isExtensionSupported()) CORRADE_SKIP(GL::Extensions::ARB::instanced_arrays::string() << "is not supported."); @@ -1823,27 +2825,71 @@ void PhongGLTest::renderInstanced() { PhongGL shader{PhongGL::Flag::DiffuseTexture| PhongGL::Flag::VertexColor| PhongGL::Flag::InstancedTransformation| - PhongGL::Flag::InstancedTextureOffset|data.flags, 2}; - shader - .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, - { 3.0f, -3.0f, 2.0f, 0.0f}}) - .setTransformationMatrix( - Matrix4::translation(Vector3::zAxis(-1.75f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)* - Matrix4::scaling(Vector3{0.4f})) - .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationX(15.0_degf)).normalMatrix()) - .setProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) - .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) - .bindDiffuseTexture(diffuse) - .setDiffuseColor(0xffff99_rgbf); - + PhongGL::Flag::InstancedTextureOffset|data.flags|flag, 2}; + shader.bindDiffuseTexture(diffuse); if(data.flags & PhongGL::Flag::NormalTexture) shader.bindNormalTexture(normal); - shader.draw(sphere); + if(flag == PhongGL::Flag{}) { + shader + .setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}, + { 3.0f, -3.0f, 2.0f, 0.0f}}) + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-1.75f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)* + Matrix4::scaling(Vector3{0.4f})) + .setNormalMatrix((Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix()) + .setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) + .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) + .setDiffuseColor(0xffff99_rgbf) + .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == PhongGL::Flag::UniformBuffers) { + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, { + TransformationUniform3D{}.setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-1.75f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)* + Matrix4::scaling(Vector3{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + PhongDrawUniform{}.setNormalMatrix( + (Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf)).normalMatrix() + ) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + PhongMaterialUniform{} + .setDiffuseColor(0xffff99_rgbf) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(Matrix3::scaling(Vector2{0.5f})) + }}; + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, { + PhongLightUniform{}.setPosition(Vector4{-3.0f, -3.0f, 2.0f, 0.0f}), + PhongLightUniform{}.setPosition(Vector4{3.0f, -3.0f, 2.0f, 0.0f}) + }}; + shader.bindProjectionBuffer(projectionUniform) + .bindTransformationBuffer(transformationUniform) + .bindTextureTransformationBuffer(textureTransformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_COMPARE_WITH( @@ -1853,6 +2899,279 @@ void PhongGLTest::renderInstanced() { (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void PhongGLTest::renderMulti() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + GL::Texture2D diffuse; + if(data.flags & PhongGL::Flag::DiffuseTexture) { + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/diffuse-texture.tga")) && (image = importer->image2D(0))); + + diffuse.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::RGB8, image->size()) + .setSubImage(0, {}, *image); + } + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, + Primitives::UVSphereFlag::TextureCoordinates| + Primitives::UVSphereFlag::Tangents); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid( + Primitives::PlaneFlag::TextureCoordinates| + Primitives::PlaneFlag::Tangents)); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, + Primitives::ConeFlag::TextureCoordinates| + Primitives::ConeFlag::Tangents); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + GL::Buffer projectionUniform{GL::Buffer::TargetHint::Uniform, { + ProjectionUniform3D{}.setProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f) + ) + }}; + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array materialData{data.uniformIncrement + 1}; + materialData[0*data.uniformIncrement] = PhongMaterialUniform{} + .setDiffuseColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0x00ffff_rgbf); + materialData[1*data.uniformIncrement] = PhongMaterialUniform{} + .setDiffuseColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0xffff00_rgbf); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + /* The shader has two lights hardcoded, so make sure the buffer can fit + 2 items enough even though the last draw needs just one light. Not a + problem on desktop, but WebGL complains. */ + Containers::Array lightData{2*data.uniformIncrement + 2}; + lightData[0*data.uniformIncrement] = PhongLightUniform{} + .setPosition(Vector4{0.0f, 0.0f, 1.0f, 0.0f}) + .setColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0x00ffff_rgbf); + lightData[1*data.uniformIncrement + 0] = PhongLightUniform{} + .setPosition(Vector4{-3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x999999_rgbf) + .setSpecularColor(0xff0000_rgbf); + lightData[1*data.uniformIncrement + 1] = PhongLightUniform{} + .setPosition(Vector4{3.0f, -3.0f, 2.0f, 0.0f}) + .setColor(0x999999_rgbf) + .setSpecularColor(0x00ff00_rgbf); + /* This will put the light to position 4 in case data.uniformIncrement is 1 + and to an offset aligned to 256 if it's higher */ + lightData[2*data.uniformIncrement + 1/data.uniformIncrement] = PhongLightUniform{} + .setPosition(Vector4{0.0f, 0.0f, 1.0f, 0.0f}) + .setColor(data.flags & PhongGL::Flag::DiffuseTexture ? + 0xffffff_rgbf : 0xff00ff_rgbf); + GL::Buffer lightUniform{GL::Buffer::TargetHint::Uniform, lightData}; + + Containers::Array transformationData{2*data.uniformIncrement + 1}; + transformationData[0*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f})* + /* to test the normal matrix is applied properly */ + Matrix4::rotationX(90.0_degf) + ); + transformationData[1*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationData[2*data.uniformIncrement] = TransformationUniform3D{} + .setTransformationMatrix( + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f}) + ); + GL::Buffer transformationUniform{GL::Buffer::TargetHint::Uniform, transformationData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.0f, 0.0f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({1.0f, 0.0f}) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::scaling(Vector2{0.5f})* + Matrix3::translation({0.5f, 1.0f}) + ); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + /* Material / light offsets are zero if we have single draw, as those are + done with UBO offset bindings instead. */ + drawData[0*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setLightOffsetCount(data.drawCount == 1 ? 0 : 1, 2) + .setNormalMatrix(transformationData[0*data.uniformIncrement].transformationMatrix.normalMatrix()) + .setObjectId(1211); + drawData[1*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + .setLightOffsetCount(data.drawCount == 1 ? 0 : 3, 1) + .setNormalMatrix(transformationData[1*data.uniformIncrement].transformationMatrix.normalMatrix()) + .setObjectId(5627); + drawData[2*data.uniformIncrement] = PhongDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setLightOffsetCount(data.drawCount == 1 ? 0 : 0, 1) + .setNormalMatrix(transformationData[2*data.uniformIncrement].transformationMatrix.normalMatrix()) + .setObjectId(36363); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + PhongGL shader{PhongGL::Flag::UniformBuffers|PhongGL::Flag::ObjectId|data.flags, data.lightCount, data.materialCount, data.drawCount}; + shader.bindProjectionBuffer(projectionUniform); + if(data.flags & PhongGL::Flag::DiffuseTexture) + shader.bindDiffuseTexture(diffuse); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindLightBuffer(lightUniform, + 1*data.uniformIncrement*sizeof(PhongLightUniform), + 2*sizeof(PhongLightUniform)); + shader.bindTransformationBuffer(transformationUniform, + 0*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(sphere); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindLightBuffer(lightUniform, + 2*data.uniformIncrement*sizeof(PhongLightUniform), + 2*sizeof(PhongLightUniform)); + shader.bindTransformationBuffer(transformationUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(plane); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(PhongMaterialUniform), + sizeof(PhongMaterialUniform)); + shader.bindLightBuffer(lightUniform, + 0*data.uniformIncrement*sizeof(PhongLightUniform), + 2*sizeof(PhongLightUniform)); + shader.bindTransformationBuffer(transformationUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(PhongDrawUniform), + sizeof(PhongDrawUniform)); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(cone); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationBuffer(transformationUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .bindLightBuffer(lightUniform); + if(data.flags & PhongGL::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + + /* + Colored case: + + - Sphere should be lower left, yellow with a white light with red and + green highlight on bottom left and right part + - Plane lower right, cyan with a magenta light so blue + - Cone up center, yellow with a cyan light so green + + Textured case: + + - Sphere should have bottom left numbers, so light 7881, rotated (78 + visible) + - Plane bottom right, 1223 + - Cone 6778 + */ + 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, "PhongTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); + + /* Object ID -- no need to verify the whole image, just check that pixels + on known places have expected values. SwiftShader insists that the read + format has to be 32bit, so the renderbuffer format is that too to make + it the same (ES3 Mesa complains if these don't match). */ + #ifndef MAGNUM_TARGET_GLES + if(GL::Context::current().isExtensionSupported()) + #endif + { + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{1}); + CORRADE_COMPARE(_framebuffer.checkStatus(GL::FramebufferTarget::Read), GL::Framebuffer::Status::Complete); + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::R32UI}); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[24][24], 1211); /* Sphere */ + CORRADE_COMPARE(image.pixels()[24][56], 5627); /* Plane */ + CORRADE_COMPARE(image.pixels()[56][40], 36363); /* Circle */ + } +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::PhongGLTest) diff --git a/src/Magnum/Shaders/Test/PhongTest.cpp b/src/Magnum/Shaders/Test/PhongTest.cpp new file mode 100644 index 000000000..7276b6440 --- /dev/null +++ b/src/Magnum/Shaders/Test/PhongTest.cpp @@ -0,0 +1,322 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Shaders/Phong.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct PhongTest: TestSuite::Tester { + explicit PhongTest(); + + template void uniformSize(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); + void drawUniformMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); + + void lightUniformConstructDefault(); + void lightUniformConstructNoInit(); + void lightUniformSetters(); +}; + +PhongTest::PhongTest() { + addTests({&PhongTest::uniformSize, + &PhongTest::uniformSize, + &PhongTest::uniformSize, + + &PhongTest::drawUniformConstructDefault, + &PhongTest::drawUniformConstructNoInit, + &PhongTest::drawUniformSetters, + &PhongTest::drawUniformMaterialIdPacking, + + &PhongTest::materialUniformConstructDefault, + &PhongTest::materialUniformConstructNoInit, + &PhongTest::materialUniformSetters, + + &PhongTest::lightUniformConstructDefault, + &PhongTest::lightUniformConstructNoInit, + &PhongTest::lightUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "PhongDrawUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "PhongMaterialUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "PhongLightUniform"; } +}; + +template void PhongTest::uniformSize() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); +} + +void PhongTest::drawUniformConstructDefault() { + PhongDrawUniform a; + PhongDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(b.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + CORRADE_COMPARE(a.objectId, 0); + CORRADE_COMPARE(b.objectId, 0); + CORRADE_COMPARE(a.lightOffset, 0); + CORRADE_COMPARE(b.lightOffset, 0); + CORRADE_COMPARE(a.lightCount, 0xffffffffu); + CORRADE_COMPARE(b.lightCount, 0xffffffffu); + + constexpr PhongDrawUniform ca; + constexpr PhongDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(cb.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 1.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f} + })); + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + CORRADE_COMPARE(ca.objectId, 0); + CORRADE_COMPARE(cb.objectId, 0); + CORRADE_COMPARE(ca.lightOffset, 0); + CORRADE_COMPARE(cb.lightOffset, 0); + CORRADE_COMPARE(ca.lightCount, 0xffffffffu); + CORRADE_COMPARE(cb.lightCount, 0xffffffffu); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + PhongDrawUniform a; + a.normalMatrix[2] = {1.5f, 0.3f, 3.1f, 0.5f}; + a.materialId = 5; + a.lightCount = 7; + + new(&a) PhongDrawUniform{NoInit}; + CORRADE_COMPARE(a.normalMatrix[2], (Vector4{1.5f, 0.3f, 3.1f, 0.5f})); + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.lightCount, 7); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::drawUniformSetters() { + PhongDrawUniform a; + a.setNormalMatrix(Matrix4::rotationX(90.0_degf).normalMatrix()) + .setMaterialId(5) + .setObjectId(7) + .setLightOffsetCount(9, 13); + CORRADE_COMPARE(a.normalMatrix, (Matrix3x4{ + Vector4{1.0f, 0.0f, 0.0f, 0.0f}, + Vector4{0.0f, 0.0f, 1.0f, 0.0f}, + Vector4{0.0f, -1.0f, 0.0f, 0.0f} + })); + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.objectId, 7); + CORRADE_COMPARE(a.lightOffset, 9); + CORRADE_COMPARE(a.lightCount, 13); +} + +void PhongTest::drawUniformMaterialIdPacking() { + PhongDrawUniform a; + a.setMaterialId(13765); + /* The normalMatrix field is 3x4 floats, materialId should be right after + in the low 16 bits on both LE and BE */ + CORRADE_COMPARE(reinterpret_cast(&a)[12] & 0xffff, 13765); +} + +void PhongTest::materialUniformConstructDefault() { + PhongMaterialUniform a; + PhongMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.ambientColor, 0x00000000_rgbaf); + CORRADE_COMPARE(b.ambientColor, 0x00000000_rgbaf); + CORRADE_COMPARE(a.diffuseColor, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.diffuseColor, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.specularColor, 0xffffff00_rgbaf); + CORRADE_COMPARE(b.specularColor, 0xffffff00_rgbaf); + CORRADE_COMPARE(a.normalTextureScale, 1.0f); + CORRADE_COMPARE(b.normalTextureScale, 1.0f); + CORRADE_COMPARE(a.shininess, 80.0f); + CORRADE_COMPARE(b.shininess, 80.0f); + CORRADE_COMPARE(a.alphaMask, 0.5f); + CORRADE_COMPARE(b.alphaMask, 0.5f); + + constexpr PhongMaterialUniform ca; + constexpr PhongMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.ambientColor, 0x00000000_rgbaf); + CORRADE_COMPARE(cb.ambientColor, 0x00000000_rgbaf); + CORRADE_COMPARE(ca.diffuseColor, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.diffuseColor, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.specularColor, 0xffffff00_rgbaf); + CORRADE_COMPARE(cb.specularColor, 0xffffff00_rgbaf); + CORRADE_COMPARE(ca.normalTextureScale, 1.0f); + CORRADE_COMPARE(cb.normalTextureScale, 1.0f); + CORRADE_COMPARE(ca.shininess, 80.0f); + CORRADE_COMPARE(cb.shininess, 80.0f); + CORRADE_COMPARE(ca.alphaMask, 0.5f); + CORRADE_COMPARE(cb.alphaMask, 0.5f); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + PhongMaterialUniform a; + a.diffuseColor = 0x354565fc_rgbaf; + a.normalTextureScale = 0.4f; + a.alphaMask = 7.0f; + + new(&a) PhongMaterialUniform{NoInit}; + CORRADE_COMPARE(a.diffuseColor, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.normalTextureScale, 0.4f); + CORRADE_COMPARE(a.alphaMask, 7.0f); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::materialUniformSetters() { + PhongMaterialUniform a; + a.setAmbientColor(0xff3366cc_rgbaf) + .setDiffuseColor(0x996600aa_rgbaf) + .setSpecularColor(0x0044ffdd_rgbaf) + .setNormalTextureScale(0.4f) + .setShininess(37.0f) + .setAlphaMask(2.5f); + CORRADE_COMPARE(a.ambientColor, 0xff3366cc_rgbaf); + CORRADE_COMPARE(a.diffuseColor, 0x996600aa_rgbaf); + CORRADE_COMPARE(a.specularColor, 0x0044ffdd_rgbaf); + CORRADE_COMPARE(a.normalTextureScale, 0.4f); + CORRADE_COMPARE(a.shininess, 37.0f); + CORRADE_COMPARE(a.alphaMask, 2.5f); +} + +void PhongTest::lightUniformConstructDefault() { + PhongLightUniform a; + PhongLightUniform b{DefaultInit}; + CORRADE_COMPARE(a.position, (Vector4{0.0f, 0.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(b.position, (Vector4{0.0f, 0.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(a.color, 0xffffff_rgbf); + CORRADE_COMPARE(b.color, 0xffffff_rgbf); + CORRADE_COMPARE(a.specularColor, 0xffffff_rgbf); + CORRADE_COMPARE(b.specularColor, 0xffffff_rgbf); + CORRADE_COMPARE(a.range, Constants::inf()); + CORRADE_COMPARE(b.range, Constants::inf()); + + constexpr PhongLightUniform ca; + constexpr PhongLightUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.position, (Vector4{0.0f, 0.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(cb.position, (Vector4{0.0f, 0.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(ca.color, 0xffffff_rgbf); + CORRADE_COMPARE(cb.color, 0xffffff_rgbf); + CORRADE_COMPARE(ca.specularColor, 0xffffff_rgbf); + CORRADE_COMPARE(cb.specularColor, 0xffffff_rgbf); + CORRADE_COMPARE(ca.range, Constants::inf()); + CORRADE_COMPARE(cb.range, Constants::inf()); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::lightUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + PhongLightUniform a; + a.color = 0x354565_rgbf; + a.range = 7.0f; + + new(&a) PhongLightUniform{NoInit}; + CORRADE_COMPARE(a.color, 0x354565_rgbf); + CORRADE_COMPARE(a.range, 7.0f); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void PhongTest::lightUniformSetters() { + PhongLightUniform a; + a.setPosition({2.5f, 3.6f, 0.7f, 1.1f}) + .setColor(0x354565_rgbf) + .setSpecularColor(0x996600_rgbf) + .setRange(7.0f); + CORRADE_COMPARE(a.position, (Vector4{2.5f, 3.6f, 0.7f, 1.1f})); + CORRADE_COMPARE(a.color, 0x354565_rgbf); + CORRADE_COMPARE(a.specularColor, 0x996600_rgbf); + CORRADE_COMPARE(a.range, 7.0f); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::PhongTest) diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/multidraw-textured.tga b/src/Magnum/Shaders/Test/PhongTestFiles/multidraw-textured.tga new file mode 100644 index 0000000000000000000000000000000000000000..3c7ce9617431f66956f2e493b9f99d0ab09c4563 GIT binary patch literal 5524 zcma)<2~bp5*2fVP*@bR;rMu~cUZJ7cnr2^Q-w_uUH$+85L2!d8VzbF60-~shyMW+^ z7!#6N%r~h?;*6S@L=%&km`pO6FSF0oEHz)v`~LU62eu|tHPk)z9^Lnz^Sl3Z?s>26 z>|E?-|MKnf?Syvsy}Z2s8ymnpr`10-ufAr6H|CiVM?p!_e)@0BBjCEpa53z~|(LF); z-|E7BFU)c2q-puJ;1yFL@Kb~_9fJ8**utMrbiaA23rlZbJn-XlZ8N8vuMh3T60)>q z)B76TYc zSzjJ*nmX2aW$>lZ-kn%U;M(D_&Bh~v_0AkJWDf`AoC?T2W6V1noOdoH?_6N)*_Ahk zo5(oPG<|#@9D(cVkv)@#8^-&0U+mp+rn7daxeDt$V6E`mTs9MBzwvG~ZV|x>YlQ?3 zrZ;~S^70ka8?WJ?7FLsPEa4}2Y18_2>4)_hM*=d3ax;%vvySIv9S@v5Tt7KbKQT~$ zsed;@jrHvs?b~^wZ~M8Py3-xohTEzUw!d*>=dLn%IY5F}eh_P4a?(<9H-=!qNN^Hs zLD0RQhg=`WUFg`EpkMq9H_a>p(N_?)Lz&Q`UfiKc?$)OCWTo^{sfV7X_T{AY8P0Uo zlF?avx^vs9j+&8`9B=YXDemqpExNI*hK} zWnH_-kR>r!%Okd_B5IYEIyo8J z<@>7i6sNkz*^d90T>3L7_mS_$n0pc{%&%y)NgwM&?R(})VN$6xLRz;6a|+{ zLN-W4Dx~<3j7lluTVd|o%M0q(<=2+w*R07657btz%HC9(S+z2&dUejar70M7>DoZjkHkD*l6lYWvrEge~R=zCN-_LhV!Qz#9iN)FR2@%Hh*ief} zr&q}UzqvFEPFrD%c0JA9`9;Q#&(pU*N&7)gMobVsgCxa2M+OeLiKkBI`=~jZ9KJT6 zuPYGfmI(BPWUR_hT9uy&2#nI)gyNj|qO3ST#Dw}shZuqls*L!s^tiC1?6|`8Xy~}H zD4iwq-1ALd!>@AczshD1!3Q-N9g%NFM8JhSxHAW5g^JI?`L7Tvqj^#z*EgE0Sj<(X zaFwavsx)s^y0>4tcR@-dMt+JVFWHj2I3g#>oDmlqW>hCd24%(*rjnd^NVs-MGUQyp zJguxSnT7M(|J~B2|1E6%wt%7B{bd$ZWF(x+GcpXg7x~(3#e-C;B%UIHD^KLeV!R}g zp3-P9X^fXFmW;?~ZE}=pF-Bxik|hvHOtb{1!~_ElxCN<^%Q9jJWo`o0h0;j$EMNW~ zzs>#qH(B3(mGPUWsoy+V{M9FkPd|=(@-XJp527AFuzd8c`Qg3r5ATLs-V2L(7bDbs zKP3EKGpsc@?CxxG-K7SuZzO>OlTXAVQHX~i$X#gi5C)TBFi9eUv=KoXbD+jO+^EJS zDnuV|4ghajY)EDTjhPZzm=+CE3`oraa*F@?r{#b9ec@le18Meee+A5xuRlxr@^Sp< zk8BwuqTXj10mv{~Utsh>Ql%Cz=mtPQ@dMnrT33#r3rCINDp7L{ep0Af32ndvGV`^TJ(=UF8%9o^ZxK_ z=$ZEG&mm^Q7azrb_93IBCHez%O|)>4 z$XO!f`UtpE5l<-%;f+73qMhA-K>L1z-u* zp$0I3&T@J;QdB)f)lt+IirPs1dkyvX)wgGm`kdc=&7d+#kB)o9pfXxoB94ce-vJ@X zcr%Mg-Y|sTdN(B3Q||2UH?d_cwE&wHGk-LEF2~k0*l8GRy z!B1w?%7gTZV1p94a=Cm~#~zAmr>G{HavMc$0_EFppg@6$(nQ8lmpD7I2_HwrKD14D zWJFh}`3~@&*O5Rvc`BV3$(-E93tf1QPM-4@xH>pE+S|<|@3U5z+k6KHXD3GwHy4g4 zAr%WaGBHmf5dc;#7h|YoLii!?0K5^3I!aN!6xBvidub(W-n)aOgR$UGzd=>v?BIOI zCw&qV|BQq(9#0cn7uIw*=H_woDC3~62Q_dnp2;4G7Vll<8a3D)}p z40-o;AmWhp5sVJlLZs1g!F=fB>f+?);tU>q$^wXQQ`9Yrx=c}LC~Al16Rh7 zHd?hcE%7i%eI+6a>|R296ECoFlL??(`F&wr1i-lC}2 zDGGZ<5Ns5}ql8hvP#x_6O^bA_^rs1nKV^2woG_-7K5JC-o&_L3I}RcM{f_)4MLnXZ zJ9G?e1Y#n&C_tPM9B1qTrAL}g=2uB6PvR0EV;8n{_k#iY5uh6ZNP78yrl_y!7}y8I zLc&l$C~|B9Edy;0%@+@djO?$Um+4uzW%FdSgF!eZMM8c!@hOTz4D171At4Y7o4`Rr zIiM|}b)q(*QfAIKj7r}x%QJc%)Y=mo{TZ#{qTYW( zA8^HByzXzD_78m7Kk!yS;48TagW<0oF}yluc=fve);0ajE4mw#x@+Uw$%~rNGwQR$ zs^KGw!v}pkTP2P4qOFyjf?UKn3Ofdq2}J88vR!gzo6_%)pJqs-JEb*T(D`4Q4Rpgc zQ0UA_66lrLKvNUit7F=U^XiMI{7xTN9vhG!>XNnX_t{m;FIx@oWH)paZ{8BIq(&xh zQmDFBnnAU0M5{liGmPm2E*k=_`5O`C=G=BnGv}X0nZB&MKB2ubsu@40K0o3&GNc?l zB=2sQ?rRWLZ|3CZuu+cVDiMmxrLuat5~B61wZj_yS)E~&31P4ak&|%5ICJUw5SO%< zN1w^n-|c&#*=Khhzl{7IAv>V`d-Do?q_w^ZtJ1HR){05Wq#@v{!8kRy1^D-$xz75x zNe8Ra(LVXX1Jb6KL^Uiw@qYp)7K%4WefKaD%_V|$PBKo?ofCi5FmvOX^Qj5l)v-Bo z6az?fi%QuIO?iuCn56z#!d~QD&R+-VRyIK4IksmWcMl6O^ES zlw^-Yw7If;&d-g@q&Yw?bcPdyXba-HYECZsVMKOz3dTXHuv?>B0f8}2(@u^tDiO`E zjth9(U7i_708mJziXa8Eg?6j3s)CJ7ua){N_m%G_seuSal4XR>j62S*y@m?Bh<=4* zDBiJ+lTP+dsL#MW>T@KGh32B9y71tWHVa9|gKp}f5x?qV^6++%PZeg9X!NSpG5p?A z#o0~ju`10}o&MHN!)RRuY85>J{Ripr(;oL#G;_G5CdsO^Ft*)|LBg=R$pxwz8t&D#%{~XL^UxD;k znQDBC_QvdFoFl-X*l=uFuk_5aIR4-odbgF02QIdlCAiTc%tX5ix&))si4E`oC)+~E;o*ya{*UJguApwOHa6oj^wLoVOP`N zu1^pEhW%E1g2T;VzrBd-d^jK9j_5b>*uwv01^qep3t5096q9NspO3=qv3*Fy&Z0lD S7Rp3oQZr z2%@V4Dy|AHu7JAY%36lns{OWKdw=)e|Mz@Du(Z0%ejV;T_ng!H-XLWv_TLDNP*a`E z&(HrOeJT5r^z+a9_17ppRloeApMKJhKkEE>ma6HC=&3q)PTzf}@4wd%Kj^}RD7~Hy znK*x5-+pWBr%$tUK7F;5UgiG_gOjID>B}#5=8VpsWmgeBVnZgr`pQ8JYaGo3tXji@vRoMn=wPd?GdAKT9N-`6|u=o^T8qK@yGM28hz=eknD*|~ zmMt8gYhekwC=6M_=K^itu6_GdU9J5!s`*9-oeq7Y!$)=au(d;nEC{XUfNJ*Zz*~Cr z4ei;Z9Xp7r_3O1_1yRcBB`!I+l;{h?=tf5qEnB9I8*S_M9oqG|c028H+Iw32-mt7R zz!7fuZtdQsBRlk;Z92C_+p4r-gVwCk^5sMb6BN0Wl;SYS{DdSCEnH|D*}kq)8~&$C zr>g&I(@t&L6b*V+RhB9%wQ+;0*UP;ww_0DX)biJ~Xpt5y(Bj23l9bVlTxzN%Dk4z{ z)l?)TN>@pVUVYV0Em@&u`?dVAR;<&?mBsWNnDB75VyQk{EVodeAApFCWtv-|c@-9^qN13dW5ap#R9>#K zGL@HTSyapBsBo6%%u!L1N=yAxJY^)3>C)55Y%MK~)Yei`tR*K~OG+{a&#eQ3U^8dx z`RDcGi+cHG&6;JHm@F(TqUYL>iC12+71r51V}@RMUNffarI&0T{Lv^a#F6E)vRr1S zwTuiGiG+|6@gyflL1R$L>p&s)bI<9CC#*gFw5CqAkJF|Bu9%){LnfGJ@yRFk_~W*b zkAMdgXn;tYJ{>3}^gNfB=W=shPL9jYzC571>=Xg1tPUiPXW~SS9jkHUG=97oPnx93 zlZ)t4hB-FFJge+^^ilgbWs079%8-G0`gCj0J{w}H?;1674H~%o{16<-Ak0%0*R3wB z=tZzHtZvdg@`y%`)P48q!3XuwLmD;8+UU_ydY%ngWSYhM@7Dtl*bFBgd(7~I5C-A# znP(V;*z#SoX0B;dS5OdwtY6>g^W4Riyu?!mue1(p6DEY%?!8w-hUm^aHF&TM?zyL! zo@B$pgR^zlT{b^-s78!1pku~__$Y1iWNYKc$C0&d>sq&VEn2wd&0UiwAwJ|qTTh>N zej>P}B-67b0a=D6Ad`nRXps8%SMT2H+t*sZe$k-!?VDqhefsFO+jPer26osmW0`}a zN9*B-EeKMN8Wnb?c^UuT|Hsv}9d>y&vW3y6bFo(a$_=-}G4F*Z>fKVn97j`Qg6YQn2wIEzCb?ctA@Zlr{@n{Lt#H>gV&U46A$ zwp6QDQF@jQ+q6-K4u+leo;{6H*ya$F25v~k3Lqw&y_gN%fB~*wKi8|5>(RqdcJA!j zwKF#Hq=)FK#D|I3BzVt*=mRnr6ZYDdLw`vta^lJ0&d5GSM ziBiWP+Bb7k_2^*=lDlnNU3HZjH&$MrA`vAgTVnvqMvc_08HE0Zl6ty#H{Br~xE(sg z@r@npMvZbKMi|P016}XlMx=)biwKP&xiH*j9vf~;qRL50CUGLZh-r#`ty>#{h7FaI zqx5toCGm4EO3zkmsxmVTvj^!FZ{J=WI~wDh?${A*_O094O`YndOmXAJxsfB?uwh1G zpFSZxM8=2Q7am1#!Yk1i4HdTEN@+$J0_Nu{D@$o&VSMf;dhy12LayK^6$B_vovf(jCtyaled)*U!tIj94& z5DWw1Z;IfR;34C|4XIKOVdnZe>W7kQ>QEkb-)#ZKomuAhp+=L5sxKT z2bTQn153P)7a8-C1c`swtRzVrO_Iu`wbD*$zx1B;k@T5#S~??rBYh|ROZq{&ApJXN z4EXqcu*3$ov6&+u2FD4qLs}!1iQwe0-{-67t@LN4b{`yWX3Pw8vv zE9ndAZ_+X8i1dcES;A2-Sr)6tXZaIn&-kv^1>3D)8|i zm+*3Bj4Lk+*gy!*^Oux>Mkp1R!?Q&BkgzVe@&SH}zlPpiv!zwR>cIpdzy`uA1@{to zawz$2X?q~(Ok>7S`^Zm9i-RRL#L2&GWK;lloc6ed_DP#~C!`6n$ZLt785$McQQs3D zw`d?}jv3Uulekm@S#a&SI1pHTnN(;&4XO|NbWp=?2_kSXAbu|>1XBur#~^Tkk+E|{ zh}433)8 zJt)c(#D08SCxoZQmqYyvrN?3+HV~%1Jx0o7r2>z6}7z28bomCR?@Besy+mvHU9WgR{13|8kXT!Tr(} nJ7p*Q{Q3Yt>4Im)=C{(5t*Fzs({}lp%J5p-Oyc+OmB#-E3-H_I literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index 581281b6c..0a25f7038 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -52,6 +52,21 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Circle.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Primitives/UVSphere.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Vector.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -60,17 +75,43 @@ struct VectorGLTest: GL::OpenGLTester { explicit VectorGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffers(); + #endif + template void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructMoveUniformBuffers(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffersZeroDraws(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + #endif template void setTextureMatrixNotEnabled(); + #ifndef MAGNUM_TARGET_GLES2 + template void bindTextureTransformBufferNotEnabled(); + #endif + #ifndef MAGNUM_TARGET_GLES2 + template void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - void renderDefaults2D(); - void renderDefaults3D(); - void render2D(); - void render3D(); + template void renderDefaults2D(); + template void renderDefaults3D(); + template void render2D(); + template void render3D(); + + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -105,6 +146,19 @@ constexpr struct { {"texture transformation", VectorGL2D::Flag::TextureTransformation} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + VectorGL2D::Flags flags; + UnsignedInt drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1}, + {"", VectorGL2D::Flag::UniformBuffers, 1}, + {"texture transformation", VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, 1}, + {"multiple draws", VectorGL2D::Flag::UniformBuffers, 128}, +}; +#endif + const struct { const char* name; VectorGL2D::Flags flags; @@ -122,30 +176,102 @@ const struct { "vector2D.tga", "vector3D.tga", false} }; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + UnsignedInt drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset", "multidraw2D.tga", "multidraw3D.tga", + 1, 16, 0.0f, 0.0f}, + {"draw offset", "multidraw2D.tga", "multidraw3D.tga", + 3, 1, 0.0f, 0.0f}, +}; +#endif + VectorGLTest::VectorGLTest() { addInstancedTests({ &VectorGLTest::construct<2>, &VectorGLTest::construct<3>}, Containers::arraySize(ConstructData)); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &VectorGLTest::constructUniformBuffers<2>, + &VectorGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + addTests({ &VectorGLTest::constructMove<2>, &VectorGLTest::constructMove<3>, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::constructMoveUniformBuffers<2>, + &VectorGLTest::constructMoveUniformBuffers<3>, + #endif + + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::constructUniformBuffersZeroDraws<2>, + &VectorGLTest::constructUniformBuffersZeroDraws<3>, + #endif + + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::setUniformUniformBuffersEnabled<2>, + &VectorGLTest::setUniformUniformBuffersEnabled<3>, + &VectorGLTest::bindBufferUniformBuffersNotEnabled<2>, + &VectorGLTest::bindBufferUniformBuffersNotEnabled<3>, + #endif &VectorGLTest::setTextureMatrixNotEnabled<2>, - &VectorGLTest::setTextureMatrixNotEnabled<3>}); + &VectorGLTest::setTextureMatrixNotEnabled<3>, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::bindTextureTransformBufferNotEnabled<2>, + &VectorGLTest::bindTextureTransformBufferNotEnabled<3>, + #endif + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::setWrongDrawOffset<2>, + &VectorGLTest::setWrongDrawOffset<3> + #endif + }); - addTests({&VectorGLTest::renderDefaults2D, - &VectorGLTest::renderDefaults3D}, + addTests({ + &VectorGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::renderDefaults2D, + #endif + &VectorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::renderDefaults3D, + #endif + }, &VectorGLTest::renderSetup, &VectorGLTest::renderTeardown); - addInstancedTests({&VectorGLTest::render2D, - &VectorGLTest::render3D}, + addInstancedTests({ + &VectorGLTest::render2D, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::render2D, + #endif + &VectorGLTest::render3D, + #ifndef MAGNUM_TARGET_GLES2 + &VectorGLTest::render3D, + #endif + }, Containers::arraySize(RenderData), &VectorGLTest::renderSetup, &VectorGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&VectorGLTest::renderMulti2D, + &VectorGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), + &VectorGLTest::renderSetup, + &VectorGLTest::renderTeardown); + #endif + /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ #ifdef ANYIMAGEIMPORTER_PLUGIN_FILENAME @@ -189,6 +315,33 @@ template void VectorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::constructUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & VectorGL::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + VectorGL shader{data.flags, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void VectorGLTest::constructMove() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -210,6 +363,115 @@ template void VectorGLTest::constructMove() { CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + VectorGL a{VectorGL::Flag::UniformBuffers, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + VectorGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), VectorGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + VectorGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), VectorGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::constructUniformBuffersZeroDraws() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + VectorGL{VectorGL::Flag::UniformBuffers, 0}; + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL: draw count can't be zero\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::setUniformUniformBuffersEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + VectorGL shader{VectorGL::Flag::UniformBuffers}; + shader.setTransformationProjectionMatrix({}) + .setTextureMatrix({}) + .setBackgroundColor({}) + .setColor({}); + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::VectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::VectorGL::setBackgroundColor(): the shader was created with uniform buffers enabled\n" + "Shaders::VectorGL::setColor(): the shader was created with uniform buffers enabled\n"); +} + +template void VectorGLTest::bindBufferUniformBuffersNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + VectorGL shader; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .bindDrawBuffer(buffer) + .bindDrawBuffer(buffer, 0, 16) + .bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} +#endif + template void VectorGLTest::setTextureMatrixNotEnabled() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -227,6 +489,54 @@ template void VectorGLTest::setTextureMatrixNotEnabled() "Shaders::VectorGL::setTextureMatrix(): the shader was not created with texture transformation enabled\n"); } +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::bindTextureTransformBufferNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer{GL::Buffer::TargetHint::Uniform}; + VectorGL shader{VectorGL::Flag::UniformBuffers}; + shader.bindTextureTransformationBuffer(buffer) + .bindTextureTransformationBuffer(buffer, 0, 16); + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n" + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VectorGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + VectorGL{VectorGL::Flag::UniformBuffers, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::VectorGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void VectorGLTest::renderSetup() { @@ -262,7 +572,18 @@ constexpr GL::TextureFormat TextureFormatR = #endif ; -void VectorGLTest::renderDefaults2D() { +template void VectorGLTest::renderDefaults2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VectorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -288,9 +609,26 @@ void VectorGLTest::renderDefaults2D() { .setSubImage(0, {}, *image); #endif - VectorGL2D{} - .bindVectorTexture(texture) - .draw(square); + VectorGL2D shader{flag}; + shader.bindVectorTexture(texture); + + if(flag == VectorGL2D::Flag{}) { + shader.draw(square); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + VectorDrawUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(square); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -309,7 +647,18 @@ void VectorGLTest::renderDefaults2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void VectorGLTest::renderDefaults3D() { +template void VectorGLTest::renderDefaults3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VectorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -335,9 +684,26 @@ void VectorGLTest::renderDefaults3D() { .setSubImage(0, {}, *image); #endif - VectorGL3D{} - .bindVectorTexture(texture) - .draw(plane); + VectorGL3D shader{flag}; + shader.bindVectorTexture(texture); + + if(flag == VectorGL3D::Flag{}) { + shader.draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + VectorDrawUniform{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -356,10 +722,21 @@ void VectorGLTest::renderDefaults3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void VectorGLTest::render2D() { +template void VectorGLTest::render2D() { auto&& data = RenderData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VectorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -385,18 +762,46 @@ void VectorGLTest::render2D() { .setSubImage(0, {}, *image); #endif - VectorGL2D shader{data.flags}; - shader.setBackgroundColor(data.backgroundColor) - .setColor(data.color) - .bindVectorTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); - else shader.setTransformationProjectionMatrix( - Matrix3::projection({2.1f, 2.1f})* - Matrix3::rotation(5.0_degf)); - - shader.draw(square); + VectorGL2D shader{data.flags|flag}; + shader.bindVectorTexture(texture); + + if(flag == VectorGL2D::Flag{}) { + shader.setBackgroundColor(data.backgroundColor) + .setColor(data.color); + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + else shader.setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::rotation(5.0_degf)); + shader.draw(square); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VectorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + data.textureTransformation == Matrix3{} ? + Matrix3::projection({2.1f, 2.1f})* + Matrix3::rotation(5.0_degf) : Matrix3{} + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + VectorDrawUniform{} + .setBackgroundColor(data.backgroundColor) + .setColor(data.color) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + if(data.flags & VectorGL2D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(square); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -418,10 +823,21 @@ void VectorGLTest::render2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -void VectorGLTest::render3D() { +template void VectorGLTest::render3D() { auto&& data = RenderData[testCaseInstanceId()]; setTestCaseDescription(data.name); + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VectorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + #endif + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); @@ -447,20 +863,50 @@ void VectorGLTest::render3D() { .setSubImage(0, {}, *image); #endif - VectorGL3D shader{data.flags}; - shader.setBackgroundColor(data.backgroundColor) - .setColor(data.color) - .bindVectorTexture(texture); - - if(data.textureTransformation != Matrix3{}) - shader.setTextureMatrix(data.textureTransformation); - else shader.setTransformationProjectionMatrix( - Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* - Matrix4::translation(Vector3::zAxis(-2.15f))* - Matrix4::rotationY(-15.0_degf)* - Matrix4::rotationZ(15.0_degf)); - - shader.draw(plane); + VectorGL3D shader{data.flags|flag}; + shader.bindVectorTexture(texture); + + if(flag == VectorGL3D::Flag{}) { + shader.setBackgroundColor(data.backgroundColor) + .setColor(data.color); + if(data.textureTransformation != Matrix3{}) + shader.setTextureMatrix(data.textureTransformation); + else shader.setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationZ(15.0_degf)); + shader.draw(plane); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VectorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + data.textureTransformation == Matrix3{} ? + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationZ(15.0_degf) : Matrix4{} + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + VectorDrawUniform{} + .setBackgroundColor(data.backgroundColor) + .setColor(data.color) + }}; + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, { + TextureTransformationUniform{} + .setTextureMatrix(data.textureTransformation) + }}; + if(data.flags & VectorGL3D::Flag::TextureTransformation) + shader.bindTextureTransformationBuffer(textureTransformationUniform); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .draw(plane); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -482,6 +928,333 @@ void VectorGLTest::render3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void VectorGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/vector.tga")) && (image = importer->image2D(0))); + GL::Texture2D vector; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32, + Primitives::Circle2DFlag::TextureCoordinates)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid( + Primitives::SquareFlag::TextureCoordinates)); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3, + Primitives::Circle2DFlag::TextureCoordinates)); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({circleData, squareData, triangleData})); + GL::MeshView circle{mesh}; + circle.setCount(circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(squareData.indexCount()) + .setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(triangleData.indexCount()) + .setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation({0.5f, 0.5f})* + Matrix3::rotation(180.0_degf)* + Matrix3::translation({-0.5f, -0.5f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation(Vector2::xAxis(1.0f))* + Matrix3::scaling(Vector2::xScale(-1.0f)) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix(Matrix3{}); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + drawData[0*data.uniformIncrement] = VectorDrawUniform{} + .setColor(0x00ff00_rgbf) + .setBackgroundColor(0xccffcc_rgbf); + drawData[1*data.uniformIncrement] = VectorDrawUniform{} + .setColor(0xff0000_rgbf) + .setBackgroundColor(0xffcccc_rgbf); + drawData[2*data.uniformIncrement] = VectorDrawUniform{} + .setColor(0x00ff00_rgbf) + .setBackgroundColor(0xccffcc_rgbf); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + VectorGL2D shader{VectorGL2D::Flag::UniformBuffers|VectorGL2D::Flag::TextureTransformation, data.drawCount}; + shader.bindVectorTexture(vector); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(circle); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(square); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(triangle); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindTextureTransformationBuffer(textureTransformationUniform); + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + + /* + - Circle lower left, green, upside down + - Square lower right, red, mirrored + - Triangle up center, green + */ + 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, "VectorTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +void VectorGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Containers::Pointer importer = _manager.loadAndInstantiate("AnyImageImporter"); + CORRADE_VERIFY(importer); + + Containers::Optional image; + CORRADE_VERIFY(importer->openFile(Utility::Directory::join(_testDir, "TestFiles/vector.tga")) && (image = importer->image2D(0))); + GL::Texture2D vector; + vector.setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setStorage(1, GL::TextureFormat::R8, image->size()) + .setSubImage(0, {}, *image); + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, + Primitives::UVSphereFlag::TextureCoordinates); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid( + Primitives::PlaneFlag::TextureCoordinates)); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f, + Primitives::ConeFlag::TextureCoordinates); + GL::Mesh mesh = MeshTools::compile(MeshTools::concatenate({sphereData, planeData, coneData})); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f})* + Matrix4::rotationY(180.0_degf) /* so the texture is visible */ + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f})* + Matrix4::rotationY(180.0_degf) /* so the texture is visible */ + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + Containers::Array textureTransformationData{2*data.uniformIncrement + 1}; + textureTransformationData[0*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation({0.5f, 0.5f})* + Matrix3::rotation(180.0_degf)* + Matrix3::translation({-0.5f, -0.5f}) + ); + textureTransformationData[1*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix( + Matrix3::translation(Vector2::xAxis(1.0f))* + Matrix3::scaling(Vector2::xScale(-1.0f)) + ); + textureTransformationData[2*data.uniformIncrement] = TextureTransformationUniform{} + .setTextureMatrix(Matrix3{}); + GL::Buffer textureTransformationUniform{GL::Buffer::TargetHint::Uniform, textureTransformationData}; + + Containers::Array drawData{2*data.uniformIncrement + 1}; + drawData[0*data.uniformIncrement] = VectorDrawUniform{} + .setColor(0x00ff00_rgbf) + .setBackgroundColor(0xccffcc_rgbf); + drawData[1*data.uniformIncrement] = VectorDrawUniform{} + .setColor(0xff0000_rgbf) + .setBackgroundColor(0xffcccc_rgbf); + drawData[2*data.uniformIncrement] = VectorDrawUniform{} + .setColor(0x00ff00_rgbf) + .setBackgroundColor(0xccffcc_rgbf); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + VectorGL3D shader{VectorGL3D::Flag::UniformBuffers|VectorGL3D::Flag::TextureTransformation, data.drawCount}; + shader.bindVectorTexture(vector); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 0*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(sphere); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 1*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(plane); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationUniform3D), + sizeof(TransformationUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(VectorDrawUniform), + sizeof(VectorDrawUniform)); + shader.bindTextureTransformationBuffer(textureTransformationUniform, + 2*data.uniformIncrement*sizeof(TextureTransformationUniform), + sizeof(TextureTransformationUniform)); + shader.draw(cone); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindTextureTransformationBuffer(textureTransformationUniform); + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + + /* + - Sphere lower left, green, upside down + - Plane lower right, red, mirrored + - Cone up center, green + */ + 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, "VectorTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::VectorGLTest) diff --git a/src/Magnum/Shaders/Test/VectorTest.cpp b/src/Magnum/Shaders/Test/VectorTest.cpp new file mode 100644 index 000000000..ce3be11bf --- /dev/null +++ b/src/Magnum/Shaders/Test/VectorTest.cpp @@ -0,0 +1,118 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Shaders/Vector.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct VectorTest: TestSuite::Tester { + explicit VectorTest(); + + template void uniformSize(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); +}; + +VectorTest::VectorTest() { + addTests({&VectorTest::uniformSize, + + &VectorTest::drawUniformConstructDefault, + &VectorTest::drawUniformConstructNoInit, + &VectorTest::drawUniformSetters}); +} + +using namespace Math::Literals; + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "VectorDrawUniform"; } +}; + +template void VectorTest::uniformSize() { + setTestCaseTemplateName(UniformTraits::name()); + + CORRADE_FAIL_IF(sizeof(T) % sizeof(Vector4) != 0, sizeof(T) << "is not a multiple of vec4 for UBO alignment."); + + /* 48-byte structures are fine, we'll align them to 768 bytes and not + 256, but warn about that */ + CORRADE_FAIL_IF(768 % sizeof(T) != 0, sizeof(T) << "can't fit exactly into 768-byte UBO alignment."); + if(256 % sizeof(T) != 0) + CORRADE_WARN(sizeof(T) << "can't fit exactly into 256-byte UBO alignment, only 768."); +} + +void VectorTest::drawUniformConstructDefault() { + VectorDrawUniform a; + VectorDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.backgroundColor, 0x00000000_rgbaf); + CORRADE_COMPARE(b.backgroundColor, 0x00000000_rgbaf); + + constexpr VectorDrawUniform ca; + constexpr VectorDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.backgroundColor, 0x00000000_rgbaf); + CORRADE_COMPARE(cb.backgroundColor, 0x00000000_rgbaf); + + CORRADE_VERIFY(std::is_nothrow_default_constructible::value); + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void VectorTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + VectorDrawUniform a; + a.color = 0x354565fc_rgbaf; + a.backgroundColor = 0x98769facb_rgbaf; + + new(&a) VectorDrawUniform{NoInit}; + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.backgroundColor, 0x98769facb_rgbaf); + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void VectorTest::drawUniformSetters() { + VectorDrawUniform a; + a.setColor(0x354565fc_rgbaf) + .setBackgroundColor(0x98769facb_rgbaf); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.backgroundColor, 0x98769facb_rgbaf); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::VectorTest) diff --git a/src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D-distancefield.tga b/src/Magnum/Shaders/Test/VectorTestFiles/multidraw2D-distancefield.tga new file mode 100644 index 0000000000000000000000000000000000000000..d255dcae9fc25c6abd695077d8ad09fffd3c766f GIT binary patch literal 3404 zcma)<%WGs+5XQfIJH8bR_*#ey7p`2XaWxBHJV1o1D>Rc4a(#0O>~v*;N! z4y34n_H(&Ree=;>eM}vE>szN zv`1Iz!`a!{uIEYk%ZMJ}y~cZy_Y;1gy;gXSK_7tUwZeN8`YCviw|L9Y2jN+Mfp;a{ zDD7I|fxHgRJ|3g&=b49I)lfYd&jY4SSP1zCrQs@1qq-NaRr@@qty*aVO14xKYot(; z27MlM*QHGaHgRacA}hNP-)e1%g&F-P-5kG9Eok0E1b}Svd0BCY+xGP+Oz=9emYUcW z!?sjwRyu8@y=Rc@n6u*~oHvR^dP(|B`rFbPULgG&)IZYg2*$X&Ebjon5MXaw$6!6Z z8m8vV!0~a;5SkNvZU!4FL{0whgT6%iK3I4cmTe68nSM?#;s@Q%y!X;YVs~=K@3Op8 zv9lMKN#7N`p0PZf8R?Di0OFeDMpnqylurCuoTzm}V|5<0l}>tQM_qt^U(wo~;{mZo9IyZunbY&KkZwDPkgz69@ zzXk_^JEdC-J?4~@vNV==dB)udReqe-JRxKJxb$p4j)l^wIw?VH{1m^m$6+3jcSWxLq674-<812nie@YsVKug4T%4(%7x)6uBbkh3hBtaO!QLZOr8P_il56;o*&Tz$&Z?x=s_Mm zG&1~3bl!3xZzq@3@Q%#$RbIKu$FD62ZUN(5|5u8rlWq{p{)M*p;CZ^IWs&L*^Rq>Dx1l&oql!@zLw@ z9K3^MoQ~ry3HOdlcU#>GcInub<Jdf7D0~;93(?N#XMcZx zUY{cWeiO|{{LeO!AIJCaQv2l#ibX+{e}$^j)Jn_L ziqyWuOLE59QkF-^Pg(B9xIFuT?(s&nL)g|BTNb%zVXHx+`wn zK!aRTC3c_0YyeVc!O#%SoC!u)z~0_bjFD2M!2$f<30^0|Qc{Y;W$oG@6UJNF3RtVHbET2V&Vs zJ1`w4-GM2vo1-XtDjZWNgkZ)5U0Km6=cov2P2Fiq3=;B%{WlRSl{Lg2JLG0ph&OR_ z4s#ySM(_xZWQVSnvd4mHor}JfO7hs1t^yD@8lCaOp`7^p12vD9j`LVW^DW+1QT06I zD#=#d#)j|`@R?0MeZtYBsMp1iW$ECcV!^il*a(*RgK~l*hIbI5Wk+Ts=@Y&?S3 zF7dNxF+7YKyBQtTtV&xYoA<=Xdy3>eS@NDXHR_xs_+$RVlO^w|l*gOn8pUmyf6>ZT z!_&5)JYA)mOP*F57Co)-|1pIr=A(}KKJ4R|jk9$xVatzi6e^a&#Jw7v%tBU%)yjDp z5SLI|#xf~$3%B@_h4UF%dww0nL23!-mwAe(UP6AvoGPHeby64(gT&n~4t%yvVGz-MA9WO2LKV#*HAT2;we1#!ja-F_4sP z(%n`Y1ZRQ+iW@}@f(RN>j6()QR8$agj?eEuw_erj>Mrn6r{2Bi{^$JXoI5?~WYy1R zZPwZPaCCI^|Hw{oQ>v6Or+EN*1SwGIe!Cn;N)O*B{VUX4knKQP49xJJL#6_>QMyGs zlQtk*ePld85JuJ(ww?e`*zG|BCXl3g@mJa$eONu*u$t{!t&@%!uoW>;(yOM}+5m5( zJsCrv899TL`o*q!7@J0%8ANRWb4YG#46TE;WDlF{VHPQLL!Wu!A-#zcpzR^)IqAjt zJxib1+91#YTIINKY5(l`26CZ8o9cv?eJ@KbYSkjTZ^05>H7vb=CVmdS&-V=dn6L+> z-#qN;Ep@zC)yWP6@nXDRdRh9Kww<$%nB|-C5zj*bS(D3ilM93&F&UoFjncg~nKW;Q zhJorN>8o%>MBAnFr0?lcplZFu6y@h|sA20%%TyFWBZBG&>7Q^#>_5f&IUt>H2Zj{d z6zYn4uMA;Sda1SxxP!$u#|+A(OZMVprO2Up1#}os3s2IXtBUr#lYp1yeaCU{w3Ym<6QJWgR_}s<}3|n$D02;>G>$vWHFTU>A%d{jEuT-%URRFFg?uhpbuI34{oIwYUaq$ce|;wnUO$dQI6@7<&}&)ZVBpP)UW3p!`3 zkGgB62Vjhq&qP(saV5hnN#~ftYq``}u^_lc^R+YN8;WUWYM(Z(Y4##>fD7-%gErlQ zSZi^43UgAs?4#8C(oM4K{1Il@KE%7r_kiE-Tk>RHW?SF9Ni3Lb<n0kboW36cfj>lR0#`OpgW5UBPps@wLaBT^j%2uP2bwohPn&63FQ95f(B@RL&A4` z2UIhV?gxD+%+h+*gXaTfbhI)y4 zK+<{517EAP%-twq2P1Y!L*NFCZBi3hKIZyLSUZ+E^HpCrYctS6Bw4TZKe09JhS`W) z;}>l%=~61k%gc0PU}mHGKvy?y($th@X0&aa!_c*ap$~lhoaWf{OT8S`=qa6QQA-|? z9thKBHG`pN9}DJgSkkRqRjVmYy`gR-jE{ST&e95w-7wbaJH5N8i>tL79<%lqLBx|@w3q>|F3S=k2dMs>&SB0M6!7ef&OFf*CbH;V)z#c_r$}7FG^VQ_KAag=)YRl9 zI#*aC?9)EX?Pao8dtd1l^iHgBS(j0oJH{b$hsTf2?X7#MbltjQDFzOW&&sjL@{{Dm zd03qG3HfooJ67oJ)iMKn7od8;(Bgl9z@>IBm3-}O!jCGOa-Zfzbs>T{yYZ4 zo>%ARJ>=tL881D^QVhIE*skrMdyynR*d-2!jt-OA?__RHZEYrX=KRO=4s{fshod?g zNzdy%+7hX49p2E|YO3DlsPYoghhf5{n9xLIJE(&zu#JwI%^?kt)08*vlk0{f9MduE zO&o9+jc-oi^Z>+ku!HfKk`TQ6W#}BobXwCVbfQjm5IUn$y5s>xj@c}xpY<92686yF zZ&v4S!`-`TZr0?a6@2aOUW|`^%sJPfta#Q`%c)%-QZ73=@kyT;^dp_v#p_aoA-s5D z7RNdn8}k)MS|E51(064qN7R)cwR`9vG4FN^c}U-FxqqMW1N3go{og%C@79tMvc+W8?&qU8E6> zu$pV1jjdjs!e!L1V&Q-e6vm!?ggB1_@vtG1Y}Cd)r9EMQB{Zmkpq2nF5Dly@G0 z+5RvPiUOFt*6TG|gTi63QwCT(PBv)6pqv=lg(vTr@KS#wUL)D1UAJ_rSjJjQV9cZw zG@RDyV%DoNY3{!e?<3@YH|%jBVrXsd**aAG9Rb6~IsTi&s7WN?Ju`N@V7IMj?0w)Y qnK8SER!<@J+pQq^*~?xHd3Z^Xytw%ng)Y&N&$e)(3c)c*%;iLihG literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp index 576698c78..692883dd9 100644 --- a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp @@ -23,9 +23,11 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include +#include #include "Magnum/DebugTools/CompareImage.h" #include "Magnum/Image.h" @@ -46,6 +48,17 @@ #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/MeshData.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Primitives/Plane.h" +#include "Magnum/Primitives/Square.h" +#include "Magnum/Shaders/Generic.h" +#endif + #include "configure.h" namespace Magnum { namespace Shaders { namespace Test { namespace { @@ -54,16 +67,38 @@ struct VertexColorGLTest: GL::OpenGLTester { explicit VertexColorGLTest(); template void construct(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffers(); + #endif + template void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + template void constructMoveUniformBuffers(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + template void constructUniformBuffersZeroDraws(); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + template void setWrongDrawOffset(); + #endif void renderSetup(); void renderTeardown(); - template void renderDefaults2D(); - template void renderDefaults3D(); + template void renderDefaults2D(); + template void renderDefaults3D(); + + template void render2D(); + template void render3D(); - template void render2D(); - template void render3D(); + #ifndef MAGNUM_TARGET_GLES2 + void renderMulti2D(); + void renderMulti3D(); + #endif private: PluginManager::Manager _manager{"nonexistent"}; @@ -89,24 +124,115 @@ struct VertexColorGLTest: GL::OpenGLTester { using namespace Math::Literals; +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + VertexColorGL2D::Flags flags; + UnsignedInt drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, 1}, + {"", VertexColorGL2D::Flag::UniformBuffers, 1}, + {"multiple draws", VertexColorGL2D::Flag::UniformBuffers, 128} +}; +#endif + +#ifndef MAGNUM_TARGET_GLES2 +constexpr struct { + const char* name; + const char* expected2D; + const char* expected3D; + UnsignedInt drawCount; + UnsignedInt uniformIncrement; + Float maxThreshold, meanThreshold; +} RenderMultiData[] { + {"bind with offset", "multidraw2D.tga", "multidraw3D.tga", + 1, 16, 0.0f, 0.0f}, + {"draw offset", "multidraw2D.tga", "multidraw3D.tga", + 3, 1, 0.0f, 0.0f} +}; +#endif + VertexColorGLTest::VertexColorGLTest() { addTests({ &VertexColorGLTest::construct<2>, - &VertexColorGLTest::construct<3>, + &VertexColorGLTest::construct<3>}); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({ + &VertexColorGLTest::constructUniformBuffers<2>, + &VertexColorGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + #endif + + addTests({ &VertexColorGLTest::constructMove<2>, - &VertexColorGLTest::constructMove<3>}); + &VertexColorGLTest::constructMove<3>, - addTests({&VertexColorGLTest::renderDefaults2D, - &VertexColorGLTest::renderDefaults2D, - &VertexColorGLTest::renderDefaults3D, - &VertexColorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::constructMoveUniformBuffers<2>, + &VertexColorGLTest::constructMoveUniformBuffers<3>, + #endif - &VertexColorGLTest::render2D, - &VertexColorGLTest::render2D, - &VertexColorGLTest::render3D, - &VertexColorGLTest::render3D}, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::constructUniformBuffersZeroDraws<2>, + &VertexColorGLTest::constructUniformBuffersZeroDraws<3>, + #endif + + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::setUniformUniformBuffersEnabled<2>, + &VertexColorGLTest::setUniformUniformBuffersEnabled<3>, + &VertexColorGLTest::bindBufferUniformBuffersNotEnabled<2>, + &VertexColorGLTest::bindBufferUniformBuffersNotEnabled<3>, + &VertexColorGLTest::setWrongDrawOffset<2>, + &VertexColorGLTest::setWrongDrawOffset<3> + #endif + }); + + addTests({ + &VertexColorGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::renderDefaults2D, + #endif + &VertexColorGLTest::renderDefaults2D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::renderDefaults2D, + #endif + &VertexColorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::renderDefaults3D, + #endif + &VertexColorGLTest::renderDefaults3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::renderDefaults3D, + #endif + + &VertexColorGLTest::render2D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::render2D, + #endif + &VertexColorGLTest::render2D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::render2D, + #endif + &VertexColorGLTest::render3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::render3D, + #endif + &VertexColorGLTest::render3D, + #ifndef MAGNUM_TARGET_GLES2 + &VertexColorGLTest::render3D, + #endif + }, + &VertexColorGLTest::renderSetup, + &VertexColorGLTest::renderTeardown); + + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&VertexColorGLTest::renderMulti2D, + &VertexColorGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), &VertexColorGLTest::renderSetup, &VertexColorGLTest::renderTeardown); + #endif /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ @@ -147,6 +273,33 @@ template void VertexColorGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); } +#ifndef MAGNUM_TARGET_GLES2 +template void VertexColorGLTest::constructUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if((data.flags & VertexColorGL::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + VertexColorGL shader{data.flags, data.drawCount}; + CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #ifdef CORRADE_TARGET_APPLE + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} +#endif + template void VertexColorGLTest::constructMove() { setTestCaseTemplateName(std::to_string(dimensions)); @@ -166,6 +319,121 @@ template void VertexColorGLTest::constructMove() { CORRADE_VERIFY(!b.id()); } +#ifndef MAGNUM_TARGET_GLES2 +template void VertexColorGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + VertexColorGL a{VertexColorGL::Flag::UniformBuffers, 5}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + VertexColorGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), VertexColorGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + VertexColorGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), VertexColorGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VertexColorGLTest::constructUniformBuffersZeroDraws() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + VertexColorGL{VertexColorGL::Flag::UniformBuffers, 0}; + CORRADE_COMPARE(out.str(), + "Shaders::VertexColorGL: draw count can't be zero\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES2 +template void VertexColorGLTest::setUniformUniformBuffersEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + VertexColorGL shader{VertexColorGL::Flag::UniformBuffers}; + shader.setTransformationProjectionMatrix({}); + CORRADE_COMPARE(out.str(), + "Shaders::VertexColorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n"); +} + +template void VertexColorGLTest::bindBufferUniformBuffersNotEnabled() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + GL::Buffer buffer; + VertexColorGL shader; + shader.bindTransformationProjectionBuffer(buffer) + .bindTransformationProjectionBuffer(buffer, 0, 16) + .setDrawOffset(0); + CORRADE_COMPARE(out.str(), + "Shaders::VertexColorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VertexColorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::VertexColorGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} + +template void VertexColorGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(std::to_string(dimensions)); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + std::ostringstream out; + Error redirectError{&out}; + VertexColorGL{VertexColorGL::Flag::UniformBuffers, 5} + .setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::VertexColorGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} +#endif + constexpr Vector2i RenderSize{80, 80}; void VertexColorGLTest::renderSetup() { @@ -193,8 +461,20 @@ void VertexColorGLTest::renderTeardown() { _color = GL::Renderbuffer{NoCreate}; } -template void VertexColorGLTest::renderDefaults2D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void VertexColorGLTest::renderDefaults2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VertexColorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates); @@ -207,8 +487,21 @@ template void VertexColorGLTest::renderDefaults2D() { GL::Mesh circle = MeshTools::compile(circleData); circle.addVertexBuffer(colors, 0, GL::Attribute{}); - VertexColorGL2D{} - .draw(circle); + VertexColorGL2D shader{flag}; + + if(flag == VertexColorGL2D::Flag{}) { + shader.draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VertexColorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -230,8 +523,20 @@ template void VertexColorGLTest::renderDefaults2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void VertexColorGLTest::renderDefaults3D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void VertexColorGLTest::renderDefaults3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VertexColorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) @@ -248,8 +553,21 @@ template void VertexColorGLTest::renderDefaults3D() { GL::Mesh sphere = MeshTools::compile(sphereData); sphere.addVertexBuffer(colors, 0, GL::Attribute{}); - VertexColorGL3D{} - .draw(sphere); + VertexColorGL3D shader{flag}; + + if(flag == VertexColorGL3D::Flag{}) { + shader.draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VertexColorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -267,8 +585,20 @@ template void VertexColorGLTest::renderDefaults3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void VertexColorGLTest::render2D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void VertexColorGLTest::render2D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VertexColorGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::Circle2DFlag::TextureCoordinates); @@ -283,9 +613,23 @@ template void VertexColorGLTest::render2D() { GL::Mesh circle = MeshTools::compile(circleData); circle.addVertexBuffer(colors, 0, GL::Attribute{}); - VertexColorGL2D{} - .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + VertexColorGL2D shader{flag}; + + if(flag == VertexColorGL2D::Flag{}) { + shader.setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) .draw(circle); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VertexColorGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(Matrix3::projection({2.1f, 2.1f})) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .draw(circle); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -308,8 +652,20 @@ template void VertexColorGLTest::render2D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } -template void VertexColorGLTest::render3D() { - setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); +template void VertexColorGLTest::render3D() { + #ifndef MAGNUM_TARGET_GLES2 + if(flag == VertexColorGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName({T::Size == 3 ? "Color3" : "Color4", "Flag::UniformBuffers"}); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } else + #endif + { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereFlag::TextureCoordinates); @@ -324,13 +680,32 @@ template void VertexColorGLTest::render3D() { GL::Mesh sphere = MeshTools::compile(sphereData); sphere.addVertexBuffer(colors, 0, GL::Attribute{}); - VertexColorGL3D{} - .setTransformationProjectionMatrix( + VertexColorGL3D shader{flag}; + + if(flag == VertexColorGL3D::Flag{}) { + shader.setTransformationProjectionMatrix( Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* Matrix4::translation(Vector3::zAxis(-2.15f))* Matrix4::rotationY(-15.0_degf)* Matrix4::rotationX(15.0_degf)) .draw(sphere); + } + #ifndef MAGNUM_TARGET_GLES2 + else if(flag == VertexColorGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::rotationY(-15.0_degf)* + Matrix4::rotationX(15.0_degf) + ) + }}; + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .draw(sphere); + } + #endif + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -353,6 +728,221 @@ template void VertexColorGLTest::render3D() { (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } +#ifndef MAGNUM_TARGET_GLES2 +void VertexColorGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + /* Circle is a fan, plane is a strip, make it indexed first */ + Trade::MeshData circleData = MeshTools::generateIndices(Primitives::circle2DSolid(32)); + Trade::MeshData squareData = MeshTools::generateIndices(Primitives::squareSolid()); + Trade::MeshData triangleData = MeshTools::generateIndices(Primitives::circle2DSolid(3)); + /* Concatenate the meshes, reserve a vertex color attribute and fill it + with a ... RAINBOW! */ + Trade::MeshData colored = MeshTools::interleave(MeshTools::concatenate({circleData, squareData, triangleData}), { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, VertexFormat::Vector3, nullptr} + }); + Deg angle = 0.0_degf; + for(Vector3& i: colored.mutableAttribute(Trade::MeshAttribute::Color)) + i = Color3::fromHsv({angle += 360.0_degf/colored.vertexCount(), 1.0f, 1.0f}); + GL::Mesh mesh = MeshTools::compile(colored); + GL::MeshView circle{mesh}; + circle.setCount(circleData.indexCount()); + GL::MeshView square{mesh}; + square.setCount(squareData.indexCount()) + .setIndexRange(circleData.indexCount()); + GL::MeshView triangle{mesh}; + triangle.setCount(triangleData.indexCount()) + .setIndexRange(circleData.indexCount() + squareData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({-1.25f, -1.25f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 1.25f, -1.25f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})* + Matrix3::translation({ 0.00f, 1.25f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + VertexColorGL2D shader{VertexColorGL2D::Flag::UniformBuffers, data.drawCount}; + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.draw(circle); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.draw(square); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.draw(triangle); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform); + shader.setDrawOffset(0) + .draw(circle); + shader.setDrawOffset(1) + .draw(square); + shader.setDrawOffset(2) + .draw(triangle); + } + + 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."); + + /* + - Circle should be lower left + - Square lower right + - Triangle up center + */ + 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, "VertexColorTestFiles", data.expected2D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} + +void VertexColorGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32); + /* Plane is a strip, make it indexed first */ + Trade::MeshData planeData = MeshTools::generateIndices(Primitives::planeSolid()); + Trade::MeshData coneData = Primitives::coneSolid(1, 32, 1.0f); + /* Concatenate the meshes, reserve a vertex color attribute and fill it + with a ... RAINBOW! */ + Trade::MeshData colored = MeshTools::interleave(MeshTools::concatenate({sphereData, planeData, coneData}), { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, VertexFormat::Vector3, nullptr} + }); + Deg angle = 0.0_degf; + for(Vector3& i: colored.mutableAttribute(Trade::MeshAttribute::Color)) + i = Color3::fromHsv({angle += 360.0_degf/colored.vertexCount(), 1.0f, 1.0f}); + GL::Mesh mesh = MeshTools::compile(colored); + GL::MeshView sphere{mesh}; + sphere.setCount(sphereData.indexCount()); + GL::MeshView plane{mesh}; + plane.setCount(planeData.indexCount()) + .setIndexRange(sphereData.indexCount()); + GL::MeshView cone{mesh}; + cone.setCount(coneData.indexCount()) + .setIndexRange(sphereData.indexCount() + planeData.indexCount()); + + /* Some drivers have uniform offset alignment as high as 256, which means + the subsequent sets of uniforms have to be aligned to a multiply of it. + The data.uniformIncrement is set high enough to ensure that, in the + non-offset-bind case this value is 1. */ + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, 0.0f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)* + Matrix4::translation(Vector3::zAxis(-2.15f))* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.0f, 1.0f, 1.0f}) + ); + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, transformationProjectionData}; + + VertexColorGL3D shader{VertexColorGL3D::Flag::UniformBuffers, data.drawCount}; + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.draw(sphere); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.draw(plane); + + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.draw(cone); + + /* Otherwise using the draw offset */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform); + shader.setDrawOffset(0) + .draw(sphere); + shader.setDrawOffset(1) + .draw(plane); + shader.setDrawOffset(2) + .draw(cone); + } + + 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."); + + /* + - Sphere should be lower left + - Plane lower right + - Cone up center + */ + 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, "VertexColorTestFiles", data.expected3D}), + (DebugTools::CompareImageToFile{_manager, data.maxThreshold, data.meanThreshold})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::VertexColorGLTest) diff --git a/src/Magnum/Shaders/Test/VertexColorGL_Test.cpp b/src/Magnum/Shaders/Test/VertexColorGL_Test.cpp index 34a8b27d9..1f86165ac 100644 --- a/src/Magnum/Shaders/Test/VertexColorGL_Test.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGL_Test.cpp @@ -23,7 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include "Magnum/Shaders/VertexColorGL.h" @@ -36,6 +38,9 @@ struct VertexColorGL_Test: TestSuite::Tester { template void constructNoCreate(); template void constructCopy(); + + void debugFlag(); + void debugFlags(); }; VertexColorGL_Test::VertexColorGL_Test() { @@ -44,7 +49,10 @@ VertexColorGL_Test::VertexColorGL_Test() { &VertexColorGL_Test::constructNoCreate<3>, &VertexColorGL_Test::constructCopy<2>, - &VertexColorGL_Test::constructCopy<3>}); + &VertexColorGL_Test::constructCopy<3>, + + &VertexColorGL_Test::debugFlag, + &VertexColorGL_Test::debugFlags}); } template void VertexColorGL_Test::constructNoCreate() { @@ -65,6 +73,30 @@ template void VertexColorGL_Test::constructCopy() { CORRADE_VERIFY(!std::is_copy_assignable>{}); } +void VertexColorGL_Test::debugFlag() { + std::ostringstream out; + + #ifndef MAGNUM_TARGET_GLES2 + Debug{&out} << VertexColorGL3D::Flag::UniformBuffers << VertexColorGL3D::Flag(0xf0); + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag::UniformBuffers Shaders::VertexColorGL::Flag(0xf0)\n"); + #else + Debug{&out} << VertexColorGL3D::Flag(0xf0); + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag(0xf0)\n"); + #endif +} + +void VertexColorGL_Test::debugFlags() { + std::ostringstream out; + + #ifndef MAGNUM_TARGET_GLES2 + Debug{&out} << (VertexColorGL3D::Flag::UniformBuffers|VertexColorGL3D::Flag(0xf0)) << VertexColorGL3D::Flags{}; + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag::UniformBuffers|Shaders::VertexColorGL::Flag(0xf0) Shaders::VertexColorGL::Flags{}\n"); + #else + Debug{&out} << VertexColorGL3D::Flag(0xf0) << VertexColorGL3D::Flags{}; + CORRADE_COMPARE(out.str(), "Shaders::VertexColorGL::Flag(0xf0) Shaders::VertexColorGL::Flags{}\n"); + #endif +} + }}}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::VertexColorGL_Test) diff --git a/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw2D.tga b/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw2D.tga new file mode 100644 index 0000000000000000000000000000000000000000..b1bb643d551c60e9563bc718f06db422dc9d7709 GIT binary patch literal 6852 zcmcJT33yahwuOHn0Yf4t1P}wHK^%c*P(cL&K}7@!AZ=(710te`i~X2|fefQaok2O}H#LbzTlT zb_X^mRyXAyEMClhOaaO?j3G+H7^0LY^cb&&YSa&(sO9-8vHa82lC@0=K4@_<`>r1* zwLU+p&AB0M&tZC?UR++z7BmD@4SMD#L| ztAq;(T_boMt%yJgegnQzyk&SyxXrlAaawWMa5%6#vAM8%DEDIVV-8@_C<|c>QyRgb z&|@4IsL>$!Q@s6HvSC-+)lY8EU)AdLf(|F9cRD)0%i$4S4`y~h)Ti6wZe72=zw@_u zcRby+!-WQI3X<+Ajl089vt_78OO+qfnSLIrF!T~~ndns_1%$5=x=yeNt(brTe_^xct_vHh9;+k5rj(K+MGc749Oe^*EN&^-SG|ev0o@ z9M{QKvy)b%qe6*kPX8XcVeVz*3ef^0*9aF9Dk4~nRzjc@zY(7auNjX8w-uKSrya%D z;ll35=EdrxJb*>R9K;l+EP_#?$N0XlMq{TlzO1-@;SaycKicZsyp9Lgci*+N_qN#s zHc!d?^DEEi4#`@VF??Om;kli%HntwJxq0UHTLuhvh`$V<8LtITIc^&+J5HUo!;RgG&4<-b zxrQZ(IfN-fnL>}T{+$|WmT;{iYx1RQ&CZ=|b^P#yf8EhDFX!paA7N9asw~34IAHV$WF{_)7%5CuC=9^#GnfSu~grO(u482f$NO9b69W^r}3{r?O zFVfE-|Ht&J$TgycM6MGqCR9SufMz66hTn|Og0~!x4YwVa1E&kc-QmIR!{*1TQ69t+ z!W_Y*&~I?R5eaoJwb3c7<^&dwqS~zj`wCU5v%$YoN;rRZG$M#%0w$sw_trt&j zzHs`j^X81XS_T@XLjT60pE@d`Iq)Aa9 zrmmcovhu^^<$tWVbW75Qd+PrF_)YI$s6E#ZH_Kb&y($H#kbhK$>qrsN5+Vk|rG&}| zn$XMy%JEzA+3-5>IB~mhd2s3q90BYawh-1Z<&_HNs(rKF(Hh&GDW4R#$jR@p>R68@ zdj~Aonl&qT?6l?6C(WHd`puQYM&&%4xut*a?R~rK?AvZ<|CZY`(>@*EX!Dqco8L;^ zG&g1AvRl@#zxksbiE9rgtU6m~d12gQN6pyFTra~(S&|Z@l&FzN8DTRa3&CW0Q9;wwZFW>+Ab6MsCe|e%<(ImSp#ty}0L;oX)Rozi-IF z*8R@h-u-G?J40Ga!yUgW?$E68(I!_1G`={zQT~Jm=VsRbZpkgj*WY|JFY(~foA&)s zdzT?D&sSqxm5yV`&nbxkF%m5!VkT@MR8G)_W+&jl@51NC>%rs0?Z>6z)O9$j>zL(9 zdb_mQgrC~JdZPQVz5NG&Haz2_iMN<1j18;0-GjeCk%%e?u=cjfyB)0J+-R^7B z+}%3e_DB(u;OdO}7Z)ez=iYp7N8+i&3CGXZK3W`iz*BQ?m5y(apIH(k zQbyE7#6q~7kd>ev%|XD0-;K|M*N4ZCJAf;QGsOSWG1ZeawzS#HKeZitqT8Uo{ri44 zyyr&~J1?HuVdm0%-`LQ6;xTXP6BTH9(-QBemnu(8mh&1oOB`Wr z^4!HX15bACy)Wa@tyvGQ8{ck8_Fc0Vr%%mk^y>DTha8OWcbc9Bbg|PVL|2{lqe4&N zaiLcg@qR*oVW99EAyfG64IM|2f2$-W#7wlDh?TI7kb|HT%|*b2-;2+WH-JaO9l}*z z$1s1wU{hMg)h=?pN>0*{CdNfJ6B9=`u@TO)7B)7+Z;b)H-nFnagnFWc*;XY zlnjY&19aAdglC1p!VqDokR=QkMhGw8&~X^~cS~YMEJUqDY=j+zoCIBH9s*wcK70YZ z8lE8TYC2M$4aN7fHhlcrosXRB_`s1TT7Q|@@{>_b)=o}cG&^zT()g*ljM>46BRu~D zgNqn!ICn9lh{;U!_D@QN^67$b}o#tRc~2swoO2Q{%ER-!f{cEV0VE`n|} zF99F^06qUmOoaX`co6x|3Q~^Ph}wxb3A+fn z33}0d1pN3ld_laGB4WQ$^&j>ob|_1`_sYF3&OCJ6;U`nR%uL+!a{Ri-F62!M)4XIy$(96Wh}y-rnZj&gjxbMnUzmSG!U5zzE`<%T6Lk=A5q1;u z5cHw>3269(_^L^0+CCh2kG+2Lq85!Wv`;?KJ#l|VeBKLuGM07OtX#^68F`iaIC z@mJb&?oo~pV*kthVG2W4e;a;vh!ukGu`yxjsv2PZCS;`kV zZ2O$8U$ONYww>dPE9^3|*TKO6M};H8VX4KT*iLNKm<;?4xc$cFKIk<-Xo7k7fz60z($-xVJUC0SDr`?&0l?=SIV374%DdSh!u2B9RzR9`#Ujr@Y^B~yJRifDWMJ#Y-f zG=h=|6wctv2V7di#gDo81(*KHl@k=6r=$RrF}C`q{&(`nIO;1=yO3YjP#@wasu8K= zsITG?ej49UT(4n!3-dfo%P{6({0!4B%!jdkhwDdtYN;dk-K?+TeTn?y_W6+j(Mn#i zHO!j`O+w4Y|9gC^@omJv9c>@sZ`8ddYNYQ({yz)~5c5B_wsGcXj&&-o=3U6`dgf>9 zEOw>V-AFq`T~{ZcR$h$17kNP6S|Kg;TbnCy)lpYEAw7_XkhaL3NK-vvQ)R$St&stDi9D?hP;yl%RNXOS0?*O9TvOUMvio8qDFDl3&K z%J;^|EqeN&D$`T%*O~Q2)LbKwamd@qMC28ny~0$tdLj>36{#7Ls`%+3Yb!w-cGMkI zRDY{aJsz2cyoXFd#^|umA^nkFNLNJ7SW(nlk$OnH4z;=xs*x(KnohZ_QeH+TAk&fe zky$!1bvg=pLAR;T-!wse)W1iuHddj^jaZRhaaTR2>PFSeBmN1!DoU8TK0v=! f_2Q|e3Z|~8SyX)0i>jt_P#K}F#NKEB>P`4Ru&%Bq literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw3D.tga b/src/Magnum/Shaders/Test/VertexColorTestFiles/multidraw3D.tga new file mode 100644 index 0000000000000000000000000000000000000000..18c5b661d63ce113811f5421cac28ddacf3ae16d GIT binary patch literal 4828 zcma)A33OED75*jy$;<>uLN*9Uz_2B1#k5$9tWpt0OHn|qibb(lse%*)L^KPFtO8*d zWYby^+k=W$yV;g=sv;cL3Ic){P$*%EENMf&e*b+l&*?Dql=ENa@!q}Pckli0yZ3(p zAw>W5LqFtVYkGS6|0DCHmjZqY?#GdD@aZ}1K94OIu&EaFsAq3V=13ZXZ-TSIGH@!m z9{dE{0&WF&fqTI(QGE>is--$pQuekqH-hEwNZ!@;?kAFjs>4F%DLdn;9q6=yrevV*HLsARi+Rdv1ta=vKY=~aua3**$(Ui zb_2VEJ-}XIZwsqGI2e4$0u|YdvY!^=h>xHo62S~T&u+}LT%t8mL|jL7B3cq+3b9?- zmM%i5LA@Vr8Fmvfoq5^L~r6w;x1w^ zaTAeGWHT+B={d~Eb}KWR*_q5~!e;4g(TMqB79_I+_#N=(OF83%3@AuxxjZ}CU=Lyd zF_d_W7)6Y=C1yF&mME2k3oH+vUPxk!kHo|QKX6XE}n7( zF@Y!}W)LqB6;zEo;(Li+qLifeB)St_iO$3gL@}YeSQUDyB=te!0pfm3`#_>U(bx8(n{9p(uV~4}5z5lb z1RA9bEhg3z?-3smeP1rkF=h z8M`vjnGk8Yq%1+0r7@&=+b%ew!&NwX9LK-LiKD2h#EB!0_Y>kBTkjHDx_+B@i%{m3 zgx85zi8aJ>Vj(e$C?SSjLL3|Ie-T$!;pA61dl)qbQM(@(KLhumZV#%C;6OFDU&Qu0 z?C=(`o!CZf)iw|x5}RySes2!Rk5xt=iMTvL#3eBihiWQ8k!=G%0ykM(`~#?0>K`~+ zi9@FlIC#?ilt@w!oyMUvI9QF(&*H#&?61MTTI?lu($#`WW1SyyKwVwj(ZY%iJeEI? z+rdA9YQ1lOzXsQWtH2eY8u4zNI*gNF;N&5k(%)a8O6xFA9L7Jt#PK6I_7%Q9j-yqm z)Sg|yK~+pVf1(`^D1N2Ky-H5<+$d19UJA|wXM)qf$?_arXnk&{n?!XxQMUsZZPo6; zzjvZ$Cob&5x!pLs2WLLR>CaImr{qp;wAxCPf+&A%{4xBl+QXb~Q)3MH1gKH+Gw@;X zNpoj|C1RthX(PfeCz^S3|9$X};0NH}zz1Er>~2SF4MfC=Hr}y0Y0A;yPi;*0 z2k!v;fVY6R*+`iKF1ED@)YA15%gu|nE7}|FkS3^hOMCX0C}rtoVs&(G`h*-oXYjk$ z)eGUKTbqEpsR=qB9EVa#$X!kVCxVl}=WLQ{H)h#Ztc&G}53fvl`If6=k{i;kQrV(0 z^U~Q=!zGLP$!?eq1P6#~0D|svkj>Jc*mzSy9sx&yV;qrd|78h+iW0f89b#@4)0(qk zKEqcr*p?~nsoByz%wq6*XnvMRQrx9(DD+O?jo?i-Q*U)FZk#%n_;5-i;kctmCQ)b| zTLVl}}*+FJCVAd7P2J>8!BOK)kB8hUiV^feAL_BOW zb0jgE7|x_(1`C-|$Y4g7`>EB9XYWUW&~~ zwZ|32T5FgZ7$k{|W56DwJb}gYo(qOaBZ(tRwPNDid&J+LAU6(8ekSL)s0I?_J zMKZ}BW@k~A*bu9=`@#D_)t%P8R)hDL!ZM5(8`U25=KTtM#Tu4g z#MRMmDUOAb#gZ#M7N5eyA8fC?>RnYYAJlASn5`)O2fjSGycAYGd&tYo^F{Wd5y4-o z-)73TkYizmspGVouGV8GDz`OgXHCEVhrA`IZ^ywIO@3o~u4)U+{Ko z%|ay*jHRvd(B6gvQnh9%b8dZ`7_lfUc@8A5s2LA9!2P4)1$L}ZmR4R zi-(}(%(VPWv)7@_c0Yo%LcbY8kORK?jW&o>e$te!mZF$$z6n;bl!DJ%M9vFC=w{}k zmhJb!8*G{lw<+!0%KyoRsj7a)wpG>+;U!7h56rNW8EP07TRBNhK6{4ZmEm(SwvO3s zMJmScnN>$OSzXyoFgnj$8BPM9b|(pUx{;l;T`6fKuIs_;Y+Y;R@stH96UC@{_N=ta z5jL}udoq#!HDHl7YAHxxEHW8yM7s01Z?#I+snWk1)cK~pbuDeThcA8A{?nJGQ7_Xvyc`>B_nn2^ZvuvmsK0;z6+KHzvGhi?>`tn+m&l#i J`FpwR{{VP)C4&F} literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Vector.frag b/src/Magnum/Shaders/Vector.frag index 8f0f63795..94f0bb419 100644 --- a/src/Magnum/Shaders/Vector.frag +++ b/src/Magnum/Shaders/Vector.frag @@ -29,8 +29,13 @@ #define texture texture2D #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 2) #endif @@ -45,6 +50,33 @@ uniform lowp vec4 color #endif ; +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +struct DrawUniform { + lowp vec4 color; + lowp vec4 backgroundColor; + lowp uvec4 reserved; +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; +#endif + /* Textures */ #ifdef EXPLICIT_BINDING @@ -66,6 +98,11 @@ out lowp vec4 fragmentColor; #endif void main() { + #ifdef UNIFORM_BUFFERS + lowp const vec4 color = draws[drawOffset].color; + lowp const vec4 backgroundColor = draws[drawOffset].backgroundColor; + #endif + lowp float intensity = texture(vectorTexture, interpolatedTextureCoordinates).r; fragmentColor = mix(backgroundColor, color, intensity); } diff --git a/src/Magnum/Shaders/Vector.h b/src/Magnum/Shaders/Vector.h index e73d13435..f110efbfb 100644 --- a/src/Magnum/Shaders/Vector.h +++ b/src/Magnum/Shaders/Vector.h @@ -25,26 +25,104 @@ DEALINGS IN THE SOFTWARE. */ -#ifdef MAGNUM_BUILD_DEPRECATED /** @file - * @brief Typedef @ref Magnum::Shaders::Vector, alias @ref Magnum::Shaders::Vector2D, @ref Magnum::Shaders::Vector3D - * @m_deprecated_since_latest Use @ref Magnum/Shaders/VectorGL.h, the - * @ref Magnum::Shaders::VectorGL "VectorGL" class and - * related typedefs instead. + * @brief Struct @ref Magnum::Shaders::VectorDrawUniform */ -#endif -#include "Magnum/configure.h" +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" #ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Shaders/VectorGL.h" - -CORRADE_DEPRECATED_FILE("use Magnum/Shaders/VectorGL.h, the VectorGL class and related typedefs instead") +#endif namespace Magnum { namespace Shaders { +/** +@brief Per-draw uniform for vector shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D / +@ref TransformationProjectionUniform3D contains parameters that are specific to +each draw call. Texture transformation, if needed, is supplied separately in a +@ref TextureTransformationUniform. +@see @ref VectorGL::bindDrawBuffer() +*/ +struct VectorDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit VectorDrawUniform(DefaultInitT = DefaultInit) noexcept: color{1.0f, 1.0f, 1.0f, 1.0f}, backgroundColor{0.0f, 0.0f, 0.0f, 0.0f} {} + + /** @brief Construct without initializing the contents */ + explicit VectorDrawUniform(NoInitT) noexcept: color{NoInit}, backgroundColor{NoInit} {} + + /** @{ + * @name Convenience setters + * + * Provided to allow the use of method chaining for populating a structure + * in a single expression, otherwise equivalent to accessing the fields + * directly. Also guaranteed to provide backwards compatibility when + * packing of the actual fields changes. + */ + + /** + * @brief Set the @ref color field + * @return Reference to self (for method chaining) + */ + VectorDrawUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref backgroundColor field + * @return Reference to self (for method chaining) + */ + VectorDrawUniform& setBackgroundColor(const Color4& color) { + backgroundColor = color; + return *this; + } + + /** + * @} + */ + + /** + * @brief Fill color + * + * Default is @cpp 0xffffffff_rgbaf @ce. + * @see @ref VectorGL::setColor() + */ + Color4 color; + + /** + * @brief Background color + * + * Default is @cpp 0x00000000_rgbaf @ce. + * @see @ref VectorGL::setBackgroundColor() + */ + Color4 backgroundColor; + + /* warning: Member __pad0__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + /* This field is an UnsignedInt in the shader and skinOffset is extracted + as (value >> 16), so the order has to be different on BE */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + UnsignedShort:16; + UnsignedShort:16; /* reserved for skinOffset */ + #else + UnsignedShort:16; /* reserved for skinOffset */ + UnsignedShort:16; + #endif + Int:32; /* reserved for objectId */ + Int:32; /* reserved for alphaMask */ + Int:32; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief Shaders::VectorGL * @m_deprecated_since_latest Use @ref Shaders::VectorGL "VectorGL" instead. */ @@ -61,10 +139,8 @@ typedef CORRADE_DEPRECATED("use VectorGL2D instead") VectorGL2D Vector2D; * @m_deprecated_since_latest Use @ref VectorGL3D instead. */ typedef CORRADE_DEPRECATED("use VectorGL3D instead") VectorGL3D Vector3D; +#endif }} -#else -#error use Magnum/Shaders/VectorGL.h, the VectorGL class and related typedefs instead -#endif #endif diff --git a/src/Magnum/Shaders/Vector.vert b/src/Magnum/Shaders/Vector.vert index 17cb41708..e0fbab898 100644 --- a/src/Magnum/Shaders/Vector.vert +++ b/src/Magnum/Shaders/Vector.vert @@ -28,8 +28,13 @@ #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif @@ -60,6 +65,51 @@ uniform mediump mat3 textureMatrix ; #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + highp + #ifdef TWO_DIMENSIONS + mat3 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrices[DRAW_COUNT]; +}; + +#ifdef TEXTURE_TRANSFORMATION +struct TextureTransformationUniform { + highp vec4 rotationScaling; + highp vec4 offsetReservedReserved; + #define textureTransformation_offset offsetReservedReserved.xy +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 3 + #endif +) uniform TextureTransformation { + TextureTransformationUniform textureTransformations[DRAW_COUNT]; +}; +#endif +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -83,6 +133,21 @@ in mediump vec2 textureCoordinates; out mediump vec2 interpolatedTextureCoordinates; void main() { + #ifdef UNIFORM_BUFFERS + highp const + #ifdef TWO_DIMENSIONS + mat3 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrix = transformationProjectionMatrices[drawOffset]; + #ifdef TEXTURE_TRANSFORMATION + mediump const mat3 textureMatrix = mat3(textureTransformations[drawOffset].rotationScaling.xy, 0.0, textureTransformations[drawOffset].rotationScaling.zw, 0.0, textureTransformations[drawOffset].textureTransformation_offset, 1.0); + #endif + #endif + #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix*vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) diff --git a/src/Magnum/Shaders/VectorGL.cpp b/src/Magnum/Shaders/VectorGL.cpp index 214f04ab6..e0c2531af 100644 --- a/src/Magnum/Shaders/VectorGL.cpp +++ b/src/Magnum/Shaders/VectorGL.cpp @@ -37,15 +37,51 @@ #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { namespace { enum: Int { TextureUnit = 6 }; + + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from other shaders which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1, + DrawBufferBinding = 2, + TextureTransformationBufferBinding = 3 + }; + #endif } -template VectorGL::VectorGL(const Flags flags): _flags{flags} { +template VectorGL::VectorGL(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _drawCount{drawCount} + #endif +{ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::VectorGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -65,9 +101,25 @@ template VectorGL::VectorGL(const Flags flag GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); vert.addSource(flags & Flag::TextureTransformation ? "#define TEXTURE_TRANSFORMATION\n" : "") - .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n") - .addSource(rs.get("generic.glsl")) + .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + } + #endif + vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("Vector.vert")); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + frag.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + } + #endif frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("Vector.frag")); @@ -92,11 +144,18 @@ template VectorGL::VectorGL(const Flags flag if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); - if(flags & Flag::TextureTransformation) - _textureMatrixUniform = uniformLocation("textureMatrix"); - _backgroundColorUniform = uniformLocation("backgroundColor"); - _colorUniform = uniformLocation("color"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + if(flags & Flag::TextureTransformation) + _textureMatrixUniform = uniformLocation("textureMatrix"); + _backgroundColorUniform = uniformLocation("backgroundColor"); + _colorUniform = uniformLocation("color"); + } } #ifndef MAGNUM_TARGET_GLES @@ -104,23 +163,51 @@ template VectorGL::VectorGL(const Flags flag #endif { setUniform(uniformLocation("vectorTexture"), TextureUnit); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"), DrawBufferBinding); + if(flags & Flag::TextureTransformation) + setUniformBlockBinding(uniformBlockIndex("TextureTransformation"), TextureTransformationBufferBinding); + } + #endif } /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); - if(flags & Flag::TextureTransformation) - setTextureMatrix(Matrix3{Math::IdentityInit}); - setColor(Color4{1.0f}); /* Background color is zero by default */ + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + if(flags & Flag::TextureTransformation) + setTextureMatrix(Matrix3{Math::IdentityInit}); + /* Background color is zero by default */ + setColor(Color4{1.0f}); + } #endif } +#ifndef MAGNUM_TARGET_GLES2 +template VectorGL::VectorGL(const Flags flags): VectorGL{flags, 1} {} +#endif + template VectorGL& VectorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } template VectorGL& VectorGL::setTextureMatrix(const Matrix3& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setTextureMatrix(): the shader was created with uniform buffers enabled", *this); + #endif CORRADE_ASSERT(_flags & Flag::TextureTransformation, "Shaders::VectorGL::setTextureMatrix(): the shader was not created with texture transformation enabled", *this); setUniform(_textureMatrixUniform, matrix); @@ -128,15 +215,80 @@ template VectorGL& VectorGL::set } template VectorGL& VectorGL::setBackgroundColor(const Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setBackgroundColor(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_backgroundColorUniform, color); return *this; } template VectorGL& VectorGL::setColor(const Color4& color) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VectorGL::setColor(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_colorUniform, color); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template VectorGL& VectorGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::VectorGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + setUniform(_drawOffsetUniform, offset); + return *this; +} + +template VectorGL& VectorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template VectorGL& VectorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +template VectorGL& VectorGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +template VectorGL& VectorGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +template VectorGL& VectorGL::bindTextureTransformationBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding); + return *this; +} + +template VectorGL& VectorGL::bindTextureTransformationBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::TextureTransformation, + "Shaders::VectorGL::bindTextureTransformationBuffer(): the shader was not created with texture transformation enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TextureTransformationBufferBinding, offset, size); + return *this; +} +#endif + template VectorGL& VectorGL::bindVectorTexture(GL::Texture2D& texture) { texture.bind(TextureUnit); return *this; @@ -154,6 +306,9 @@ Debug& operator<<(Debug& debug, const VectorGLFlag value) { /* LCOV_EXCL_START */ #define _c(v) case VectorGLFlag::v: return debug << "::" #v; _c(TextureTransformation) + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + #endif #undef _c /* LCOV_EXCL_STOP */ } @@ -163,8 +318,11 @@ Debug& operator<<(Debug& debug, const VectorGLFlag value) { Debug& operator<<(Debug& debug, const VectorGLFlags value) { return Containers::enumSetDebugOutput(debug, value, "Shaders::VectorGL::Flags{}", { - VectorGLFlag::TextureTransformation - }); + VectorGLFlag::TextureTransformation, + #ifndef MAGNUM_TARGET_GLES2 + VectorGLFlag::UniformBuffers + #endif + }); } } diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index dd5694fc4..c899e5130 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -39,7 +39,10 @@ namespace Magnum { namespace Shaders { namespace Implementation { enum class VectorGLFlag: UnsignedByte { - TextureTransformation = 1 << 0 + TextureTransformation = 1 << 0, + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 1 + #endif }; typedef Containers::EnumSet VectorGLFlags; } @@ -117,7 +120,23 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @see @ref setTextureMatrix() * @m_since{2020,06} */ - TextureTransformation = 1 << 0 + TextureTransformation = 1 << 0, + + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * instead of direct uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 1 + #endif }; /** @@ -137,9 +156,44 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL /** * @brief Constructor * @param flags Flags + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref VectorGL(Flags, UnsignedInt) with @p drawCount set to @cpp 1 @ce. */ explicit VectorGL(Flags flags = {}); + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref TransformationProjectionUniform3D / + * @ref VectorDrawUniform / @ref TextureTransformationUniform + * buffer bound with @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers @p drawCount + * describes the uniform buffer sizes as these are required to have a + * statically defined size. The draw offset is then set via + * @ref setDrawOffset(). + * + * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is + * ignored and the constructor behaves the same as @ref VectorGL(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit VectorGL(Flags flags, UnsignedInt drawCount); + #endif + /** * @brief Construct without creating the underlying OpenGL object * @@ -172,8 +226,26 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL */ Flags flags() const { return _flags; } + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, @ref VectorDrawUniform and + * @ref TextureTransformationUniform uniform buffers. Has use only if + * @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -181,6 +253,11 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @return Reference to self (for method chaining) * * Initial value is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ VectorGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); @@ -192,6 +269,11 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * Expects that the shader was created with * @ref Flag::TextureTransformation enabled. Initial value is an * identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TextureTransformationUniform::rotationScaling and + * @ref TextureTransformationUniform::offset and call + * @ref bindTextureTransformationBuffer() instead. */ VectorGL& setTextureMatrix(const Matrix3& matrix); @@ -200,6 +282,10 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @return Reference to self (for method chaining) * * Initial value is @cpp 0x00000000_rgbaf @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref VectorDrawUniform::backgroundColor and call + * @ref bindDrawBuffer() instead. * @see @ref setColor() */ VectorGL& setBackgroundColor(const Color4& color); @@ -209,6 +295,10 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @return Reference to self (for method chaining) * * Initial value is @cpp 0xffffffff_rgbaf @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref VectorDrawUniform::color and call @ref bindDrawBuffer() + * instead. * @see @ref setBackgroundColor() */ VectorGL& setColor(const Color4& color); @@ -217,6 +307,98 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D, + * @ref VectorDrawUniform and @ref TextureTransformationUniform buffers + * bound with @ref bindTransformationProjectionBuffer(), + * @ref bindDrawBuffer() and @ref bindTextureTransformationBuffer() + * should be used for current draw. Expects that + * @ref Flag::UniformBuffers is set and @p offset is less than + * @ref drawCount(). Initial value is @cpp 0 @ce. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D. At the very least you need + * to call also @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VectorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a draw uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref VectorDrawUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& bindDrawBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VectorGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Set a texture transformation uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that both @ref Flag::UniformBuffers and + * @ref Flag::TextureTransformation is set. The buffer is expected to + * contain @ref drawCount() instances of + * @ref TextureTransformationUniform. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VectorGL& bindTextureTransformationBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VectorGL& bindTextureTransformationBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + /** @{ * @name Texture binding */ @@ -243,10 +425,18 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL #endif Flags _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _drawCount{}; + #endif Int _transformationProjectionMatrixUniform{0}, _textureMatrixUniform{1}, _backgroundColorUniform{2}, _colorUniform{3}; + #ifndef MAGNUM_TARGET_GLES2 + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; + #endif }; /** diff --git a/src/Magnum/Shaders/VertexColor.vert b/src/Magnum/Shaders/VertexColor.vert index fda6574e3..c7597a413 100644 --- a/src/Magnum/Shaders/VertexColor.vert +++ b/src/Magnum/Shaders/VertexColor.vert @@ -28,8 +28,13 @@ #define out varying #endif +#ifndef RUNTIME_CONST +#define const +#endif + /* Uniforms */ +#ifndef UNIFORM_BUFFERS #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) #endif @@ -49,6 +54,35 @@ uniform highp mat4 transformationProjectionMatrix #error #endif +/* Uniform buffers */ + +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 0) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + highp + #ifdef TWO_DIMENSIONS + mat3 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrices[DRAW_COUNT]; +}; +#endif + /* Inputs */ #ifdef EXPLICIT_ATTRIB_LOCATION @@ -72,6 +106,18 @@ in lowp vec4 color; out lowp vec4 interpolatedColor; void main() { + #ifdef UNIFORM_BUFFERS + highp const + #ifdef TWO_DIMENSIONS + mat3 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrix = transformationProjectionMatrices[drawOffset]; + #endif + #ifdef TWO_DIMENSIONS gl_Position.xywz = vec4(transformationProjectionMatrix*vec3(position, 1.0), 0.0); #elif defined(THREE_DIMENSIONS) diff --git a/src/Magnum/Shaders/VertexColorGL.cpp b/src/Magnum/Shaders/VertexColorGL.cpp index 66ef23fc1..4c1b4eb3f 100644 --- a/src/Magnum/Shaders/VertexColorGL.cpp +++ b/src/Magnum/Shaders/VertexColorGL.cpp @@ -25,6 +25,7 @@ #include "VertexColorGL.h" +#include #include #include @@ -35,11 +36,47 @@ #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/GL/Buffer.h" +#endif + #include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" namespace Magnum { namespace Shaders { -template VertexColorGL::VertexColorGL() { +namespace { + #ifndef MAGNUM_TARGET_GLES2 + enum: Int { + /* Not using the zero binding to avoid conflicts with + ProjectionBufferBinding from other shaders which can likely stay + bound to the same buffer for the whole time */ + TransformationProjectionBufferBinding = 1 + }; + #endif +} + +template VertexColorGL::VertexColorGL(const Flags flags + #ifndef MAGNUM_TARGET_GLES2 + , const UnsignedInt drawCount + #endif +): + _flags{flags} + #ifndef MAGNUM_TARGET_GLES2 + , _drawCount{drawCount} + #endif +{ + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(flags >= Flag::UniformBuffers) || drawCount, + "Shaders::VertexColorGL: draw count can't be zero", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + if(flags >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShadersGL")) @@ -58,8 +95,16 @@ template VertexColorGL::VertexColorGL() { GL::Shader vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); - vert.addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n") - .addSource(rs.get("generic.glsl")) + vert.addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n" : "#define THREE_DIMENSIONS\n"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + vert.addSource(Utility::formatString( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n", + drawCount)); + } + #endif + vert.addSource(rs.get("generic.glsl")) .addSource(rs.get("VertexColor.vert")); frag.addSource(rs.get("generic.glsl")) .addSource(rs.get("VertexColor.frag")); @@ -85,21 +130,106 @@ template VertexColorGL::VertexColorGL() { if(!context.isExtensionSupported(version)) #endif { - _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + _drawOffsetUniform = uniformLocation("drawOffset"); + } else + #endif + { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"); + } + } + + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers + #ifndef MAGNUM_TARGET_GLES + && !context.isExtensionSupported(version) + #endif + ) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"), TransformationProjectionBufferBinding); } + #endif /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ #ifdef MAGNUM_TARGET_GLES - setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + #ifndef MAGNUM_TARGET_GLES2 + if(flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else + #endif + { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + } #endif } +#ifndef MAGNUM_TARGET_GLES2 +template VertexColorGL::VertexColorGL(const Flags flags): VertexColorGL{flags, 1} {} +#endif + template VertexColorGL& VertexColorGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::VertexColorGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + #endif setUniform(_transformationProjectionMatrixUniform, matrix); return *this; } +#ifndef MAGNUM_TARGET_GLES2 +template VertexColorGL& VertexColorGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VertexColorGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::VertexColorGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + setUniform(_drawOffsetUniform, offset); + return *this; +} + +template VertexColorGL& VertexColorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VertexColorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template VertexColorGL& VertexColorGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::VertexColorGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} +#endif + template class MAGNUM_SHADERS_EXPORT VertexColorGL<2>; template class MAGNUM_SHADERS_EXPORT VertexColorGL<3>; +namespace Implementation { + +Debug& operator<<(Debug& debug, const VertexColorGLFlag value) { + debug << "Shaders::VertexColorGL::Flag" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case VertexColorGLFlag::v: return debug << "::" #v; + #ifndef MAGNUM_TARGET_GLES2 + _c(UniformBuffers) + #endif + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const VertexColorGLFlags value) { + return Containers::enumSetDebugOutput(debug, value, "Shaders::VertexColorGL::Flags{}", { + #ifndef MAGNUM_TARGET_GLES2 + VertexColorGLFlag::UniformBuffers + #endif + }); +} + +} + }} diff --git a/src/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index 513312fba..d84acec62 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -37,6 +37,15 @@ namespace Magnum { namespace Shaders { +namespace Implementation { + enum class VertexColorGLFlag: UnsignedByte { + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 0 + #endif + }; + typedef Containers::EnumSet VertexColorGLFlags; +} + /** @brief Vertex color OpenGL shader @m_since_latest @@ -107,7 +116,84 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ ColorOutput = GenericGL::ColorOutput }; - explicit VertexColorGL(); + #ifdef DOXYGEN_GENERATING_OUTPUT + /** + * @brief Flag + * @m_since{2020,06} + * + * @see @ref Flags, @ref flags() + */ + enum class Flag: UnsignedByte { + #ifndef MAGNUM_TARGET_GLES2 + /** + * Use uniform buffers. Expects that uniform data are supplied via + * @ref bindTransformationProjectionBuffer() instead of direct + * uniform setters. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES + * 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL + * 1.0. + * @m_since_latest + */ + UniformBuffers = 1 << 0 + #endif + }; + + /** + * @brief Flags + * @m_since{2020,06} + * + * @see @ref flags() + */ + typedef Containers::EnumSet Flags; + #else + /* Done this way to be prepared for possible future diversion of 2D + and 3D flags (e.g. introducing 3D-specific features) */ + typedef Implementation::VertexColorGLFlag Flag; + typedef Implementation::VertexColorGLFlags Flags; + #endif + + /** + * @brief Constructor + * @param flags Flags + * + * While this function is meant mainly for the classic uniform + * scenario (without @ref Flag::UniformBuffers set), it's equivalent to + * @ref VertexColorGL(Flags, UnsignedInt) with @p drawCount set to + * @cpp 1 @ce. + */ + explicit VertexColorGL(Flags flags = {}); + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Construct for a multi-draw scenario + * @param flags Flags + * @param drawCount Size of a @ref TransformationProjectionUniform2D + * / @ref TransformationProjectionUniform3D buffer bound with + * @ref bindTransformationProjectionBuffer() + * + * If @p flags contains @ref Flag::UniformBuffers, @p drawCount + * describes the uniform buffer sizes as these are required to have a + * statically defined size. The draw offset is then set via + * @ref setDrawOffset(). + * + * If @p flags don't contain @ref Flag::UniformBuffers, @p drawCount is + * ignored and the constructor behaves the same as + * @ref VertexColorGL(Flags). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + /** @todo this constructor will eventually need to have also joint + count, per-vertex weight count, view count for multiview and clip + plane count ... and putting them in arbitrary order next to each + other is too error-prone, so it needs some other solution + (accepting pairs of parameter type and value like in GL context + creation, e.g., which will probably need a new enum as reusing Flag + for this might be too confusing) */ + explicit VertexColorGL(Flags flags, UnsignedInt drawCount); + #endif /** * @brief Construct without creating the underlying OpenGL object @@ -135,8 +221,28 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ /** @brief Move assignment */ VertexColorGL& operator=(VertexColorGL&&) noexcept = default; + /** @brief Flags */ + Flags flags() const { return _flags; } + + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Draw count + * @m_since_latest + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D uniform buffers. Has use only + * if @ref Flag::UniformBuffers is set. + * @requires_gles30 Not defined on OpenGL ES 2.0 builds. + * @requires_webgl20 Not defined on WebGL 1.0 builds. + */ + UnsignedInt drawCount() const { return _drawCount; } + #endif + /** @{ * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. */ /** @@ -144,6 +250,11 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * @return Reference to self (for method chaining) * * Default is an identity matrix. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. */ VertexColorGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); @@ -151,6 +262,54 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ * @} */ + #ifndef MAGNUM_TARGET_GLES2 + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Set a draw offset + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D buffers bound with + * @ref bindTransformationProjectionBuffer() should be used for current + * draw. Expects that @ref Flag::UniformBuffers is set and @p offset is + * less than @ref drawCount(). Initial value is @cpp 0 @ce. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VertexColorGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Set a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D. + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + * @requires_gles30 Uniform buffers are not available in OpenGL ES 2.0. + * @requires_webgl20 Uniform buffers are not available in WebGL 1.0. + */ + VertexColorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + /** + * @overload + * @m_since_latest + */ + VertexColorGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @} + */ + #endif + private: /* Prevent accidentally calling irrelevant functions */ #ifndef MAGNUM_TARGET_GLES @@ -160,7 +319,16 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ using GL::AbstractShaderProgram::dispatchCompute; #endif + Flags _flags; + #ifndef MAGNUM_TARGET_GLES2 + UnsignedInt _drawCount{}; + #endif Int _transformationProjectionMatrixUniform{0}; + #ifndef MAGNUM_TARGET_GLES2 + /* Used instead of all other uniforms when Flag::UniformBuffers is set, + so it can alias them */ + Int _drawOffsetUniform{0}; + #endif }; /** @@ -175,6 +343,20 @@ typedef VertexColorGL<2> VertexColorGL2D; */ typedef VertexColorGL<3> VertexColorGL3D; +#ifdef DOXYGEN_GENERATING_OUTPUT +/** @debugoperatorclassenum{VertexColorGL,VertexColorGL::Flag} */ +template Debug& operator<<(Debug& debug, VertexColorGL::Flag value); + +/** @debugoperatorclassenum{VertexColorGL,VertexColorGL::Flags} */ +template Debug& operator<<(Debug& debug, VertexColorGL::Flags value); +#else +namespace Implementation { + MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, VertexColorGLFlag value); + MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, VertexColorGLFlags value); + CORRADE_ENUMSET_OPERATORS(VertexColorGLFlags) +} +#endif + }} #endif