diff --git a/doc/artwork/line-annotation.svg b/doc/artwork/line-annotation.svg new file mode 100644 index 000000000..aef5bec22 --- /dev/null +++ b/doc/artwork/line-annotation.svg @@ -0,0 +1,331 @@ + + + + + + + + + + + + + + + + + + + + + + + + + U·B + ··B + UJ· + ·J· + ·JB + UJB + ·J· + ·JB + UJ· + UJB + U·· + ··· + + + + + + + + + + + diff --git a/doc/artwork/line-caps.svg b/doc/artwork/line-caps.svg new file mode 100644 index 000000000..d415d7668 --- /dev/null +++ b/doc/artwork/line-caps.svg @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/artwork/line-joins.svg b/doc/artwork/line-joins.svg new file mode 100644 index 000000000..be32c6614 --- /dev/null +++ b/doc/artwork/line-joins.svg @@ -0,0 +1,395 @@ + + + + + + + + + + + + + + + + + + + + A + B + + + + + + + + C + + + + + diff --git a/doc/artwork/line-quad-data-neighbor.svg b/doc/artwork/line-quad-data-neighbor.svg new file mode 100644 index 000000000..9e9e95518 --- /dev/null +++ b/doc/artwork/line-quad-data-neighbor.svg @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + U·B + ··B + U·· + ··· + A + B + C + D + UJ· + ·J· + UJB + ·JB + U·B + ··B + ··· + U·· + + diff --git a/doc/artwork/line-quad-data-other.svg b/doc/artwork/line-quad-data-other.svg new file mode 100644 index 000000000..216a341b5 --- /dev/null +++ b/doc/artwork/line-quad-data-other.svg @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/artwork/line-quad-data-overlap.svg b/doc/artwork/line-quad-data-overlap.svg new file mode 100644 index 000000000..2c8a26639 --- /dev/null +++ b/doc/artwork/line-quad-data-overlap.svg @@ -0,0 +1,2287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ··· + ··· + A/C + B/D + UJ? + ·J? + U·B + ··B + U·· + U·B + ··B + U·· + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/artwork/line-quad-data.svg b/doc/artwork/line-quad-data.svg new file mode 100644 index 000000000..debabf694 --- /dev/null +++ b/doc/artwork/line-quad-data.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/artwork/line-quad-expansion.svg b/doc/artwork/line-quad-expansion.svg new file mode 100644 index 000000000..7a6336afa --- /dev/null +++ b/doc/artwork/line-quad-expansion.svg @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + A + C + B + + + + D + + + + + + + + + + + + + diff --git a/doc/snippets/MagnumShaders-gl.cpp b/doc/snippets/MagnumShaders-gl.cpp index 7193089ec..1e72e7353 100644 --- a/doc/snippets/MagnumShaders-gl.cpp +++ b/doc/snippets/MagnumShaders-gl.cpp @@ -42,6 +42,7 @@ #include "Magnum/GL/Shader.h" #include "Magnum/GL/Renderbuffer.h" #include "Magnum/GL/RenderbufferFormat.h" +#include "Magnum/GL/Renderer.h" #include "Magnum/GL/Texture.h" #include "Magnum/GL/TextureFormat.h" #include "Magnum/GL/Version.h" @@ -66,6 +67,8 @@ #include "Magnum/Shaders/DistanceFieldVector.h" #include "Magnum/Shaders/Flat.h" #include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Line.h" +#include "Magnum/Shaders/LineGL.h" #include "Magnum/Shaders/MeshVisualizer.h" #include "Magnum/Shaders/Phong.h" #include "Magnum/Shaders/Vector.h" @@ -694,6 +697,67 @@ vert.addSource(Utility::format( /* [GenericGL-custom-preprocessor] */ } +#ifndef MAGNUM_TARGET_GLES2 +{ +GL::Mesh mesh; +Matrix3 transformationMatrix, projectionMatrix; +/* [LineGL-usage2] */ +Shaders::LineGL2D shader; +shader + .setViewportSize(Vector2{GL::defaultFramebuffer.viewport().size()}) + .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) + .setColor(0x2f83cc_rgbf) + .setWidth(4.0f) + .draw(mesh); +/* [LineGL-usage2] */ +} + +{ +GL::Mesh mesh; +/* [LineGL-usage-antialiasing] */ +GL::Renderer::enable(GL::Renderer::Feature::Blending); +GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + +Shaders::LineGL2D shader; +shader + DOXYGEN_ELLIPSIS() + .setSmoothness(1.0f) + .draw(mesh); +/* [LineGL-usage-antialiasing] */ +} + +{ +GL::Mesh mesh; +Matrix3 transformationMatrix, projectionMatrix; +/* [LineGL-ubo] */ +GL::Buffer transformationProjectionUniform, materialUniform, drawUniform; +transformationProjectionUniform.setData({ + Shaders::TransformationProjectionUniform2D{} + .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) +}); +materialUniform.setData({ + Shaders::LineMaterialUniform{} + .setColor(0x2f83cc_rgbf) +}); +drawUniform.setData({ + Shaders::LineDrawUniform{} + .setMaterialId(0) +}); + +Shaders::LineGL2D shader{Shaders::LineGL2D::Configuration{} + .setFlags(Shaders::LineGL2D::Flag::UniformBuffers)}; +shader + .setViewportSize(Vector2{GL::defaultFramebuffer.viewport().size()}) + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindMaterialBuffer(materialUniform) + .bindDrawBuffer(drawUniform) + .draw(mesh); +/* [LineGL-ubo] */ +} +#endif + { GL::Mesh mesh; /* [MeshVisualizerGL2D-usage-instancing] */ diff --git a/doc/snippets/README.md b/doc/snippets/README.md index 64477e04d..c886790cd 100644 --- a/doc/snippets/README.md +++ b/doc/snippets/README.md @@ -14,13 +14,25 @@ smaller file sizes: The output printed by the application can be used to update the example output in `doc/getting-started.dox`. -### triangulate.svg, scenedata-tree.svg, scenedata-dod.svg +### triangulate.svg, scenedata-tree.svg, scenedata-dod.svg, line-*.svg -Created by Inkscape from `doc/artwork/triangulate.svg` by saving as Optimized -SVG and: +Created by Inkscape from `doc/artwork/triangulate.svg` and `line-*.svg` by +saving as Optimized SVG. On fresh installations you need the `scour` package +for it: -- cleaning up the `` header (removing `version`, `xmlns`) +- enabling all possible options in the dialog, saving +- cleaning up the `` header (removing `version`, `xmlns`) in an editor - converting to a `style=""`, *keeping* `viewBox` - adding `class="m-image"` -- removing metadata, the background layer and all layers that have - `display: none` +- removing all layers that have `display: none` + +In case of the `line-quad-data-expansion*.svg` and +`line-quad-data-overlap-*.svg`, they're all generated from +`doc/artwork/line-quad-expansion.svg` and `doc/artwork/line-quad-overlap.svg`, +each time with different layers shown. + +The `doc/artwork/line-quad-data-other.svg` is derived from +`doc/artwork/line-quad-data-neighbor.svg` by removing the "neighbor" layer and +making the canvas smaller, the `doc/artwork/line-quad-data.svg` is then derived +from `doc/artwork/line-quad-data-other.svg` by removing the "other" layer and +making the canvas smaller yet again. diff --git a/doc/snippets/line-annotation.svg b/doc/snippets/line-annotation.svg new file mode 100644 index 000000000..014bbb60f --- /dev/null +++ b/doc/snippets/line-annotation.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + U·B + ··B + + + + + UJ· + ·J· + ·JB + UJB + + ·J· + ·JB + UJ· + + UJB + U·· + ··· + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-cap-butt.svg b/doc/snippets/line-cap-butt.svg new file mode 100644 index 000000000..d428936b3 --- /dev/null +++ b/doc/snippets/line-cap-butt.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-cap-round.svg b/doc/snippets/line-cap-round.svg new file mode 100644 index 000000000..ce6227f16 --- /dev/null +++ b/doc/snippets/line-cap-round.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-cap-square.svg b/doc/snippets/line-cap-square.svg new file mode 100644 index 000000000..e49e6e06b --- /dev/null +++ b/doc/snippets/line-cap-square.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-cap-triangle.svg b/doc/snippets/line-cap-triangle.svg new file mode 100644 index 000000000..e8961a3ff --- /dev/null +++ b/doc/snippets/line-cap-triangle.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-join-bevel.svg b/doc/snippets/line-join-bevel.svg new file mode 100644 index 000000000..0b3f62c5b --- /dev/null +++ b/doc/snippets/line-join-bevel.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + A + B + + + + + + + + + + C + + + + + + + diff --git a/doc/snippets/line-join-miter.svg b/doc/snippets/line-join-miter.svg new file mode 100644 index 000000000..73903f44d --- /dev/null +++ b/doc/snippets/line-join-miter.svg @@ -0,0 +1,32 @@ + + + + + + + + B + A + + + + + + l + α + + + + + + + + + C + + + + + + + diff --git a/doc/snippets/line-quad-data-neighbor.svg b/doc/snippets/line-quad-data-neighbor.svg new file mode 100644 index 000000000..4f81aa0d9 --- /dev/null +++ b/doc/snippets/line-quad-data-neighbor.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + U·B + ··B + U·· + ··· + A + B + C + D + UJ· + ·J· + UJB + ·JB + U·B + ··B + ··· + U·· + + + diff --git a/doc/snippets/line-quad-data-other.svg b/doc/snippets/line-quad-data-other.svg new file mode 100644 index 000000000..e564d24a0 --- /dev/null +++ b/doc/snippets/line-quad-data-other.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-quad-data-overlap-fixedjoin.svg b/doc/snippets/line-quad-data-overlap-fixedjoin.svg new file mode 100644 index 000000000..545ee5193 --- /dev/null +++ b/doc/snippets/line-quad-data-overlap-fixedjoin.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UJ· + ·J? + UJB + U·B + ··B + ··· + U·· + U·B + ··B + U·· + ··· + A + B/D + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-quad-data-overlap-generic.svg b/doc/snippets/line-quad-data-overlap-generic.svg new file mode 100644 index 000000000..46c366cc8 --- /dev/null +++ b/doc/snippets/line-quad-data-overlap-generic.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UJ· + ·J· + UJB + ·JB + U·B + ··B + ··· + U·· + U·B + ··B + U·· + ··· + A + B + C + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-quad-data-overlap-miterjoin.svg b/doc/snippets/line-quad-data-overlap-miterjoin.svg new file mode 100644 index 000000000..5db2799f8 --- /dev/null +++ b/doc/snippets/line-quad-data-overlap-miterjoin.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ··· + ··· + A/C + B/D + UJ? + ·J? + U·B + ··B + U·· + U·B + ··B + U·· + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-quad-data-overlap-nojoin.svg b/doc/snippets/line-quad-data-overlap-nojoin.svg new file mode 100644 index 000000000..86726836e --- /dev/null +++ b/doc/snippets/line-quad-data-overlap-nojoin.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + U·· + ··· + U·B + ··B + U·B + ··B + ··· + U·· + U·B + ··B + U·· + ··· + A + B + C + D + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-quad-data.svg b/doc/snippets/line-quad-data.svg new file mode 100644 index 000000000..6601905c6 --- /dev/null +++ b/doc/snippets/line-quad-data.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/doc/snippets/line-quad-expansion-joins-caps.svg b/doc/snippets/line-quad-expansion-joins-caps.svg new file mode 100644 index 000000000..9336079fd --- /dev/null +++ b/doc/snippets/line-quad-expansion-joins-caps.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + A + C + B + + + + + + + + + + D + + + + + + + + + diff --git a/doc/snippets/line-quad-expansion-joins-miter-caps.svg b/doc/snippets/line-quad-expansion-joins-miter-caps.svg new file mode 100644 index 000000000..9cc72d635 --- /dev/null +++ b/doc/snippets/line-quad-expansion-joins-miter-caps.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + A + C + B + + + + + + + D + + + + + + + + + + + + + + diff --git a/doc/snippets/line-quad-expansion.svg b/doc/snippets/line-quad-expansion.svg new file mode 100644 index 000000000..71266040e --- /dev/null +++ b/doc/snippets/line-quad-expansion.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Magnum/Shaders/CMakeLists.txt b/src/Magnum/Shaders/CMakeLists.txt index 4503a70ef..66c1b7163 100644 --- a/src/Magnum/Shaders/CMakeLists.txt +++ b/src/Magnum/Shaders/CMakeLists.txt @@ -73,6 +73,19 @@ set(MagnumShaders_HEADERS set(MagnumShaders_PRIVATE_HEADERS Implementation/CreateCompatibilityShader.h) +if(NOT MAGNUM_TARGET_GLES2) + list(APPEND MagnumShaders_GracefulAssert_SRCS + Line.cpp + LineGL.cpp) + + list(APPEND MagnumShaders_HEADERS + Line.h + LineGL.h) + + list(APPEND MagnumShaders_PRIVATE_HEADERS + Implementation/lineMiterLimit.h) +endif() + if(MAGNUM_BUILD_DEPRECATED) list(APPEND MagnumShaders_HEADERS VertexColor.h) diff --git a/src/Magnum/Shaders/Implementation/lineMiterLimit.h b/src/Magnum/Shaders/Implementation/lineMiterLimit.h new file mode 100644 index 000000000..87ee4dfb0 --- /dev/null +++ b/src/Magnum/Shaders/Implementation/lineMiterLimit.h @@ -0,0 +1,55 @@ +#ifndef Magnum_Shaders_Implementation_lineMiterLimit_h +#define Magnum_Shaders_Implementation_lineMiterLimit_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 "Magnum/Math/Functions.h" + +namespace Magnum { namespace Shaders { namespace Implementation { + +inline Float lineMiterLengthLimit(const char* const name, const Float limit) { + #ifdef CORRADE_NO_ASSERT + static_cast(name); + #endif + CORRADE_ASSERT(limit >= 1.0f && !Math::isInf(limit), + name << "expected a finite value greater than or equal to 1, got" << limit, {}); + /* Calculate the half-angle from the length and return a cosine of it */ + return Math::cos(2.0f*Math::asin(1.0f/limit)); +} + +inline Float lineMiterAngleLimit(const char* const name, const Rad limit) { + using namespace Math::Literals; + #ifdef CORRADE_NO_ASSERT + static_cast(name); + #endif + CORRADE_ASSERT(limit > 0.0_radf && limit <= Rad{Constants::pi()}, + name << "expected a value greater than 0° and less than or equal to 180°, got" << Float(Deg(limit)) << Debug::nospace << "°", {}); + /* Return a cosine of the angle */ + return Math::cos(limit); +} + +}}} + +#endif diff --git a/src/Magnum/Shaders/Line.cpp b/src/Magnum/Shaders/Line.cpp new file mode 100644 index 000000000..da9213653 --- /dev/null +++ b/src/Magnum/Shaders/Line.cpp @@ -0,0 +1,76 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 "Line.h" + +#include + +#include "Magnum/Shaders/Implementation/lineMiterLimit.h" + +namespace Magnum { namespace Shaders { + +LineMaterialUniform& LineMaterialUniform::setMiterLengthLimit(const Float limit) { + miterLimit = Implementation::lineMiterLengthLimit("Shaders::LineMaterialUniform::setMiterLengthLimit():", limit); + return *this; +} + +LineMaterialUniform& LineMaterialUniform::setMiterAngleLimit(const Rad limit) { + miterLimit = Implementation::lineMiterAngleLimit("Shaders::LineMaterialUniform::setMiterAngleLimit():", limit); + return *this; +} + +Debug& operator<<(Debug& debug, const LineCapStyle value) { + debug << "Shaders::LineCapStyle" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case LineCapStyle::v: return debug << "::" #v; + _c(Butt) + _c(Square) + _c(Round) + _c(Triangle) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const LineJoinStyle value) { + debug << "Shaders::LineJoinStyle" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case LineJoinStyle::v: return debug << "::" #v; + _c(Miter) + _c(Bevel) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +}} diff --git a/src/Magnum/Shaders/Line.frag b/src/Magnum/Shaders/Line.frag new file mode 100644 index 000000000..c3c833030 --- /dev/null +++ b/src/Magnum/Shaders/Line.frag @@ -0,0 +1,257 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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. +*/ + +#if defined(OBJECT_ID) && !defined(GL_ES) && !defined(NEW_GLSL) +#extension GL_EXT_gpu_shader4: require +#endif + +/* See the corresponding block in Line.vert for more information */ +#ifndef GL_ES +#define CAN_USE_NOPERSPECTIVE +#elif defined(GL_ES) && defined(GL_NV_shader_noperspective_interpolation) +#extension GL_NV_shader_noperspective_interpolation: require +#define CAN_USE_NOPERSPECTIVE +#endif + +#ifndef NEW_GLSL +#define fragmentColor gl_FragColor +#define in varying +#endif + +#ifndef RUNTIME_CONST +#define const +#endif + +/* Uniforms */ + +#ifndef UNIFORM_BUFFERS +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 2) +#endif +uniform lowp vec4 backgroundColor + #ifndef GL_ES + = vec4(0.0) + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 3) +#endif +uniform lowp vec4 color + #ifndef GL_ES + = vec4(1.0) + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 4) +#endif +uniform mediump float width + #ifndef GL_ES + = 1.0 + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 5) +#endif +uniform mediump float smoothness + #ifndef GL_ES + = 0.0 + #endif + ; + +#ifdef OBJECT_ID +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 7) +#endif +/* mediump is just 2^10, which might not be enough, this is 2^16 */ +uniform highp uint objectId; /* defaults to zero */ +#endif + +/* Uniform buffers */ + +#else +#ifndef MULTI_DRAW +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 1) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif +#define drawId drawOffset +#endif + +struct DrawUniform { + highp uvec4 materialIdReservedObjectIdReservedReserved; + #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x + #define draw_objectId materialIdReservedObjectIdReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +struct MaterialUniform { + lowp vec4 backgroundColor; + lowp vec4 color; + highp vec4 widthSmoothnessMiterLimitReserved; + #define material_width widthSmoothnessMiterLimitReserved.x + #define material_smoothness widthSmoothnessMiterLimitReserved.y + #define material_miterLimit widthSmoothnessMiterLimitReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 3 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + +/* Inputs */ + +#ifdef CAN_USE_NOPERSPECTIVE +noperspective +#endif +in highp vec2 centerDistanceSigned; +in highp float halfSegmentLength; +#ifdef CAN_USE_NOPERSPECTIVE +noperspective +#endif +in highp float hasCap; + +#ifdef VERTEX_COLOR +in lowp vec4 interpolatedVertexColor; +#endif + +#ifdef INSTANCED_OBJECT_ID +flat in highp uint interpolatedInstanceObjectId; +#endif + +#ifdef MULTI_DRAW +flat in highp uint drawId; +#endif + +/* Outputs */ + +#ifdef NEW_GLSL +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = COLOR_OUTPUT_ATTRIBUTE_LOCATION) +#endif +out lowp vec4 fragmentColor; +#endif +#ifdef OBJECT_ID +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = OBJECT_ID_OUTPUT_ATTRIBUTE_LOCATION) +#endif +/* mediump is just 2^10, which might not be enough, this is 2^16 */ +out highp uint fragmentObjectId; +#endif + +void main() { + #ifdef UNIFORM_BUFFERS + #ifdef OBJECT_ID + highp const uint objectId = draws[drawId].draw_objectId; + #endif + #if MATERIAL_COUNT > 1 + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + #else + #define materialId 0u + #endif + lowp const vec4 backgroundColor = materials[materialId].backgroundColor; + lowp const vec4 color = materials[materialId].color; + mediump const float width = materials[materialId].material_width; + mediump const float smoothness = materials[materialId].material_smoothness; + #endif + + /* Calculate a distance from the original line endpoint (B). Assuming a cap + that's not a butt, actual quad vertices (2, 3 on the left diagram) would + be at a distance `width/2` in both X and Y (in the space of the line + segment, where X is in direction of the segment and Y is in direction to + the line edges): + + ----------2 --------2 + | | + [0,0] B | [0,0] B + | | + ----------3 --------3 + + For a butt cap, the endpoint B would be at the edge instead (right + diagram) -- to have handling consistent for all cap styles, add + `width/2` to the center distance in that case. For fragments on the left + of B the X distance would be negative, make it 0 in that case + instead. */ + highp const vec2 centerDistance = abs(centerDistanceSigned); + highp vec2 endpointDistance = vec2(max(centerDistance.x + #ifdef CAP_STYLE_BUTT + + width*0.5 + #endif + - halfSegmentLength, 0.0), centerDistance.y); + + /* If hasCap is negative, it means the nearest endpoint is a join, not a + cap. Thus no smoothing happens in the direction of a cap, i.e. same as + if we'd be at the center of the line. */ + if(hasCap < 0.0) endpointDistance.x = 0.0; + + /* Calculate a single distance factor out of the two-dimensional endpoint + distance. This will form the cap shape. */ + #if defined(CAP_STYLE_BUTT) || defined(CAP_STYLE_SQUARE) + highp const float distance1D = max(endpointDistance.x, endpointDistance.y); + #elif defined(CAP_STYLE_ROUND) + highp const float distance1D = length(endpointDistance); + #elif defined(CAP_STYLE_TRIANGLE) + highp const float distance1D = endpointDistance.x + endpointDistance.y; + #else + #error + #endif + + mediump const float factor = smoothstep(width*0.5 - smoothness, width*0.5 + smoothness, distance1D); + + fragmentColor = mix( + #ifdef VERTEX_COLOR + interpolatedVertexColor* + #endif + color, backgroundColor, factor); + + #ifdef OBJECT_ID + fragmentObjectId = + #ifdef INSTANCED_OBJECT_ID + interpolatedInstanceObjectId + + #endif + objectId; + #endif +} diff --git a/src/Magnum/Shaders/Line.h b/src/Magnum/Shaders/Line.h new file mode 100644 index 000000000..777ca5fa8 --- /dev/null +++ b/src/Magnum/Shaders/Line.h @@ -0,0 +1,472 @@ +#ifndef Magnum_Shaders_Line_h +#define Magnum_Shaders_Line_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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. +*/ + +/** @file + * @brief Struct @ref Magnum::Shaders::LineDrawUniform, @ref Magnum::Shaders::LineMaterialUniform, enum @ref Magnum::Shaders::LineCapStyle, @ref Magnum::Shaders::LineJoinStyle, @ref Magnum::Shaders::LineVertexAnnotation, enum set @ref Magnum::Shaders::LineVertexAnnotations + * @m_since_latest + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Shaders/visibility.h" + +namespace Magnum { namespace Shaders { + +/** +@brief Line cap style +@m_since_latest + +@see @ref LineGL::capStyle(), @ref LineGL::Configuration::setCapStyle() +*/ +enum class LineCapStyle: UnsignedByte { + /** + * [Butt cap](https://en.wikipedia.org/wiki/Butt_joint). The line is cut + * off right at the endpoint. Lines of zero length will be invisible. + * + * @htmlinclude line-cap-butt.svg + */ + Butt, + + /** + * Square cap. The line is extended by half of its width past the endpoint. + * Lines of zero length will be shown as squares. + * + * @htmlinclude line-cap-square.svg + */ + Square, + + /** + * Round cap. The line is extended by half of its width past the endpoint. + * It's still rendered as a quad but pixels outside of the half-circle have + * the background color. Lines of zero length will be shown as circles. + * + * @htmlinclude line-cap-round.svg + * + * @see @ref LineMaterialUniform::backgroundColor, + * @ref LineGL::setBackgroundColor() + */ + Round, + + /** + * Triangle cap. The line is extended by half of its width past the + * endpoint. It's still rendered as a quad but pixels outside of the + * triangle have the background color. Lines of zero length will be shown + * as squares rotated by 45°. + * + * @htmlinclude line-cap-triangle.svg + * + * @see @ref LineMaterialUniform::backgroundColor, + * @ref LineGL::setBackgroundColor() + */ + Triangle +}; + +/** +@brief Line join style +@m_since_latest + +@see @ref LineGL::joinStyle(), @ref LineGL::Configuration::setJoinStyle() +*/ +enum class LineJoinStyle: UnsignedByte { + /** + * [Miter join](https://en.wikipedia.org/wiki/Miter_joint). The outer edges + * of both line segments extend until they intersect. + * + * @htmlinclude line-join-miter.svg + * + * In this style, the points `A`, `B` and `C` collapse to a zero-area + * triangle. If the miter length `l` would be larger than the limit set in + * @ref LineGL::setMiterLengthLimit() / + * @ref LineMaterialUniform::setMiterLengthLimit() or the angle between the + * two segments `α` would be less than the limit set in + * @ref LineGL::setMiterAngleLimit() / + * @ref LineMaterialUniform::setMiterAngleLimit(), it switches to + * @ref LineJoinStyle::Bevel instead. + */ + Miter, + + /** + * [Bevel join](https://en.wikipedia.org/wiki/Bevel). Outer edges of both + * line segments are cut off at a right angle at their endpoints. + * + * @htmlinclude line-join-bevel.svg + * + * The area between points `A`, `B` and `C` is filled with an extra + * triangle. + */ + Bevel +}; + +/** +@brief Line vertex annotation +@m_since_latest + +A line segment drawn by the @ref LineGL shader consists of four vertices, first +two having the @ref LineGL::Position attribute set to the first point of the +segment and second two having it set to the second point of the segment. In +order to distinguish the direction in which the point should be expanded to +form a quad and whether the expansion should be for a line join or line cap, +each vertex contains @ref LineVertexAnnotations in the @ref LineGL::Annotation +attribute. + +@htmlinclude line-annotation.svg + +In the above diagram, there's a line strip consisting of three line segments +and six pairs of points, with @m_span{m-label m-success} green @m_endspan and +@m_span{m-label m-default} white @m_endspan forming (square) line caps, while +@m_span{m-label m-primary} azure @m_endspan, +@m_span{m-label m-danger} red @m_endspan form a miter line join, and +@m_span{m-label m-info} blue @m_endspan and +@m_span{m-label m-warning} yellow @m_endspan form a bevel join. The +twelve corresponding annotations, forming three quads (and one extra triangle +for the bevel), are shown with `U`, `J` and `B` letters, color-coded to show +which original line point they correspond to. Line cap style and join style +isn't a part of the annotation, it's set with @ref LineCapStyle and +@ref LineJoinStyle at shader compilation time instead. + +The type is 32-bit in order to match the default type of the +@ref LineGL::Annotation attribute, but the values are guaranteed to fit into 8 +bits. +*/ +enum class LineVertexAnnotation: UnsignedInt { + /** + * The point extends upwards assuming a left-to-right direction of the line + * segment. If not set, it extends downwards. Visualized as `U` in the + * above diagram. + */ + Up = 1 << 0, + + /** + * The point is forming a join with a neighboring line segment defined by + * either @ref LineGL::PreviousPosition or @ref LineGL::NextPosition based + * on whether @ref LineVertexAnnotation::Begin is set. If not set, the + * point is forming a line cap, extending in the opposite of the line + * segment direction if @ref LineVertexAnnotation::Begin is set, and in the + * direction if not set. Visualized as `J` in the above diagram. + */ + Join = 1 << 1, + + /** + * The point is forming the beginning of the line segment, i.e. + * @ref LineGL::NextPosition contains the other point of the line segment. + * If not set, @ref LineGL::PreviousPosition contains the other point of + * the line segment instead. + * + * If @ref LineVertexAnnotation::Join is set as well, the point is a common + * point of two neighboring line segments and @ref LineGL::PreviousPosition + * contains the other point of the neighboring line segment. If + * @ref LineVertexAnnotation::Join is set and this bit is not set, + * @ref LineGL::NextPosition contaons the other point of the neighboring + * line segment instead. Visualized as `B` in the above diagram. + */ + Begin = 1 << 2, +}; + +/** +@brief Line vertex annotations +@m_since_latest + +Contents of the @ref LineGL::Annotation attribute. See +@ref LineVertexAnnotation for more information. +*/ +typedef Containers::EnumSet LineVertexAnnotations; + +CORRADE_ENUMSET_OPERATORS(LineVertexAnnotations) + +/** + * @debugoperatorenum{LineCapStyle} + * @m_since_latest + */ +MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, LineCapStyle value); + +/** + * @debugoperatorenum{LineJoinStyle} + * @m_since_latest + */ +MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, LineJoinStyle value); + +/** +@brief Per-draw uniform for line shaders +@m_since_latest + +Together with the generic @ref TransformationProjectionUniform2D / +@ref TransformationProjectionUniform3D 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 LineMaterialUniform structure, referenced by @ref materialId. +@see @ref LineGL::bindDrawBuffer() +*/ +struct LineDrawUniform { + /** @brief Construct with default parameters */ + constexpr explicit LineDrawUniform(DefaultInitT = DefaultInit) noexcept: materialId{0}, objectId{0} {} + + /** @brief Construct without initializing the contents */ + explicit LineDrawUniform(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) + */ + LineDrawUniform& setMaterialId(UnsignedInt id) { + materialId = id; + return *this; + } + + /** + * @brief Set the @ref objectId field + * @return Reference to self (for method chaining) + */ + LineDrawUniform& setObjectId(UnsignedInt id) { + objectId = id; + return *this; + } + + /** + * @} + */ + + /** @var materialId + * @brief Material ID + * + * References a particular material from a @ref LineMaterialUniform 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 LineGL::Configuration::setMaterialCount(), if material count is + * @cpp 1 @ce, this field is assumed to be @cpp 0 @ce and isn't even read + * by the shader. 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 + alignas(4) 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 */ + #endif + #else + alignas(4) UnsignedShort:16; /* reserved */ + UnsignedShort materialId; + #endif + + /** + * @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 LineGL::Flag::ObjectId is enabled, ignored otherwise. + * If @ref LineGL::Flag::InstancedObjectId is enabled as well, this value + * is added to the ID coming from the @ref LineGL::ObjectId attribute. + * @see @ref LineGL::setObjectId() + */ + UnsignedInt objectId; + + /* warning: Member __pad1__ is not documented. FFS DOXYGEN WHY DO YOU THINK + I MADE THOSE UNNAMED, YOU DUMB FOOL */ + #ifndef DOXYGEN_GENERATING_OUTPUT + Int:32; + Int:32; + #endif +}; + +/** +@brief Material uniform for line shaders +@m_since_latest + +Describes material properties referenced from +@ref LineDrawUniform::materialId. +@see @ref LineGL::bindMaterialBuffer() +*/ +struct MAGNUM_SHADERS_EXPORT LineMaterialUniform { + /** @brief Construct with default parameters */ + constexpr explicit LineMaterialUniform(DefaultInitT = DefaultInit) noexcept: backgroundColor{0.0f, 0.0f, 0.0f, 0.0f}, color{1.0f, 1.0f, 1.0f, 1.0f}, width{1.0f}, smoothness{0.0f}, miterLimit{0.875f} {} + + /** @brief Construct without initializing the contents */ + explicit LineMaterialUniform(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 color field + * @return Reference to self (for method chaining) + */ + LineMaterialUniform& setColor(const Color4& color) { + this->color = color; + return *this; + } + + /** + * @brief Set the @ref backgroundColor field + * @return Reference to self (for method chaining) + */ + LineMaterialUniform& setBackgroundColor(const Color4& color) { + backgroundColor = color; + return *this; + } + + /** + * @brief Set the @ref width field + * @return Reference to self (for method chaining) + */ + LineMaterialUniform& setWidth(Float width) { + this->width = width; + return *this; + } + + /** + * @brief Set the @ref smoothness field + * @return Reference to self (for method chaining) + */ + LineMaterialUniform& setSmoothness(Float smoothness) { + this->smoothness = smoothness; + return *this; + } + + /** + * @brief Set the @ref miterLimit field to a length value + * @return Reference to self (for method chaining) + * + * Expects that @p limit is greater than or equal to @cpp 1.0f @ce and + * finite. + */ + LineMaterialUniform& setMiterLengthLimit(Float limit); + + /** + * @brief Set the @ref miterLimit field to an angle value + * @return Reference to self (for method chaining) + * + * Expects that @p limit is greater than @cpp 0.0_radf @ce. + */ + LineMaterialUniform& setMiterAngleLimit(Rad limit); + + /** + * @} + */ + + /** + * @brief Background color + * + * Default value is @cpp 0x00000000_rgbaf @ce. Used for edge smoothing if + * smoothness is non-zero, and for background areas if + * @ref LineCapStyle::Round or @ref LineCapStyle::Triangle is used. If + * smoothness is zero and @ref LineCapStyle::Butt or + * @ref LineCapStyle::Square is used, only the foreground color is used. + * @see @ref LineGL::setBackgroundColor(), @ref LineGL::setSmoothness(), + * @ref LineGL::Configuration::setCapStyle() + */ + Color4 backgroundColor; + + /** + * @brief Color + * + * Default value is @cpp 0xffffffff_rgbaf @ce. + * + * If @ref LineGL::Flag::VertexColor is enabled, the color is multiplied + * with a color coming from the @ref LineGL::Color3 / @ref LineGL::Color4 + * attribute. + * @see @ref LineGL::setColor() + */ + Color4 color; + + /** + * @brief Line width + * + * Screen-space, interpreted depending on the viewport size --- i.e., a + * value of @cpp 1.0f @ce is one pixel only if @ref LineGL::setViewportSize() + * is called with the actual pixel size of the viewport. Default value is + * @cpp 1.0f @ce. + * @see @ref LineGL::setWidth() + */ + Float width; + + /** + * @brief Line smoothness + * + * Larger values will make edges look less aliased (but blurry), smaller + * values will make them more crisp (but possibly aliased). Screen-space, + * interpreted depending on the viewport size --- i.e., a value of + * @cpp 1.0f @ce is one pixel only if @ref LineGL::setViewportSize() is + * called with the actual pixel size of the viewport. Initial value is + * @cpp 0.0f @ce. + * @see @ref LineGL::setSmoothness() + */ + Float smoothness; + + /** + * @brief Miter limit + * + * Limit at which a @ref LineJoinStyle::Miter join is converted to a + * @ref LineJoinStyle::Bevel in order to avoid sharp corners extending too + * much. If joint style is not @ref LineJoinStyle::Miter, this value is + * unused. + * + * Represented as a cosine of the angle between two neighboring line + * segments, with @ref LineJoinStyle::Bevel used for angles below the limit + * (thus their cosine larger than this value). For length-based limits, + * the relation between angle @f$ \theta @f$, miter length @f$ l @f$ and + * line half-width @f$ w @f$ is as follows: @f[ + * \frac{w}{l} = \sin(\frac{\theta}{2}) + * @f] + * + * For convenience it's recommended to use the @ref setMiterLengthLimit() + * and @ref setMiterAngleLimit() helpers instead of setting this value + * directly. + * @see @ref LineGL::setMiterLengthLimit(), + * @ref LineGL::setMiterAngleLimit() + */ + Float miterLimit; + + /* 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 dynamic cap/join style */ + #endif +}; + +}} + +#endif diff --git a/src/Magnum/Shaders/Line.vert b/src/Magnum/Shaders/Line.vert new file mode 100644 index 000000000..6a5776cee --- /dev/null +++ b/src/Magnum/Shaders/Line.vert @@ -0,0 +1,666 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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. +*/ + +#if !defined(GL_ES) && !defined(NEW_GLSL) +#extension GL_EXT_gpu_shader4: require +#endif + +#if defined(UNIFORM_BUFFERS) && defined(TEXTURE_ARRAYS) && !defined(GL_ES) +#extension GL_ARB_shader_bit_encoding: require +#endif + +/* Use the noperspective keyword to avoid artifacts in screen-space + interpolation if perspective projection is used in 3D. If not available, + it's worked around by dividing gl_Position with gl_Position.w (which is + extra instructions, so the noperspective keyword is preferred). */ +#ifndef GL_ES +#define CAN_USE_NOPERSPECTIVE +#elif defined(GL_ES) && defined(GL_NV_shader_noperspective_interpolation) +#extension GL_NV_shader_noperspective_interpolation: require +#define CAN_USE_NOPERSPECTIVE +#endif + +#ifdef MULTI_DRAW +#ifndef GL_ES +#extension GL_ARB_shader_draw_parameters: require +#else /* covers WebGL as well */ +#extension GL_ANGLE_multi_draw: require +#endif +#endif + +#ifndef NEW_GLSL +#define in attribute +#define out varying +#endif + +#ifndef RUNTIME_CONST +#define const +#endif + +/* 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 = 0) +#endif +uniform lowp vec2 viewportSize; /* defaults to zero */ + +#ifndef UNIFORM_BUFFERS +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 1) +#endif +#ifdef TWO_DIMENSIONS +uniform highp mat3 transformationProjectionMatrix + #ifndef GL_ES + = mat3(1.0) + #endif + ; +#elif defined(THREE_DIMENSIONS) +uniform highp mat4 transformationProjectionMatrix + #ifndef GL_ES + = mat4(1.0) + #endif + ; +#else +#error +#endif + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 4) +#endif +uniform mediump float width + #ifndef GL_ES + = 1.0 + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 5) +#endif +uniform mediump float smoothness + #ifndef GL_ES + = 0.0 + #endif + ; + +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 6) +#endif +uniform mediump float miterLimit + #ifndef GL_ES + /* cos(2*asin(1.0/4.0)), with 4 being the documented length limit */ + = 0.875 + #endif + ; + +/* Uniform buffers */ + +#else +#if DRAW_COUNT > 1 +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 1) +#endif +uniform highp uint drawOffset + #ifndef GL_ES + = 0u + #endif + ; +#else +#define drawOffset 0u +#endif + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 1 + #endif +) uniform TransformationProjection { + highp + #ifdef TWO_DIMENSIONS + /* Can't be a mat3 because of ANGLE, see DrawUniform in Phong.vert for + details */ + mat3x4 + #elif defined(THREE_DIMENSIONS) + mat4 + #else + #error + #endif + transformationProjectionMatrices[DRAW_COUNT]; +}; + +struct DrawUniform { + highp uvec4 materialIdReservedObjectIdReservedReserved; + #define draw_materialIdReserved materialIdReservedObjectIdReservedReserved.x + #define draw_objectId materialIdReservedObjectIdReservedReserved.y +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 2 + #endif +) uniform Draw { + DrawUniform draws[DRAW_COUNT]; +}; + +struct MaterialUniform { + lowp vec4 backgroundColor; + lowp vec4 color; + highp vec4 widthSmoothnessMiterLimitReserved; + #define material_width widthSmoothnessMiterLimitReserved.x + #define material_smoothness widthSmoothnessMiterLimitReserved.y + #define material_miterLimit widthSmoothnessMiterLimitReserved.z +}; + +layout(std140 + #ifdef EXPLICIT_BINDING + , binding = 3 + #endif +) uniform Material { + MaterialUniform materials[MATERIAL_COUNT]; +}; +#endif + +/* Inputs */ + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = POSITION_ATTRIBUTE_LOCATION) +#endif +#ifdef TWO_DIMENSIONS +in highp vec2 position; +#elif defined(THREE_DIMENSIONS) +/* Last component is reserved for line distance */ +in highp vec3 position; +#else +#error +#endif + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = LINE_PREVIOUS_POSITION_ATTRIBUTE_LOCATION) +#endif +#ifdef TWO_DIMENSIONS +in highp vec2 previousPosition; +#elif defined(THREE_DIMENSIONS) +in highp vec3 previousPosition; +#else +#error +#endif + +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = LINE_NEXT_POSITION_ATTRIBUTE_LOCATION) +#endif +#ifdef TWO_DIMENSIONS +in highp vec2 nextPosition; +#elif defined(THREE_DIMENSIONS) +in highp vec3 nextPosition; +#else +#error +#endif + +/* Point annotation, matching the LineVertexAnnotation enum bits */ +#define ANNOTATION_UP_MASK 1u +#define ANNOTATION_JOIN_MASK 2u +#define ANNOTATION_BEGIN_MASK 4u +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = LINE_ANNOTATION_ATTRIBUTE_LOCATION) +#endif +in lowp uint annotation; + +#ifdef VERTEX_COLOR +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = COLOR_ATTRIBUTE_LOCATION) +#endif +in lowp vec4 vertexColor; +#endif + +#ifdef INSTANCED_OBJECT_ID +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = OBJECT_ID_ATTRIBUTE_LOCATION) +#endif +in highp uint instanceObjectId; +#endif + +#ifdef INSTANCED_TRANSFORMATION +#ifdef EXPLICIT_ATTRIB_LOCATION +layout(location = TRANSFORMATION_MATRIX_ATTRIBUTE_LOCATION) +#endif +#ifdef TWO_DIMENSIONS +in highp mat3 instancedTransformationMatrix; +#elif defined(THREE_DIMENSIONS) +in highp mat4 instancedTransformationMatrix; +#else +#error +#endif +#endif + +/* Outputs */ + +#ifdef CAN_USE_NOPERSPECTIVE +noperspective +#endif +out highp vec2 centerDistanceSigned; +out highp float halfSegmentLength; +#ifdef CAN_USE_NOPERSPECTIVE +noperspective +#endif +out highp float hasCap; + +#ifdef VERTEX_COLOR +out lowp vec4 interpolatedVertexColor; +#endif + +#ifdef INSTANCED_OBJECT_ID +flat out highp uint interpolatedInstanceObjectId; +#endif + +#ifdef MULTI_DRAW +flat out highp uint drawId; +#endif + +/* Same as Math::Vector2::perpendicular() */ +vec2 perpendicular(vec2 a) { + return vec2(-a.y, a.x); +} + +void main() { + #ifdef UNIFORM_BUFFERS + #ifdef MULTI_DRAW + drawId = drawOffset + uint( + #ifndef GL_ES + gl_DrawIDARB /* Using GL_ARB_shader_draw_parameters, not GLSL 4.6 */ + #else + gl_DrawID + #endif + ); + #else + #define drawId drawOffset + #endif + + #ifdef TWO_DIMENSIONS + highp const mat3 transformationProjectionMatrix = mat3(transformationProjectionMatrices[drawId]); + #elif defined(THREE_DIMENSIONS) + highp const mat4 transformationProjectionMatrix = transformationProjectionMatrices[drawId]; + #else + #error + #endif + #if MATERIAL_COUNT > 1 + mediump const uint materialId = draws[drawId].draw_materialIdReserved & 0xffffu; + #else + #define materialId 0u + #endif + mediump const float width = materials[materialId].material_width; + mediump const float smoothness = materials[materialId].material_smoothness; + highp const float miterLimit = materials[materialId].material_miterLimit; + #endif + + #ifdef TWO_DIMENSIONS + highp const vec2 transformedPosition = (transformationProjectionMatrix* + #ifdef INSTANCED_TRANSFORMATION + instancedTransformationMatrix* + #endif + vec3(position, 1.0)).xy; + + highp const vec2 transformedPreviousPosition = (transformationProjectionMatrix* + #ifdef INSTANCED_TRANSFORMATION + instancedTransformationMatrix* + #endif + vec3(previousPosition, 1.0)).xy; + + highp const vec2 transformedNextPosition = (transformationProjectionMatrix* + #ifdef INSTANCED_TRANSFORMATION + instancedTransformationMatrix* + #endif + vec3(nextPosition, 1.0)).xy; + #elif defined(THREE_DIMENSIONS) + highp const vec4 transformedPosition4 = transformationProjectionMatrix* + #ifdef INSTANCED_TRANSFORMATION + instancedTransformationMatrix* + #endif + vec4(position, 1.0); + highp const vec2 transformedPosition = transformedPosition4.xy/transformedPosition4.w; + + highp const vec4 transformedPreviousPosition4 = transformationProjectionMatrix* + #ifdef INSTANCED_TRANSFORMATION + instancedTransformationMatrix* + #endif + vec4(previousPosition, 1.0); + highp const vec2 transformedPreviousPosition = transformedPreviousPosition4.xy/transformedPreviousPosition4.w; + + highp const vec4 transformedNextPosition4 = transformationProjectionMatrix* + #ifdef INSTANCED_TRANSFORMATION + instancedTransformationMatrix* + #endif + vec4(nextPosition, 1.0); + highp const vec2 transformedNextPosition = transformedNextPosition4.xy/transformedNextPosition4.w; + #else + #error + #endif + + /* Decide about the line direction vector `d` and edge direction vector `e` + from the `pointMarkerComponent` input. Quad corners 0 and 1 come from + segment endpoint A, are marked with the POINT_MARKER_BEGIN_MASK bit and + so their line direction is taken from `nextPosition`, quad corners 2 and + 3 come from B and are *not* marked with POINT_MARKER_BEGIN_MASK and so + their line direction is taken from `previousPosition`, with the + direction being always from point A to point B. The edge direction is + then perpendicular to the line direction, with points 0 and 2 marked + with POINT_MARKER_UP_MASK using it directly, while points 1 and 3 + don't have POINT_MARKER_UP_MASK and have to negate it: + + ^ ^ + e e + | | + [UP, BEGIN] 0-d--> 2-d--> [UP] + + A B + + [BEGIN] 1-d--> 3-d--> [] + | | + e e + v v + + The POINT_MARKER_CAP_MASK is then used below. */ + highp const vec2 lineDirection = bool(annotation & ANNOTATION_BEGIN_MASK) ? + transformedNextPosition - transformedPosition : + transformedPosition - transformedPreviousPosition; + mediump const float edgeSign = bool(annotation & ANNOTATION_UP_MASK) ? 1.0 : -1.0; + mediump const float neighborSign = bool(annotation & ANNOTATION_BEGIN_MASK) ? -1.0 : 1.0; + + /* Line direction and its length converted from the [-1, 1] unit square to + the screen space so we properly take aspect ratio into account. In the + end it undoes the transformation by multiplying by 2.0/viewportSize + again. */ + highp const vec2 screenspaceLineDirection = lineDirection*viewportSize/2.0; + highp const float screenspaceLineDirectionLength = length(screenspaceLineDirection); + + /* Normalized screenspace line and edge direction. In case of zero-sized + lines (i.e., points) the X axis is picked as line direction instead, and + thus Y axis for edge direction. */ + highp const vec2 screenspaceLineDirectionNormalized = screenspaceLineDirectionLength == 0.0 ? vec2(1.0, 0.0) : screenspaceLineDirection/screenspaceLineDirectionLength; + highp const vec2 screenspaceEdgeDirectionNormalized = perpendicular(screenspaceLineDirectionNormalized); + + /* Line width includes also twice the smoothness (because it's a radius + instead of a diameter, and is on both sides of the line), and is rounded + to whole pixels. So for the edge distance we need half of it. */ + mediump const float edgeDistance = ceil(width + 2.0*smoothness)*0.5; + #ifdef CAP_STYLE_BUTT + mediump const float capDistance = ceil(2.0*smoothness)*0.5; + #elif defined(CAP_STYLE_SQUARE) || defined(CAP_STYLE_ROUND) || defined(CAP_STYLE_TRIANGLE) + mediump const float capDistance = edgeDistance; + #else + #error + #endif + + /* Line segment half-length, passed to the fragment shader. Same for all + four points. */ + halfSegmentLength = screenspaceLineDirectionLength*0.5; + + /* Calculate the actual endpoint parameters depending on whether we're at a + line cap, line join bevel, line join miter etc. + + - `screenspacePointDirection` contains screenspace direction from + `transformedPosition` to the actual point. After undoing the + screenspace projection the sum of the two is written to + gl_Position. + - `centerDistanceSigned` contains signed distance from the edge to + center, passed to the fragment shader. It's chosen in a way that + interpolates to zero in the quad center, and the area where + `all(abs(centerDistanceSigned) <= vec2(halfSegmentLength + + capDistance, edgeDistance))` is inside the line. + - `hasCap` contains `abs(centerDistanceSigned.x)` with a sign + positive if the point is a cap and negative if it isn't. Given + segment endpoints A and B (and quad points 0/1 and 2/3 + corresponding to these), the following cases can happen: + + - if both have a cap, it's a negative value in both, thus has a + constant negative value in the fragment shader + - if neither have a cap, it's a positive value in both, thus has + a constant positive value in the fragment shader + - if one has a cap and the other not, it's a negative value in + one and positive in the other, interpolating to zero in the + quad center + + In the fragment shader, `abs(centerDistanceSigned)` and `sign(hasCap)` + is then used to perform cap rendering and antialiasing. For example, + with a standalone line segment that has square caps on both ends, the + value of `centerDistanceSigned` is like in the following diagram, with + `d` being `halfSegmentLength`, `w` being `edgeDistance`, `c` being + `capDistance`, and an extra margin for `smoothness` indicated by `s` and + the double border: + + [-d-c-s,+w+s] [+d+c+s,+w+s] + 0-----------------------------2 + [-d-c,+w]------------------[+d+c,+w] + | | | | hasCap[0] = hasCap[1] = +d+c+s + [-d-c,0] [0,0] [+d+c,0] + | | | | hasCap[2] = hasCap[3] = +d+c+s + [-d-c,-w]------------------[+d+c,-w] + 1-----------------------------3 + [-d-c-s,-w-s] [+d+c+s,-w+s] + + With a cap only on the left side, `centerDistanceSigned` would be like + this. Note the absence of a smoothness margin on the right side: + + [-d-c-s,+w+s] [+d,+w+s] + 0---------------------------2 + [-d-c,+w]-------------------[+d,+w] + | | | hasCap[0] = hasCap[1] = +d+c+s + [-d-c,0] [0,0] [+d,0] + | | | hasCap[2] = hasCap[3] = -d + [-d-c,-w]-------------------[+d,-w] + 1---------------------------3 + [-d-c-s,-w-s] [+d,-w-s] + + */ + centerDistanceSigned = + /* The the Y coordinate is same for all cases, X coordinate gets + further adjusted below */ + vec2(halfSegmentLength*neighborSign, edgeDistance*edgeSign); + highp vec2 screenspacePointDirection; + + /* Line join */ + if(bool(annotation & ANNOTATION_JOIN_MASK)) { + /* Neighbor direction `nd`, needed to distinguish whether this is the + inner or outer join point. Calculated with basically an inverse of + the logic used to calculate `lineDirection`, with the neighbor + direction always pointing from the A/B endpoint to the other + neighbor line endpoint: + + <--nd-0 [BEGIN] [END] 2-nd--> + + A B + + <--nd-1 [BEGIN] [END] 3-nd--> */ + highp const vec2 neighborDirection = bool(annotation & ANNOTATION_BEGIN_MASK) ? + transformedPreviousPosition - transformedPosition : + transformedNextPosition - transformedPosition; + /* Screenspace neighbor direction and its length, calculated + equivalently to screenspace line direction above */ + highp const vec2 screenspaceNeighborDirectionNormalized = normalize(neighborDirection*viewportSize/2.0); + + /* If the edge direction vector `e` and the neighbor direction vector + `nd` point to the opposite direction (i.e., their dot product is + negative), this is an outer point of the line and a candidate for + a bevel. + + ^ + e + | + -d->-2 + |\ + B | nd + | \ + -----3 v + + If a miter join is used instead of a bevel, the point is beveled + only if the line direction `d` and neighbor direction `nd` is + sharper than a limit (i.e., their dot product, or a cosine of their + angle, is between `[-1, -miterLimit]`). */ + const bool outerBeveledPoint = + dot(screenspaceEdgeDirectionNormalized*edgeSign, screenspaceNeighborDirectionNormalized) < 0.0 + #if defined(JOIN_STYLE_MITER) + && dot(screenspaceLineDirectionNormalized*neighborSign, screenspaceNeighborDirectionNormalized) < -miterLimit + #elif !defined(JOIN_STYLE_BEVEL) + #error + #endif + ; + + /* Outer point of a beveled join -- although + https://www.w3.org/TR/svg-strokes/#LineJoin doesn't define *what + exactly* is a bevel, it's defined as "Cuts the outside edge off + where a circle the diameter of the stroke intersects the stroke." at + e.g. https://apike.ca/prog_svg_line_cap_join.html. + + 0--- ----2a + | |^\ + | | e -_ + | | |ρ \ + A-- ----|--B-e->2b + | | | _-| + | | _- | + | | _- | | + 1-- --3 | | + | | | + C + + Which ultimately means the `2a` and `2b` quad endpoints are simply + the edge direction vector `e` away from point B, in one case with + the `e` calculated from the AB segment, and in the other from the BC + segment. */ + if(outerBeveledPoint) { + screenspacePointDirection = screenspaceEdgeDirectionNormalized*edgeDistance*edgeSign; + /* centerDistanceSigned doesn't need any adjustment, hasCap is set + below for both */ + + /* Otherwise it's either an outer point of a miter join (basically + points 2a and 2b from above evaluated to the same position), or the + inner point, which is the same for bevel and mitter joins. Given + normalized direction `d` and neighbor direction `nd`, + `normalized(d + nd)` is the "average" direction of the two and `perpendicular(normalized(d + nd))` gives us the direction from B to + 2 (or from 3 to B): + + 0--- --------+---2 + | | α/ \ + | w | / j \ + | |/ \ + A-- +_-----d->-B \ + | -_ α/α\ \ + | -_ / nd \ + | d + nd /-_ v \ + 1---- ----3 -_ \ + \ -+ + \ \ + C + + With `2α` being the angle between `d` and `nd`, `α` appears in two + right triangles and the following holds, `w` being the edge distance + from above, and `j` having the length that's needed to scale + `perpendicular(normalized(d + nd))` to get point 2: + + |d + nd| w 2 w + sin(α) = -------- = --- --> |j| = -------- + 2 |d| |j| |d + nd| + + Then, vector j is the following, meaning we avoid the normalization + square root completely: + + perp(d + nd) (2 w)perp(d + nd) + j = |j| ------------ = ----------------- + |d + nd| dot(d + nd) + + Point 3 is then just in the opposite direction; for the other side + it's done equivalently. */ + } else { + highp const vec2 averageDirection = neighborSign*screenspaceLineDirectionNormalized + screenspaceNeighborDirectionNormalized; + screenspacePointDirection = (perpendicular(averageDirection)*(neighborSign*edgeSign*2.0*edgeDistance/dot(averageDirection, averageDirection))); + + /* By projecting the point direction onto the line direction we + get a signed distance from the endpoint, adjust center distance + with that */ + centerDistanceSigned.x += dot(screenspacePointDirection, screenspaceLineDirectionNormalized); + } + + /* No cap here, store a negative value. TODO If + sign(centerDistanceSigned.x) is different from neighborSign, then + the sign here should be taken based on whether the other point is a + join -- add more bits to the vertex annotation? */ + hasCap = -abs(centerDistanceSigned.x); + + /* Line cap otherwise -- the quad corner 0/1/2/3 a sum of the signed cap + distance (`cdS`) and signed edge distance vectors (`eDS`), which are + formed by the line direction vector `d` and its perpendicular vector. + Neighbor direction (i.e., the other input from the one used to calculate + `lineDirection`) isn't used at all in this case. + + cDS + 0<---+---------- + | ^ + | | eDS + | | + | A--d--> + | + | + | + 1--- + + The signed center distance a sum of half segment length and the cap + distance, multiplied by the cap sign (thus negative for points derived + from A and positive for B). */ + } else { + screenspacePointDirection = + screenspaceLineDirectionNormalized*capDistance*neighborSign + + screenspaceEdgeDirectionNormalized*edgeDistance*edgeSign; + + /* Add signed cap distance to the center distance */ + centerDistanceSigned.x += capDistance*neighborSign; + + /* Cap is here, store a positive value */ + hasCap = abs(centerDistanceSigned.x); + } + + /* Undo the screenspace projection */ + highp const vec2 pointDirection = screenspacePointDirection*2.0/viewportSize; + + #ifdef TWO_DIMENSIONS + gl_Position = vec4(transformedPosition + pointDirection, 0.0, 1.0); + #elif defined(THREE_DIMENSIONS) + gl_Position = vec4(transformedPosition4.xy + pointDirection*transformedPosition4.w, transformedPosition4.zw); + #ifndef CAN_USE_NOPERSPECTIVE + /* See the CAN_USE_NOPERSPECTIVE macro definition at the top for details */ + gl_Position /= gl_Position.w; + #endif + #else + #error + #endif + + #ifdef VERTEX_COLOR + /* Vertex colors, if enabled */ + interpolatedVertexColor = vertexColor; + #endif + + #ifdef INSTANCED_OBJECT_ID + /* Instanced object ID, if enabled */ + interpolatedInstanceObjectId = instanceObjectId; + #endif +} diff --git a/src/Magnum/Shaders/LineGL.cpp b/src/Magnum/Shaders/LineGL.cpp new file mode 100644 index 000000000..cc867a675 --- /dev/null +++ b/src/Magnum/Shaders/LineGL.cpp @@ -0,0 +1,432 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 "LineGL.h" + +#include +#include +#include +#include +#include + +#include "Magnum/GL/Buffer.h" +#include "Magnum/GL/Context.h" +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/Shader.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Shaders/Line.h" +#include "Magnum/Shaders/Implementation/CreateCompatibilityShader.h" +#include "Magnum/Shaders/Implementation/lineMiterLimit.h" + +namespace Magnum { namespace Shaders { + +using namespace Containers::Literals; +using namespace Math::Literals; + +namespace { + enum: Int { + /* 0/1/2/3 taken by Phong (A/D/S/N), 4 by MeshVisualizer colormap, 5 by + object ID textures, 6 by Vector */ + TextureUnit = 7 + }; + + 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, + MaterialBufferBinding = 3 + }; +} + +template typename LineGL::CompileState LineGL::compile(const Configuration& configuration) { + CORRADE_ASSERT(!(configuration.flags() >= Flag::UniformBuffers) || configuration.materialCount(), + "Shaders::LineGL: material count can't be zero", CompileState{NoCreate}); + CORRADE_ASSERT(!(configuration.flags() >= Flag::UniformBuffers) || configuration.drawCount(), + "Shaders::LineGL: draw count can't be zero", CompileState{NoCreate}); + + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::EXT::gpu_shader4); + if(configuration.flags() >= Flag::UniformBuffers) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::uniform_buffer_object); + #endif + if(configuration.flags() >= Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::shader_draw_parameters); + #elif !defined(MAGNUM_TARGET_WEBGL) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ANGLE::multi_draw); + #else + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::WEBGL::multi_draw); + #endif + } + + #ifdef MAGNUM_BUILD_STATIC + /* Import resources on static build, if not already */ + if(!Utility::Resource::hasGroup("MagnumShadersGL"_s)) + importShaderResources(); + #endif + Utility::Resource rs{"MagnumShadersGL"_s}; + + + #ifndef MAGNUM_TARGET_GLES + const GL::Context& context = GL::Context::current(); + const GL::Version version = context.supportedVersion({GL::Version::GL320, GL::Version::GL310, GL::Version::GL300, GL::Version::GL210}); + #else + constexpr GL::Version version = GL::Version::GLES300; + #endif + + /* Cap and join style is needed by both the vertex and fragment shader, + prepare their defines just once for both */ + Containers::StringView capStyleDefine, joinStyleDefine; + switch(configuration.capStyle()) { + case LineCapStyle::Butt: + capStyleDefine = "#define CAP_STYLE_BUTT\n"_s; + break; + case LineCapStyle::Square: + capStyleDefine = "#define CAP_STYLE_SQUARE\n"_s; + break; + case LineCapStyle::Round: + capStyleDefine = "#define CAP_STYLE_ROUND\n"_s; + break; + case LineCapStyle::Triangle: + capStyleDefine = "#define CAP_STYLE_TRIANGLE\n"_s; + break; + } + switch(configuration.joinStyle()) { + case LineJoinStyle::Miter: + joinStyleDefine = "#define JOIN_STYLE_MITER\n"_s; + break; + case LineJoinStyle::Bevel: + joinStyleDefine = "#define JOIN_STYLE_BEVEL\n"_s; + break; + } + CORRADE_INTERNAL_ASSERT(capStyleDefine); + CORRADE_INTERNAL_ASSERT(joinStyleDefine); + + GL::Shader vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); + vert.addSource(capStyleDefine) + .addSource(joinStyleDefine) + .addSource(configuration.flags() & Flag::VertexColor ? "#define VERTEX_COLOR\n"_s : ""_s) + .addSource(dimensions == 2 ? "#define TWO_DIMENSIONS\n"_s : "#define THREE_DIMENSIONS\n"_s) + .addSource(configuration.flags() >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n"_s : ""_s) + .addSource(configuration.flags() & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n"_s : ""_s); + if(configuration.flags() >= Flag::UniformBuffers) { + vert.addSource(Utility::format( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + configuration.drawCount(), + configuration.materialCount())); + vert.addSource(configuration.flags() >= Flag::MultiDraw ? "#define MULTI_DRAW\n"_s : ""_s); + } + vert.addSource(rs.getString("generic.glsl"_s)) + .addSource(rs.getString("Line.vert"_s)) + .submitCompile(); + + GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); + frag.addSource(capStyleDefine) + .addSource(joinStyleDefine) + .addSource(configuration.flags() & Flag::VertexColor ? "#define VERTEX_COLOR\n"_s : ""_s) + .addSource(configuration.flags() & Flag::ObjectId ? "#define OBJECT_ID\n"_s : ""_s) + .addSource(configuration.flags() >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n"_s : ""_s); + if(configuration.flags() >= Flag::UniformBuffers) { + frag.addSource(Utility::format( + "#define UNIFORM_BUFFERS\n" + "#define DRAW_COUNT {}\n" + "#define MATERIAL_COUNT {}\n", + configuration.drawCount(), + configuration.materialCount())); + frag.addSource(configuration.flags() >= Flag::MultiDraw ? "#define MULTI_DRAW\n"_s : ""_s); + } + frag.addSource(rs.getString("generic.glsl"_s)) + .addSource(rs.getString("Line.frag"_s)) + .submitCompile(); + + LineGL out{NoInit}; + out._flags = configuration.flags(); + out._capStyle = configuration.capStyle(); + out._joinStyle = configuration.joinStyle(); + out._materialCount = configuration.materialCount(); + out._drawCount = configuration.drawCount(); + + out.attachShaders({vert, frag}); + + /* ES3 has this done in the shader directly and doesn't even provide + bindFragmentDataLocation() */ + #ifndef MAGNUM_TARGET_GLES + if(!context.isExtensionSupported(version)) { + out.bindAttributeLocation(Position::Location, "position"_s); + out.bindAttributeLocation(PreviousPosition::Location, "previousPosition"_s); + out.bindAttributeLocation(NextPosition::Location, "nextPosition"_s); + out.bindAttributeLocation(Annotation::Location, "annotation"_s); + if(configuration.flags() & Flag::VertexColor) + out.bindAttributeLocation(Color3::Location, "vertexColor"_s); /* Color4 is the same */ + if(configuration.flags() & Flag::ObjectId) { + out.bindFragmentDataLocation(ColorOutput, "color"_s); + out.bindFragmentDataLocation(ObjectIdOutput, "objectId"_s); + } + if(configuration.flags() >= Flag::InstancedObjectId) + out.bindAttributeLocation(ObjectId::Location, "instanceObjectId"_s); + if(configuration.flags() & Flag::InstancedTransformation) + out.bindAttributeLocation(TransformationMatrix::Location, "instancedTransformationMatrix"_s); + } + #endif + + out.submitLink(); + + return CompileState{std::move(out), std::move(vert), std::move(frag) + #ifndef MAGNUM_TARGET_GLES + , version + #endif + }; +} + +template LineGL::LineGL(CompileState&& state): LineGL{static_cast(std::move(state))} { + #ifdef CORRADE_GRACEFUL_ASSERT + /* When graceful assertions fire from within compile(), we get a NoCreate'd + CompileState. Exiting makes it possible to test the assert. */ + if(!id()) return; + #endif + + CORRADE_INTERNAL_ASSERT_OUTPUT(checkLink({GL::Shader(state._vert), GL::Shader(state._frag)})); + + const GL::Context& context = GL::Context::current(); + + #ifndef MAGNUM_TARGET_GLES + const GL::Version version = state._version; + if(!context.isExtensionSupported(version)) + #endif + { + _viewportSizeUniform = uniformLocation("viewportSize"_s); + if(_flags >= Flag::UniformBuffers) { + if(_drawCount > 1) + _drawOffsetUniform = uniformLocation("drawOffset"_s); + } else { + _transformationProjectionMatrixUniform = uniformLocation("transformationProjectionMatrix"_s); + _widthUniform = uniformLocation("width"_s); + _smoothnessUniform = uniformLocation("smoothness"_s); + if(_joinStyle == LineJoinStyle::Miter) + _miterLimitUniform = uniformLocation("miterLimit"_s); + _backgroundColorUniform = uniformLocation("backgroundColor"_s); + _colorUniform = uniformLocation("color"_s); + if(_flags & Flag::ObjectId) + _objectIdUniform = uniformLocation("objectId"_s); + } + } + + #ifndef MAGNUM_TARGET_GLES + if(!context.isExtensionSupported(version)) + #endif + { + if(_flags >= Flag::UniformBuffers) { + setUniformBlockBinding(uniformBlockIndex("TransformationProjection"_s), TransformationProjectionBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Draw"_s), DrawBufferBinding); + setUniformBlockBinding(uniformBlockIndex("Material"_s), MaterialBufferBinding); + } + } + + /* Set defaults in OpenGL ES (for desktop they are set in shader code itself) */ + #ifdef MAGNUM_TARGET_GLES + if(_flags >= Flag::UniformBuffers) { + /* Draw offset is zero by default */ + } else { + setTransformationProjectionMatrix(MatrixTypeFor{Math::IdentityInit}); + setWidth(1.0f); + /* Smoothness is zero by default */ + if(_joinStyle == LineJoinStyle::Miter) + setMiterLengthLimit(4.0f); + setColor(Magnum::Color4{1.0f}); + /* Object ID is zero by default */ + } + #endif + + static_cast(context); +} + +template LineGL::LineGL(const Configuration& configuration): LineGL{compile(configuration)} {} + +template LineGL::LineGL(NoInitT) {} + +template LineGL& LineGL::setViewportSize(const Vector2& size) { + setUniform(_viewportSizeUniform, size); + return *this; +} + +template LineGL& LineGL::setTransformationProjectionMatrix(const MatrixTypeFor& matrix) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::LineGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled", *this); + setUniform(_transformationProjectionMatrixUniform, matrix); + return *this; +} + +template LineGL& LineGL::setBackgroundColor(const Magnum::Color4& color) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::LineGL::setBackgroundColor(): the shader was created with uniform buffers enabled", *this); + setUniform(_backgroundColorUniform, color); + return *this; +} + +template LineGL& LineGL::setColor(const Magnum::Color4& color) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::LineGL::setColor(): the shader was created with uniform buffers enabled", *this); + setUniform(_colorUniform, color); + return *this; +} + +template LineGL& LineGL::setWidth(const Float width) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::LineGL::setWidth(): the shader was created with uniform buffers enabled", *this); + setUniform(_widthUniform, width); + return *this; +} + +template LineGL& LineGL::setSmoothness(const Float smoothness) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::LineGL::setSmoothness(): the shader was created with uniform buffers enabled", *this); + setUniform(_smoothnessUniform, smoothness); + return *this; +} + +template LineGL& LineGL::setMiterLengthLimit(const Float limit) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::LineGL::setMiterLengthLimit(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_joinStyle == LineJoinStyle::Miter, + "Shaders::LineGL::setMiterLengthLimit(): the shader was created with" << _joinStyle, *this); + setUniform(_miterLimitUniform, Implementation::lineMiterLengthLimit("Shaders::LineGL::setMiterLengthLimit():", limit)); + return *this; +} + +template LineGL& LineGL::setMiterAngleLimit(const Rad limit) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::LineGL::setMiterAngleLimit(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_joinStyle == LineJoinStyle::Miter, + "Shaders::LineGL::setMiterAngleLimit(): the shader was created with" << _joinStyle, *this); + setUniform(_miterLimitUniform, Implementation::lineMiterAngleLimit("Shaders::LineGL::setMiterAngleLimit():", limit)); + return *this; +} + +template LineGL& LineGL::setObjectId(UnsignedInt id) { + CORRADE_ASSERT(!(_flags >= Flag::UniformBuffers), + "Shaders::LineGL::setObjectId(): the shader was created with uniform buffers enabled", *this); + CORRADE_ASSERT(_flags & Flag::ObjectId, + "Shaders::LineGL::setObjectId(): the shader was not created with object ID enabled", *this); + setUniform(_objectIdUniform, id); + return *this; +} + +template LineGL& LineGL::setDrawOffset(const UnsignedInt offset) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::LineGL::setDrawOffset(): the shader was not created with uniform buffers enabled", *this); + CORRADE_ASSERT(offset < _drawCount, + "Shaders::LineGL::setDrawOffset(): draw offset" << offset << "is out of bounds for" << _drawCount << "draws", *this); + if(_drawCount > 1) setUniform(_drawOffsetUniform, offset); + return *this; +} + +template LineGL& LineGL::bindTransformationProjectionBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::LineGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding); + return *this; +} + +template LineGL& LineGL::bindTransformationProjectionBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::LineGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, TransformationProjectionBufferBinding, offset, size); + return *this; +} + +template LineGL& LineGL::bindDrawBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::LineGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding); + return *this; +} + +template LineGL& LineGL::bindDrawBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::LineGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, DrawBufferBinding, offset, size); + return *this; +} + +template LineGL& LineGL::bindMaterialBuffer(GL::Buffer& buffer) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::LineGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding); + return *this; +} + +template LineGL& LineGL::bindMaterialBuffer(GL::Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + CORRADE_ASSERT(_flags >= Flag::UniformBuffers, + "Shaders::LineGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled", *this); + buffer.bind(GL::Buffer::Target::Uniform, MaterialBufferBinding, offset, size); + return *this; +} + +template LineGL::Configuration::Configuration(): _capStyle{LineCapStyle::Square}, _joinStyle{LineJoinStyle::Miter} {} + +template class MAGNUM_SHADERS_EXPORT LineGL<2>; +template class MAGNUM_SHADERS_EXPORT LineGL<3>; + +namespace Implementation { + +Debug& operator<<(Debug& debug, const LineGLFlag value) { + debug << "Shaders::LineGL::Flag" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case LineGLFlag::v: return debug << "::" #v; + _c(VertexColor) + _c(ObjectId) + _c(InstancedObjectId) + _c(InstancedTransformation) + _c(UniformBuffers) + _c(MultiDraw) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const LineGLFlags value) { + return Containers::enumSetDebugOutput(debug, value, "Shaders::LineGL::Flags{}", { + LineGLFlag::VertexColor, + LineGLFlag::InstancedObjectId, /* Superset of ObjectId */ + LineGLFlag::ObjectId, + LineGLFlag::InstancedTransformation, + LineGLFlag::MultiDraw, /* Superset of UniformBuffers */ + LineGLFlag::UniformBuffers + }); +} + +} + +}} diff --git a/src/Magnum/Shaders/LineGL.h b/src/Magnum/Shaders/LineGL.h new file mode 100644 index 000000000..35a18c0d1 --- /dev/null +++ b/src/Magnum/Shaders/LineGL.h @@ -0,0 +1,1150 @@ +#ifndef Magnum_Shaders_LineGL_h +#define Magnum_Shaders_LineGL_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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. +*/ + +#ifndef MAGNUM_TARGET_GLES2 +/** @file + * @brief Class @ref Magnum::Shaders::LineGL, typedef @ref Magnum::Shaders::LineGL2D, @ref Magnum::Shaders::LineGL3D + * @m_since_latest + */ +#endif + +#include + +#include "Magnum/DimensionTraits.h" +#include "Magnum/GL/AbstractShaderProgram.h" +#include "Magnum/Shaders/GenericGL.h" +#include "Magnum/Shaders/glShaderWrapper.h" +#include "Magnum/Shaders/visibility.h" + +#ifndef MAGNUM_TARGET_GLES2 +namespace Magnum { namespace Shaders { + +namespace Implementation { + enum class LineGLFlag: UnsignedShort { + VertexColor = 1 << 0, + #ifndef MAGNUM_TARGET_GLES2 + ObjectId = 1 << 1, + InstancedObjectId = (1 << 2)|ObjectId, + #endif + InstancedTransformation = 1 << 3, + #ifndef MAGNUM_TARGET_GLES2 + UniformBuffers = 1 << 4, + MultiDraw = UniformBuffers|(1 << 5) + #endif + }; + typedef Containers::EnumSet LineGLFlags; + CORRADE_ENUMSET_OPERATORS(LineGLFlags) +} + +/** +@brief Line GL shader +@m_since_latest + +Renders lines expanded to quads in screen space. Compared to builtin GPU line +rendering, the lines can be of arbitrary width, with configurable join and cap +styles, and antialiased independently of MSAA being used or not. + +@requires_gl30 Extension @gl_extension{EXT,gpu_shader4} +@requires_gles30 Requires integer support in shaders which is not available in + OpenGL ES 2.0. +@requires_webgl20 Requires integer support in shaders which is not available in + WebGL 1.0. + +@section Shaders-LineGL-usage Usage + +The shader doesn't work with @ref MeshPrimitive::Lines, +@ref MeshPrimitive::LineStrip or @ref MeshPrimitive::LineLoop directly, as that +would only be implementable with a relatively expensive geometry shader. +Instead, it requires the input data to be organized in quads, with +@ref Position, @ref PreviousPosition and @ref NextPosition attributes +describing point wíth their surroundings, and @ref Annotation with +point-specific annotation such as whether given point is a line cap or a join +with neighboring segment. The data layout is described in detail in +@ref Shaders-LineGL-mesh-representation below. + +For rendering use @ref setTransformationProjectionMatrix(), +@ref setColor(), @ref setWidth() and others. It's important to pass viewport +size in @ref setViewportSize() as the line width is interpreted relative to it. + +@snippet MagnumShaders-gl.cpp LineGL-usage2 + +@subsection Shaders-LineGL-usage-triangulation Line triangulation + +Each line segment is rendered as a quad consisting of two triangles. Standalone +segments have cap style configurable via @ref Configuration::setCapStyle() with +the following styles, segments of zero length can be also used to render +points. The @m_span{m-label m-default} white @m_endspan lines show generated +triangles, the @m_span{m-label m-info} blue @m_endspan line is the actually +visible edge between foreground and background: + +@m_class{m-row} + +@parblock + +@m_div{m-col-l-6 m-text-center} +@htmlinclude line-cap-butt.svg +@ref LineCapStyle::Butt +@m_enddiv + +@m_div{m-col-l-6 m-text-center} +@htmlinclude line-cap-square.svg +@ref LineCapStyle::Square +@m_enddiv + +@m_div{m-col-l-6 m-text-center} +@htmlinclude line-cap-round.svg +@ref LineCapStyle::Round +@m_enddiv + +@m_div{m-col-l-6 m-text-center} +@htmlinclude line-cap-triangle.svg +@ref LineCapStyle::Triangle +@m_enddiv + +@endparblock + +Joins between consecutive segments in contiguous line strips are expanded to +form a gap-less mesh without overlaps. Depending on join style picked in +@ref Configuration::setJoinStyle() and angle between the segments the area +between points `A`, `B` and `C` may be filled with another triangle: + +@m_class{m-row} + +@parblock + +@m_div{m-col-l-6 m-text-center} +@htmlinclude line-join-miter.svg +@ref LineJoinStyle::Miter +@m_enddiv + +@m_div{m-col-l-6 m-text-center} +@htmlinclude line-join-bevel.svg +@ref LineJoinStyle::Bevel +@m_enddiv + +@endparblock + +@subsection Shaders-LineGL-usage-antialiasing Antialiasing + +The lines aren't smoothed out by default, use @ref setSmoothness() to pick a +tradeoff between the line being aliased and blurry. This is implemented by +interpolating between the foreground color and the background, which assumes +blending is set up for pre-multiplied alpha. If you're drawing lines on a +single-color background, you can @ref setBackgroundColor() to a color matching +the background and keep blending disabled, but note that you may get artifacts +if the lines are self-overlapping. + +@snippet MagnumShaders-gl.cpp LineGL-usage-antialiasing + +@subsection Shaders-LineGL-usage-3d Lines in 3D + +The 3D variant of this shader renders the geometry with depth values derived +from the original line endpoints, however without any perspective shortening +applied --- the line width is the same viewport-relative value independently of +the depth the point is at. + +@section Shaders-LineGL-object-id Object ID output + +The shader supports writing object ID to the framebuffer for object picking or +other annotation purposes. Enable it using @ref Flag::ObjectId and set up an +integer buffer attached to the @ref ObjectIdOutput attachment. If you have a +batch of meshes with different object IDs, enable @ref Flag::InstancedObjectId +and supply per-vertex IDs to the @ref ObjectId attribute. The output will +contain a sum of the per-vertex ID and ID coming from @ref setObjectId(). + +The functionality is practically the same as in the @ref FlatGL shader, see +@ref Shaders-FlatGL-object-id "its documentation" for more information and usage +example. + +Note that the object ID is emitted for the whole triangle area, including +transparent areas of caps when using @ref LineCapStyle::Round or +@ref LineCapStyle::Triangle as well as semi-transparent edges with smoothness +values larger than zero. In particular, the object ID output will be aliased +even if the color output isn't. + +@section Shaders-LineGL-instancing Instanced rendering + +Enabling @ref Flag::InstancedTransformation will turn the shader into an +instanced one. It'll take per-instance transformation from the +@ref TransformationMatrix attribute, applying it before the matrix set by +@ref setTransformationProjectionMatrix(). Besides that, @ref Flag::VertexColor +(and the @ref Color3 / @ref Color4) attributes can work as both per-vertex and +per-instance. The functionality is practically the same as in the @ref FlatGL +shader, see @ref Shaders-FlatGL-instancing "its documentation" for more +information and usage example. + +@requires_gl33 Extension @gl_extension{ARB,instanced_arrays} + +@section Shaders-LineGL-ubo Uniform buffers + +See @ref shaders-usage-ubo for a high-level overview that applies to all +shaders. In this particular case, because the shader doesn't need a separate +projection and transformation matrix, a combined one is supplied via a +@ref TransformationProjectionUniform2D / @ref TransformationProjectionUniform3D +buffer bound with @ref bindTransformationProjectionBuffer(). To maximize use of the limited uniform buffer memory, materials are supplied separately in a +@ref LineMaterialUniform buffer bound with @ref bindMaterialBuffer() and then +referenced via @relativeref{LineDrawUniform,materialId} from a +@ref LineDrawUniform bound with @ref bindDrawBuffer(). A uniform buffer setup +equivalent to the @ref Shaders-LineGL-usage "snippet at the top" would look +like this --- note that @ref setViewportSize() is an immediate uniform +here as well, as it's assumed to be set globally and rarely changed: + +@snippet MagnumShaders-gl.cpp LineGL-ubo + +For a multidraw workflow enable @ref Flag::MultiDraw and supply desired +material and draw count via @ref Configuration::setMaterialCount() and +@relativeref{Configuration,setDrawCount()}. For every draw then specify +material references. Besides that, the usage is similar for all shaders, see +@ref shaders-usage-multidraw for an example. + +@requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} for uniform + buffers. +@requires_gl46 Extension @gl_extension{ARB,shader_draw_parameters} for + multidraw. +@requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + (unlisted) for multidraw. +@requires_webgl_extension Extension @webgl_extension{ANGLE,multi_draw} for + multidraw. + +@section Shaders-LineGL-mesh-representation Line mesh representation + +In order to avoid performing expensive CPU-side expansion of the quads every +time the transformation, line width and other parameters change, the shader +gets just the original line segment endpoints as an input, transforms them in +2D or 3D as usual, and then expands them in screen space for a desired line +width. + +@htmlinclude line-quad-expansion.svg + +Ignoring all complexity related to line caps and joins for now, an example +expansion of three line segments into quads is shown above --- the first two +segments form a join at the @m_span{m-label m-info} blue @m_endspan point, the +third segment is standalone. In order to form a quad, each of the points has +to be present twice in the vertex stream, with first copy expanding up and +second copy expanding down. The @ref Position data needed to render quads would +then look like below, color-coded to match the above, and in order following +the segment direction. An index buffer would then form two triangles out of +every four points --- @cpp {0, 1, 2, 2, 1, 3, …} @ce. + +@htmlinclude line-quad-data.svg + +To figure out the direction in which to expand, for given endpoint position the +shader needs also *screen-space direction* to the other endpoint. But since a +2D / 3D transformation has to be applied for both endpoints before calculating +their screen-space position, it makes more sense to supply directly its +position instead, and calculate the direction only after transforming both +points. The input data would then look like this, with "previous" positions +shown above and "next" positions shown below: + +@htmlinclude line-quad-data-other.svg + +With line joins and caps present, the quad expansion changes in the following +way. In the general case, to avoid overlapping geometry and gaps, points `B` +and `D` collapse to a single position and the area in between is filled with an +extra triangle. Depending on the transformation, it can however also happen +that `A` and `C` collapse into a single point instead, for example if the +@m_span{m-label m-primary} azure @m_endspan point would appear above the +@m_span{m-label m-success} green @m_endspan one instead of below. Thus the +index buffer needs to handle both cases --- @cpp {…, 2, 3, 4, 4, 3, 5, …} @ce +--- and one of them always denegerates to a zero-area triangle. + +@htmlinclude line-quad-expansion-joins-caps.svg + +To handle the join, the shader needs to know whether there's a neighboring line +segment to join with, and what is the position of its other endpoint. Thus, +every vertex gets *two* neighboring positions, a @ref PreviousPosition and a +@ref NextPosition. Both of them are filled only in case the point forms a line +join; if the point is a line cap, one of them is left unspecified. + +@htmlinclude line-quad-data-neighbor.svg + +What's left is giving the shader an ability to distinguish the direction in +which to expand the point (@ref LineVertexAnnotation::Up or downwards), whether +it's a @relativeref{LineVertexAnnotation,Join} or a cap and whether the point +is a @relativeref{LineVertexAnnotation,Begin} or an end of the segment in order +to know what the neigboring positions represent. This info is stored in the +@ref Annotation attribute and shown with `U`, `J` and `B` letters above. In +this particular case the info could also be inferred from the vertex index and +for example NaNs in the neigbor positions, but a dedicated attribute makes it +more flexible for optimized data layouts explained below. + +@subsection Shaders-LineGL-data-representation-overlap Overlapping layouts with less data redundancy + +Assuming a 3D line mesh with floating-point position attributes, the +@ref Annotation attribute packed into a single byte and +@ref MeshIndexType::UnsignedShort indices, a a single contiguous line strip +consisting of @f$ n @f$ line segments would need +@f$ (4(36 + 1) + 24)n = 172n @f$ bytes of data. In comparison, +CPU-side-generated indexed quads would need just +@f$ (24 + 18)n + 24 = 42n + 24 @f$ bytes, and a (non-indexed) +@ref MeshPrimitive::LineStrip only @f$ 12n + 12 @f$ bytes, which is ~14x less. +Fortunately, the position data can be organized in a way that makes it possible +to reuse them for previous and next positions as well, by binding the same data +again under an offset. + +There's the following possibilites, each with different tradeoffs depending on +the use case. Such data layout variants require no special-casing in the +shader, only a different mesh setup, making it possible to pick the best option +for each line mesh without having to pay for expensive shader switching. + +@subsubsection Shaders-LineGL-data-representation-overlap-nojoin Standalone line segments without joins + +If the mesh consists just of loose line segments and no joints need to be +drawn, the @ref Position attribute can be bound with an offset of @cpp -2 @ce +elements to the @ref PreviousPosition and @cpp +2 @ce elements to the +@ref NextPosition. To avoid out-of-bound reads, the position buffer needs to be +padded with two elements at the front and at the end. Together with no indices +needed for joint triangles the memory requirement would be reduced to +@f$ (4(12 + 1) + 12)n + 12 = 64 n + 12 @f$ bytes, which is roughly the same +amount of data as for loose CPU-side-generated indexed quads, and ~2.7x as much +as @f$ 24n @f$ bytes a sufficiently large (non-indexed) +@ref MeshPrimitive::Lines would need. + +@m_div{m-row m-container-inflate} +@m_div{m-col-l-12 m-nopady m-nopadx} +@htmlinclude line-quad-data-overlap-nojoin.svg +@m_enddiv +@m_enddiv + +@subsubsection Shaders-LineGL-data-representation-overlap-generic Generic lines + +For arbitrary lines that consist of both joined strips and standalone segments +and the joins can be of any style in any direction, the @ref Position attribute +has to be additionally padded with two elements at begin and end of every +contiguous line strip together with skipping the elements in the index buffer +appropriately, and then bound with an offset of @cpp -4 @ce elements to the +@ref PreviousPosition and @cpp +4 @ce elements to the @ref NextPosition. + +This needs only one triangle in the index buffer for each join instead of two +and has a memory requirement of +@f$ (4(12 + 1) + 24)n + (4(12 + 1) - 12)l + 12 @f$ bytes, with @f$ l @f$ +being the line strip count. With a mesh consisting of just a single strip this +is @f$ 76n + 52 @f$ bytes, which is ~1.8x as much as CPU-side-generated indexed +quads and ~6.3x as much as a @ref MeshPrimitive::LineStrip would need. + +@m_div{m-row m-container-inflate} +@m_div{m-col-l-12 m-nopady m-nopadx} +@htmlinclude line-quad-data-overlap-generic.svg +@m_enddiv +@m_enddiv + +@subsubsection Shaders-LineGL-data-representation-overlap-fixedjoin Lines with fixed join directions + +If the joint direction is known to be fixed, i.e. the B and D points always +collapse to the same position independently of the transform used, the two +points can be replaced with just one. This is commonly the case in 2D if +negative transformation scaling isn't involved and with planar line art in 3D +if it additionally also isn't viewed from the back side. This allows padding +of the @ref Position attribute at the begin and end of every contiguous line +strip to be reduced to just one element, binding it with an offset of +@cpp -3 @ce elements to the @ref PreviousPosition and @cpp +3 @ce elements to +the @ref NextPosition. + +This has a memory requirement of +@f$ (3(12 + 1) + 18)n + (3(12 + 1) - 6)l + 12 @f$ bytes. With a mesh consisting +of just a single strip this is @f$ 57n + 45 @f$ bytes, which is ~1.4x as much +as CPU-side-generated indexed quads and ~4.75x as much as a +@ref MeshPrimitive::LineStrip would need. + +@m_div{m-row m-container-inflate} +@m_div{m-col-l-12 m-nopady m-nopadx} +@htmlinclude line-quad-data-overlap-fixedjoin.svg +@m_enddiv +@m_enddiv + +@subsubsection Shaders-LineGL-data-representation-overlap-miterjoin Lines with miter joins only + +The final and most data-efficient case is for line meshes where the contiguous +segments consist of miter joints only (i.e., with the assumption that the angle +between two segments is never too sharp to fall back to +@ref LineJoinStyle::Bevel), resulting in the join collapsing to just two +vertices, with no triangle in between: + +@htmlinclude line-quad-expansion-joins-miter-caps.svg + +This is the usual case for finely subdivided curves. Generic line art can be +patched in a preprocessing step, subdividing sharp corners to a sequence of +joins with larger angles. This layout doesn't require any padding of the +@ref Position attribute between contiguous line strips, and it's bound with an +offset of @cpp -2 @ce elements to the @ref PreviousPosition and @cpp +2 @ce +elements to the @ref NextPosition. + +The memory requirement is @f$ (2(12 + 1) + 12)n + 2(12 + 1)l + 12 @f$ bytes. +With a mesh consisting of a single strip it's @f$ 38n + 38 @f$ bytes. This is +roughly the same memory use as @f$ 36n + 24 @f$ bytes for CPU-side-generated +quads with miter joins only, and ~3.2x as much as a +@ref MeshPrimitive::LineStrip would need. + +@m_div{m-row m-container-inflate} +@m_div{m-col-l-12 m-nopady m-nopadx} +@htmlinclude line-quad-data-overlap-miterjoin.svg +@m_enddiv +@m_enddiv +*/ +template class MAGNUM_SHADERS_EXPORT LineGL: public GL::AbstractShaderProgram { + public: + /* MSVC needs dllexport here as well */ + class MAGNUM_SHADERS_EXPORT Configuration; + class CompileState; + + /** + * @brief Vertex position + * + * @ref shaders-generic "Generic attribute", + * @relativeref{Magnum,Vector2} in 2D, @relativeref{Magnum,Vector3} in + * 3D. + */ + typedef typename GenericGL::Position Position; + + /** + * @brief Previous position + * + * @relativeref{Magnum,Vector2} in 2D, @relativeref{Magnum,Vector3} in + * 3D. Uses the same location as @ref GenericGL::Tangent with the + * assumption that lines don't need tangent space information. + * + * If @ref LineVertexAnnotation::Begin is set in @ref Annotation, + * contains the other point of the neighboring line segment if + * @ref LineVertexAnnotation::Join is also set, and is ignored + * otherwise. If @ref LineVertexAnnotation::Begin is not set in + * @ref Annotation, contains the other point of the line segment. + */ + typedef GL::Attribute<3, VectorTypeFor> PreviousPosition; + + /** + * @brief Next position + * + * @relativeref{Magnum,Vector2} in 2D, @relativeref{Magnum,Vector3} in + * 3D. Uses the same location as @ref GenericGL::Normal with the + * assumption that lines don't need tangent space information. + * + * If @ref LineVertexAnnotation::Begin is set in @ref Annotation, + * contains the other point of the line segment. If + * @ref LineVertexAnnotation::Begin is not set in @ref Annotation, + * contains the other point of the neighboring line segment if + * @ref LineVertexAnnotation::Join is set, and is ignored otherwise. + */ + typedef GL::Attribute<5, VectorTypeFor> NextPosition; + + /** + * @brief Vertex annotation + * + * Uses the same location as @ref GenericGL::TextureCoordinates with + * the assumption that lines don't need a two-dimensional texture + * space information. + * + * Contains a set of @ref LineVertexAnnotation bits, see their + * documentation for more information. The values are guaranteed to fit + * into 8 bits. + */ + typedef GL::Attribute<1, UnsignedInt> Annotation; + + /** + * @brief Three-component vertex color + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Color3. Use + * either this or the @ref Color4 attribute. + */ + typedef typename GenericGL::Color3 Color3; + + /** + * @brief Four-component vertex color + * + * @ref shaders-generic "Generic attribute", @ref Magnum::Color4. Use + * either this or the @ref Color3 attribute. + */ + typedef typename GenericGL::Color4 Color4; + + /** + * @brief (Instanced) object ID + * + * @ref shaders-generic "Generic attribute", + * @relativeref{Magnum,UnsignedInt}. Used only if + * @ref Flag::InstancedObjectId is set. + */ + typedef typename GenericGL::ObjectId ObjectId; + + /** + * @brief (Instanced) transformation matrix + * + * @ref shaders-generic "Generic attribute", + * @relativeref{Magnum,Matrix3} in 2D, @relativeref{Magnum,Matrix4} in + * 3D. Used only if @ref Flag::InstancedTransformation is set. + * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} + */ + typedef typename GenericGL::TransformationMatrix TransformationMatrix; + + enum: UnsignedInt { + /** + * Color shader output. Present always, expects three- or + * four-component floating-point or normalized buffer attachment. + */ + ColorOutput = GenericGL::ColorOutput, + + /** + * Object ID shader output. @ref shaders-generic "Generic output", + * present only if @ref Flag::ObjectId is set. Expects a + * single-component unsigned integral attachment. Writes the value + * set in @ref setObjectId() and possibly also a per-vertex ID and + * an ID fetched from a texture, see @ref Shaders-LineGL-object-id + * for more information. + * @requires_gl30 Extension @gl_extension{EXT,texture_integer} + */ + ObjectIdOutput = GenericGL::ObjectIdOutput + }; + + #ifdef DOXYGEN_GENERATING_OUTPUT + /** + * @brief Flag + * + * @see @ref Flags, @ref flags(), @ref Configuration::setFlags() + */ + enum class Flag: UnsignedShort { + /** + * Multiply the color with a vertex color. Requires either the + * @ref Color3 or @ref Color4 attribute to be present. + */ + VertexColor = 1 << 0, + + /** + * Enable object ID output. See @ref Shaders-LineGL-object-id for + * more information. + */ + ObjectId = 1 << 1, + + /** + * 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() or + * @ref LineDrawUniform::objectId. Implicitly enables + * @ref Flag::ObjectId. See @ref Shaders-LineGL-object-id for more + * information. + */ + InstancedObjectId = (1 << 2)|ObjectId, + + /** + * Instanced transformation. Retrieves a per-instance + * transformation matrix from the @ref TransformationMatrix + * attribute and uses it together with the matrix coming from + * @ref setTransformationProjectionMatrix() or + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix + * / @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * (first the per-instance, then the uniform matrix). See + * @ref Shaders-LineGL-instancing for more information. + * @requires_gl33 Extension @gl_extension{ARB,instanced_arrays} + */ + InstancedTransformation = 1 << 3, + + /** + * 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} + */ + UniformBuffers = 1 << 4, + + /** + * Enable multidraw functionality. Implies @ref Flag::UniformBuffers + * and adds the value from @ref setDrawOffset() with the + * @glsl gl_DrawID @ce builtin, which makes draws submitted via + * @ref GL::AbstractShaderProgram::draw(const Containers::Iterable&) + * and related APIs pick up per-draw parameters directly, without + * having to rebind the uniform buffers or specify + * @ref setDrawOffset() before each draw. In a non-multidraw + * scenario, @glsl gl_DrawID @ce is @cpp 0 @ce, which means a + * shader with this flag enabled can be used for regular draws as + * well. + * @requires_gl46 Extension @gl_extension{ARB,uniform_buffer_object} + * and @gl_extension{ARB,shader_draw_parameters} + * @requires_es_extension OpenGL ES 3.0 and extension + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * (unlisted). While the extension alone needs only OpenGL ES + * 2.0, the shader implementation relies on uniform buffers, + * which require OpenGL ES 3.0. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{ANGLE,multi_draw}. While the extension + * alone needs only WebGL 1.0, the shader implementation + * relies on uniform buffers, which require WebGL 2.0. + */ + MultiDraw = UniformBuffers|(1 << 5) + }; + + /** + * @brief Flags + * + * @see @ref flags(), @ref Configuration::setFlags() + */ + 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::LineGLFlag Flag; + typedef Implementation::LineGLFlags Flags; + #endif + + /** + * @brief Compile asynchronously + * + * Compared to @ref LineGL(const Configuration&) can perform an + * asynchronous compilation and linking. See @ref shaders-async for + * more information. + * @see @ref LineGL(CompileState&&) + */ + /* Compared to the non-templated shaders like PhongGL or + MeshVisualizerGL2D using a forward declaration is fine here. Huh. */ + static CompileState compile(const Configuration& configuration = Configuration{}); + + /** @brief Constructor */ + /* Compared to the non-templated shaders like PhongGL or + MeshVisualizerGL2D using a forward declaration is fine here. Huh. */ + explicit LineGL(const Configuration& configuration = Configuration{}); + + /** + * @brief Finalize an asynchronous compilation + * + * Takes an asynchronous compilation state returned by @ref compile() + * and forms a ready-to-use shader object. See @ref shaders-async for + * more information. + */ + explicit LineGL(CompileState&& state); + + /** + * @brief Construct without creating the underlying OpenGL object + * + * The constructed instance is equivalent to a moved-from state. Useful + * in cases where you will overwrite the instance later anyway. Move + * another object over it to make it useful. + * + * This function can be safely used for constructing (and later + * destructing) objects even without any OpenGL context being active. + * However note that this is a low-level and a potentially dangerous + * API, see the documentation of @ref NoCreate for alternatives. + */ + explicit LineGL(NoCreateT) noexcept: GL::AbstractShaderProgram{NoCreate} {} + + /** @brief Copying is not allowed */ + LineGL(const LineGL&) = delete; + + /** @brief Move constructor */ + LineGL(LineGL&&) noexcept = default; + + /** @brief Copying is not allowed */ + LineGL& operator=(const LineGL&) = delete; + + /** @brief Move assignment */ + LineGL& operator=(LineGL&&) noexcept = default; + + /** + * @brief Flags + * + * @see @ref Configuration::setFlags() + */ + Flags flags() const { return _flags; } + + /** + * @brief Cap style + * + * @see @ref Configuration::setCapStyle() + */ + LineCapStyle capStyle() const { return _capStyle; } + + /** + * @brief Join style + * + * @see @ref Configuration::setJoinStyle() + */ + LineJoinStyle joinStyle() const { return _joinStyle; } + + /** + * @brief Material count + * + * Statically defined size of the @ref LineMaterialUniform uniform + * buffer bound with @ref bindMaterialBuffer(). Has use only if + * @ref Flag::UniformBuffers is set. + * @see @ref Configuration::setMaterialCount() + */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Draw count + * + * Statically defined size of each of the + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D and @ref LineDrawUniform + * uniform buffers bound with @ref bindTransformationProjectionBuffer() + * and @ref bindDrawBuffer(). Has use only if @ref Flag::UniformBuffers + * is set. + * @see @ref Configuration::setDrawCount() + */ + UnsignedInt drawCount() const { return _drawCount; } + + /** + * @brief Set viewport size + * @return Reference to self (for method chaining) + * + * Line width and smoothness set in either @ref setWidth() / + * @ref setSmoothness() or @ref LineMaterialUniform::width / + * @ref LineMaterialUniform::smoothness depends on this value --- i.e., + * a value of @cpp 1.0f @ce is one pixel only if @ref setViewportSize() + * is called with the actual pixel size of the viewport. Initial value + * is a zero vector. + */ + LineGL& setViewportSize(const Vector2& size); + + /** @{ + * @name Uniform setters + * + * Used only if @ref Flag::UniformBuffers is not set. + */ + + /** + * @brief Set transformation and projection matrix + * @return Reference to self (for method chaining) + * + * Initial value is an identity matrix. If + * @ref Flag::InstancedTransformation is set, the per-instance + * transformation matrix coming from the @ref TransformationMatrix + * attribute is applied first, before this one. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref TransformationProjectionUniform2D::transformationProjectionMatrix / + * @ref TransformationProjectionUniform3D::transformationProjectionMatrix + * and call @ref bindTransformationProjectionBuffer() instead. + */ + LineGL& setTransformationProjectionMatrix(const MatrixTypeFor& matrix); + + /** + * @brief Set background color + * @return Reference to self (for method chaining) + * + * Initial value is @cpp 0x00000000_rgbaf @ce. Used for edge smoothing + * if smoothness is non-zero, and for background areas if + * @ref LineCapStyle::Round or @ref LineCapStyle::Triangle is used. If + * smoothness is zero and @ref LineCapStyle::Butt or + * @ref LineCapStyle::Square is used, only the foreground color is + * used. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref LineMaterialUniform::backgroundColor and call + * @ref bindMaterialBuffer() instead. + * @see @ref setColor(), @ref setSmoothness(), + * @ref Configuration::setCapStyle() + */ + LineGL& setBackgroundColor(const Magnum::Color4& color); + + /** + * @brief Set color + * @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 LineMaterialUniform::color and call @ref bindMaterialBuffer() + * instead. + * @see @ref setBackgroundColor() + */ + LineGL& setColor(const Magnum::Color4& color); + + /** + * @brief Set line width + * @return Reference to self (for method chaining) + * + * Screen-space, interpreted depending on the viewport size --- i.e., + * a value of @cpp 1.0f @ce is one pixel only if @ref setViewportSize() + * is called with the actual pixel size of the viewport. Initial value + * is @cpp 1.0f @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref LineMaterialUniform::width and call @ref bindMaterialBuffer() + * instead. + */ + LineGL& setWidth(Float width); + + /** + * @brief Set line smoothness + * @return Reference to self (for method chaining) + * + * Larger values will make edges look less aliased (but blurry), + * smaller values will make them more crisp (but possibly aliased). + * Screen-space, interpreted depending on the viewport size --- i.e., + * a value of @cpp 1.0f @ce is one pixel only if @ref setViewportSize() + * is called with the actual pixel size of the viewport. Initial value + * is @cpp 0.0f @ce. + * + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref LineMaterialUniform::smoothness and call @ref bindMaterialBuffer() + * instead. + */ + LineGL& setSmoothness(Float smoothness); + + /** + * @brief Set miter length limit + * @return Reference to self (for method chaining) + * + * Maximum length (relative to line width) over which a + * @ref LineJoinStyle::Miter join is converted to a + * @ref LineJoinStyle::Bevel in order to avoid sharp corners extending + * too much. Default value is @cpp 4.0f @ce, which corresponds to + * approximately 29 degrees. Alternatively you can set the limit as an + * angle using @ref setMiterAngleLimit(). Miter length is calculated + * using the following formula, where @f$ w @f$ is line half-width, + * @f$ l @f$ is miter length and @f$ \theta @f$ is angle between two + * line segments: @f[ + * \frac{w}{l} = \sin(\frac{\theta}{2}) + * @f] + * + * Expects that @ref joinStyle() is @ref LineJoinStyle::Miter and + * @p limit is greater than or equal to @cpp 1.0f @ce and finite. + * Expects that @ref Flag::UniformBuffers is not set, in that case fill + * @ref LineMaterialUniform::miterLimit using + * @ref LineMaterialUniform::setMiterLengthLimit() and call + * @ref bindMaterialBuffer() instead. + */ + LineGL& setMiterLengthLimit(Float limit); + + /** + * @brief Set miter angle limit + * @return Reference to self (for method chaining) + * + * Like @ref setMiterLengthLimit(), but specified as a minimum angle + * between two line segments below which a @ref LineJoinStyle::Miter + * join is converted to a @ref LineJoinStyle::Bevel in order to avoid + * sharp corners extending too much. Default value is approximately + * @cpp 28.955_degf @ce, see @ref setMiterLengthLimit() above for more + * information. + * + * Expects that @ref joinStyle() is @ref LineJoinStyle::Miter and + * @p limit is greater than @cpp 0.0_radf @ce. Expects that + * @ref Flag::UniformBuffers is not set, in that case fill + * @ref LineMaterialUniform::miterLimit using + * @ref LineMaterialUniform::setMiterAngleLimit() and call + * @ref bindMaterialBuffer() instead. + */ + LineGL& setMiterAngleLimit(Rad limit); + + /** + * @brief Set object ID + * @return Reference to self (for method chaining) + * + * Expects that the shader was created with @ref Flag::ObjectId + * enabled. Value set here is written to the @ref ObjectIdOutput, see + * @ref Shaders-LineGL-object-id for more information. Initial value + * 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 LineDrawUniform::objectId and call @ref bindDrawBuffer() + * instead. + */ + LineGL& setObjectId(UnsignedInt id); + + /** + * @} + */ + + /** @{ + * @name Uniform buffer binding and related uniform setters + * + * Used if @ref Flag::UniformBuffers is set. + */ + + /** + * @brief Bind a draw offset + * @return Reference to self (for method chaining) + * + * Specifies which item in the @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D and @ref LineDrawUniform + * 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, if @ref drawCount() + * is @cpp 1 @ce, the function is a no-op as the shader assumes draw + * offset to be always zero. + * + * If @ref Flag::MultiDraw is set, @glsl gl_DrawID @ce is added to this + * value, which makes each draw submitted via + * @ref GL::AbstractShaderProgram::draw(const Containers::Iterable&) + * pick up its own per-draw parameters. + * @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. + */ + LineGL& setDrawOffset(UnsignedInt offset); + + /** + * @brief Bind a transformation and projection uniform buffer + * @return Reference to self (for method chaining) + * + * 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} + */ + LineGL& bindTransformationProjectionBuffer(GL::Buffer& buffer); + LineGL& bindTransformationProjectionBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); /**< @overload */ + + /** + * @brief Bind a draw uniform buffer + * @return Reference to self (for method chaining) + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref drawCount() instances of + * @ref LineDrawUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer() and + * @ref bindMaterialBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + */ + LineGL& bindDrawBuffer(GL::Buffer& buffer); + LineGL& bindDrawBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); /**< @overload */ + + /** + * @brief Bind a material uniform buffer + * @return Reference to self (for method chaining) + * + * Expects that @ref Flag::UniformBuffers is set. The buffer is + * expected to contain @ref materialCount() instances of + * @ref LineMaterialUniform. At the very least you need to call also + * @ref bindTransformationProjectionBuffer() and @ref bindDrawBuffer(). + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + */ + LineGL& bindMaterialBuffer(GL::Buffer& buffer); + LineGL& bindMaterialBuffer(GL::Buffer& buffer, GLintptr offset, GLsizeiptr size); /**< @overload */ + + /** + * @} + */ + + MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION(LineGL) + + private: + /* Creates the GL shader program object but does nothing else. + Internal, used by compile(). */ + explicit LineGL(NoInitT); + + Flags _flags; + LineCapStyle _capStyle; + LineJoinStyle _joinStyle; + UnsignedInt _materialCount{}, + _drawCount{}; + Int _viewportSizeUniform{0}, + _transformationProjectionMatrixUniform{1}, + _backgroundColorUniform{2}, + _colorUniform{3}, + _widthUniform{4}, + _smoothnessUniform{5}, + _miterLimitUniform{6}, + _objectIdUniform{7}, + /* Used instead of all other uniforms except viewportSize when + Flag::UniformBuffers is set, so it can alias them */ + _drawOffsetUniform{1}; +}; + +/** +@brief Configuration + +@see @ref LineGL(const Configuration&), @ref compile(const Configuration&) +*/ +template class MAGNUM_SHADERS_EXPORT LineGL::Configuration { + public: + explicit Configuration(); + + /** @brief Flags */ + Flags flags() const { return _flags; } + + /** + * @brief Set flags + * + * No flags are set by default. + * @see @ref LineGL::flags() + */ + Configuration& setFlags(Flags flags) { + _flags = flags; + return *this; + } + + /** @brief Cap style */ + LineCapStyle capStyle() const { return _capStyle; } + + /** + * @brief Set cap style + * + * Unlike for example the SVG specification that uses + * @ref LineCapStyle::Butt by default, the default value is + * @ref LineCapStyle::Square, in order to make zero-length lines visible. + * @see @ref LineGL::capStyle() + */ + Configuration& setCapStyle(LineCapStyle style) { + _capStyle = style; + return *this; + } + + /** @brief Join style */ + LineJoinStyle joinStyle() const { return _joinStyle; } + + /** + * @brief Set join style + * + * Default value is @ref LineJoinStyle::Miter, consistently with the + * SVG specification. + * @see @ref LineGL::joinStyle() + */ + Configuration& setJoinStyle(LineJoinStyle style) { + _joinStyle = style; + return *this; + } + + /** @brief Material count */ + UnsignedInt materialCount() const { return _materialCount; } + + /** + * @brief Set material count + * + * If @ref Flag::UniformBuffers is set, describes size of a + * @ref LineMaterialUniform buffer bound with + * @ref bindMaterialBuffer(). Uniform buffers have a statically defined + * size and @cpp count*sizeof(LineMaterialUniform) @ce has to be within + * @ref GL::AbstractShaderProgram::maxUniformBlockSize(). + * + * The per-draw materials are then specified via + * @ref LineDrawUniform::materialId. Default value is @cpp 1 @ce. + * + * If @ref Flag::UniformBuffers isn't set, this value is ignored. + * @see @ref setFlags(), @ref setDrawCount(), + * @ref LineGL::materialCount() + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + */ + Configuration& setMaterialCount(UnsignedInt count) { + _materialCount = count; + return *this; + } + + /** @brief Draw count */ + UnsignedInt drawCount() const { return _drawCount; } + + /** + * @brief Set draw count + * + * If @ref Flag::UniformBuffers is set, describes size of a + * @ref TransformationProjectionUniform2D / + * @ref TransformationProjectionUniform3D / + * @ref LineDrawUniform buffer bound with + * @ref bindTransformationProjectionBuffer() and @ref bindDrawBuffer(). + * Uniform buffers have a statically defined size and the maximum of + * @cpp count*sizeof(TransformationProjectionUniform2D) @ce / + * @cpp count*sizeof(TransformationProjectionUniform3D) @ce and + * @cpp count*sizeof(LineDrawUniform) @ce has to be within + * @ref GL::AbstractShaderProgram::maxUniformBlockSize(). + * + * The draw offset is then set via @ref setDrawOffset(). Default value + * is @cpp 1 @ce. + * + * If @ref Flag::UniformBuffers isn't set, this value is ignored. + * @see @ref setFlags(), @ref setMaterialCount(), + * @ref LineGL::drawCount() + * @requires_gl31 Extension @gl_extension{ARB,uniform_buffer_object} + */ + Configuration& setDrawCount(UnsignedInt count) { + _drawCount = count; + return *this; + } + + private: + Flags _flags; + LineCapStyle _capStyle; + LineJoinStyle _joinStyle; + UnsignedInt _materialCount{1}; + UnsignedInt _drawCount{1}; +}; + +/** +@brief Asynchronous compilation state + +Returned by @ref compile(). See @ref shaders-async for more information. +*/ +template class LineGL::CompileState: public LineGL { + /* Everything deliberately private except for the inheritance */ + friend class LineGL; + + explicit CompileState(NoCreateT): LineGL{NoCreate}, _vert{NoCreate}, _frag{NoCreate} {} + + explicit CompileState(LineGL&& shader, GL::Shader&& vert, GL::Shader&& frag + #ifndef MAGNUM_TARGET_GLES + , GL::Version version + #endif + ): LineGL{Utility::move(shader)}, _vert{Utility::move(vert)}, _frag{Utility::move(frag)} + #ifndef MAGNUM_TARGET_GLES + , _version{version} + #endif + {} + + Implementation::GLShaderWrapper _vert, _frag; + #ifndef MAGNUM_TARGET_GLES + GL::Version _version; + #endif +}; + +/** +@brief 2D line OpenGL shader +@m_since_latest +*/ +typedef LineGL<2> LineGL2D; + +/** +@brief 3D LineGL OpenGL shader +@m_since_latest +*/ +typedef LineGL<3> LineGL3D; + +#ifdef DOXYGEN_GENERATING_OUTPUT +/** + * @debugoperatorclassenum{LineGL,LineGL::Flag} + * @m_since_latest + */ +template Debug& operator<<(Debug& debug, LineGL::Flag value); + +/** + * @debugoperatorclassenum{LineGL,LineGL::Flags} + * @m_since_latest + */ +template Debug& operator<<(Debug& debug, LineGL::Flags value); +#else +namespace Implementation { + MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, LineGLFlag value); + MAGNUM_SHADERS_EXPORT Debug& operator<<(Debug& debug, LineGLFlags value); +} +#endif + +}} +#else +#error this header is not available in the OpenGL ES 2.0 / WebGL 1.0 build +#endif + +#endif diff --git a/src/Magnum/Shaders/Shaders.h b/src/Magnum/Shaders/Shaders.h index 53441b7dc..c56d576cb 100644 --- a/src/Magnum/Shaders/Shaders.h +++ b/src/Magnum/Shaders/Shaders.h @@ -58,6 +58,15 @@ typedef CORRADE_DEPRECATED("use FlatGL3D instead") FlatGL3D Flat3D; /* Generic is used only statically */ +#ifndef MAGNUM_TARGET_GLES2 +enum class LineCapStyle: UnsignedByte; +enum class LineJoinStyle: UnsignedByte; + +template class LineGL; +typedef LineGL<2> LineGL2D; +typedef LineGL<3> LineGL3D; +#endif + class MeshVisualizerGL2D; class MeshVisualizerGL3D; #ifdef MAGNUM_BUILD_DEPRECATED diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index f617ccd7a..fbeceff2b 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -45,6 +45,13 @@ corrade_add_test(ShadersPhongGL_Test PhongGL_Test.cpp LIBRARIES MagnumShadersTes corrade_add_test(ShadersVectorGL_Test VectorGL_Test.cpp LIBRARIES MagnumShaders) corrade_add_test(ShadersVertexColorGL_Test VertexColorGL_Test.cpp LIBRARIES MagnumShaders) +if(NOT MAGNUM_TARGET_GLES2) + corrade_add_test(ShadersLineTest LineTest.cpp LIBRARIES MagnumShadersTestLib) + target_compile_definitions(ShadersLineTest PRIVATE "CORRADE_GRACEFUL_ASSERT") + + corrade_add_test(ShadersLineGL_Test LineGL_Test.cpp LIBRARIES MagnumShaders) +endif() + if(MAGNUM_BUILD_GL_TESTS) # Otherwise CMake complains that Corrade::PluginManager is not found, wtf find_package(Corrade REQUIRED PluginManager) @@ -455,4 +462,62 @@ if(MAGNUM_BUILD_GL_TESTS) VertexColorTestFiles PROPERTIES MACOSX_PACKAGE_LOCATION Resources) endif() + + if(NOT MAGNUM_TARGET_GLES2) + set(ShadersLineGLTest_SRCS LineGLTest.cpp) + if(CORRADE_TARGET_IOS) + list(APPEND ShadersLineGLTest_SRCS TestFiles LineTestFiles) + endif() + corrade_add_test(ShadersLineGLTest ${ShadersLineGLTest_SRCS} + LIBRARIES + MagnumDebugTools + MagnumMeshTools + MagnumPrimitives + MagnumShadersTestLib + MagnumOpenGLTester + FILES + LineTestFiles/caps-butt-joins-bevel.tga + LineTestFiles/caps-butt-joins-miter-flat.tga + LineTestFiles/caps-round-joins-miter.tga + LineTestFiles/caps-square-joins-bevel.tga + LineTestFiles/caps-square-joins-miter-flat.tga + LineTestFiles/caps-square-joins-miter-limit-36.tga + LineTestFiles/caps-square-joins-miter-limit-70deg.tga + LineTestFiles/caps-square-joins-miter-limit-91deg.tga + LineTestFiles/caps-square-joins-miter.tga + LineTestFiles/caps-triangle-joins-bevel.tga + LineTestFiles/cube3D-caps-butt-joins-bevel.tga + LineTestFiles/cube3D-caps-square-joins-miter.tga + LineTestFiles/cube3D-depth.tga + LineTestFiles/cube3D-flat-single-pixel.tga + LineTestFiles/defaults.tga + LineTestFiles/instanced.tga + LineTestFiles/multidraw.tga + LineTestFiles/perspective3D.tga + LineTestFiles/vertex-color.tga) + target_include_directories(ShadersLineGLTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) + if(MAGNUM_BUILD_PLUGINS_STATIC) + if(MAGNUM_WITH_ANYIMAGEIMPORTER) + target_link_libraries(ShadersLineGLTest PRIVATE AnyImageImporter) + endif() + if(MAGNUM_WITH_TGAIMPORTER) + target_link_libraries(ShadersLineGLTest PRIVATE TgaImporter) + endif() + else() + # So the plugins get properly built when building the test + if(MAGNUM_WITH_ANYIMAGEIMPORTER) + add_dependencies(ShadersLineGLTest AnyImageImporter) + endif() + if(MAGNUM_WITH_TGAIMPORTER) + add_dependencies(ShadersLineGLTest TgaImporter) + endif() + endif() + + if(CORRADE_TARGET_IOS) + set_source_files_properties( + LineTestFiles + PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + endif() + endif() endif() + diff --git a/src/Magnum/Shaders/Test/GenericGL_Test.cpp b/src/Magnum/Shaders/Test/GenericGL_Test.cpp index cb99b8652..3e1fe38fa 100644 --- a/src/Magnum/Shaders/Test/GenericGL_Test.cpp +++ b/src/Magnum/Shaders/Test/GenericGL_Test.cpp @@ -89,6 +89,8 @@ void GenericGL_Test::glslMatch() { CORRADE_COMPARE(TEXTURE_OFFSET_ATTRIBUTE_LOCATION, GenericGL2D::TextureOffset::Location); CORRADE_COMPARE(TEXTURE_OFFSET_ATTRIBUTE_LOCATION, GenericGL3D::TextureOffset::Location); + + /* Line-specific attributes tested in LineGL_Test instead */ } void GenericGL_Test::glslMatchOutput() { diff --git a/src/Magnum/Shaders/Test/LineGLTest.cpp b/src/Magnum/Shaders/Test/LineGLTest.cpp new file mode 100644 index 000000000..594caeeb4 --- /dev/null +++ b/src/Magnum/Shaders/Test/LineGLTest.cpp @@ -0,0 +1,2760 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Magnum/Image.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/DebugTools/CompareImage.h" +#include "Magnum/GL/Buffer.h" +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/Framebuffer.h" +#include "Magnum/GL/Mesh.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/GL/OpenGLTester.h" +#include "Magnum/GL/Renderbuffer.h" +#include "Magnum/GL/RenderbufferFormat.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Math/Complex.h" +#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/MeshTools/Compile.h" +#include "Magnum/Primitives/Cube.h" +#include "Magnum/Shaders/PhongGL.h" /* for testing correct depth in 3D */ +#include "Magnum/Shaders/Generic.h" +#include "Magnum/Shaders/Line.h" +#include "Magnum/Shaders/LineGL.h" +#include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MeshData.h" + +#include "configure.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct LineGLTest: GL::OpenGLTester { + explicit LineGLTest(); + + template void construct(); + template void constructAsync(); + template void constructUniformBuffers(); + template void constructUniformBuffersAsync(); + + template void constructMove(); + template void constructMoveUniformBuffers(); + + /* No constructInvalid() as there isn't any assertion in the constructor + that wouldn't be related to UBOs */ + template void constructUniformBuffersInvalid(); + + template void setUniformUniformBuffersEnabled(); + template void bindBufferUniformBuffersNotEnabled(); + template void setMiterLengthLimitInvalid(); + template void setMiterAngleLimitInvalid(); + template void setObjectIdNotEnabled(); + template void setWrongDrawOffset(); + + void renderSetupLarge(); + void renderSetupSmall(); + void renderTeardown(); + + template void renderDefaults2D(); + template void renderDefaults3D(); + + template void renderLineCapsJoins2D(); + /* These test the shader algorithms, which are independent of UBOs */ + void renderLineCapsJoins2DReversed(); + void renderLineCapsJoins2DTransformed(); + + template void renderCube3D(); + /* This test the shader algorithms, which are independent of UBOs */ + void renderPerspective3D(); + + template void renderVertexColor2D(); + template void renderVertexColor3D(); + + template void renderObjectId2D(); + template void renderObjectId3D(); + + template void renderInstanced2D(); + template void renderInstanced3D(); + + void renderMulti2D(); + void renderMulti3D(); + + private: + PluginManager::Manager _manager{"nonexistent"}; + + GL::Renderbuffer _color{NoCreate}; + GL::Renderbuffer _depth{NoCreate}; + GL::Renderbuffer _objectId{NoCreate}; + GL::Framebuffer _framebuffer{NoCreate}; +}; + +using namespace Containers::Literals; +using namespace Math::Literals; + +const struct { + const char* name; + LineGL2D::Flags flags; + Containers::Optional capStyle; + Containers::Optional joinStyle; +} ConstructData[]{ + {"", {}, {}, {}}, + {"square caps, bevel joins", {}, LineCapStyle::Square, LineJoinStyle::Bevel}, + {"round caps, miter joins", {}, LineCapStyle::Round, LineJoinStyle::Miter}, + /** @todo use JoinStyle::MiterClip once it exists */ + {"butt caps, miter joins", {}, LineCapStyle::Butt, LineJoinStyle::Miter}, + /** @todo use JoinStyle::Round once it exists */ + {"triangle caps, miter joins", {}, LineCapStyle::Triangle, LineJoinStyle::Miter}, + {"vertex colors", LineGL2D::Flag::VertexColor, {}, {}}, + {"object ID", LineGL2D::Flag::ObjectId, {}, {}}, + {"instanced object ID", LineGL2D::Flag::InstancedObjectId, {}, {}}, + {"instanced transformation", LineGL2D::Flag::InstancedTransformation, {}, {}}, +}; + +const struct { + const char* name; + LineGL2D::Flags flags; + Containers::Optional capStyle; + Containers::Optional joinStyle; + UnsignedInt materialCount, drawCount; +} ConstructUniformBuffersData[]{ + {"classic fallback", {}, {}, {}, 1, 1}, + {"", LineGL2D::Flag::UniformBuffers, {}, {}, 1, 1}, + /* Just to verify that access to the miter limits is properly guarded, + no need to check all variants */ + {"round caps, miter joins", LineGL2D::Flag::UniformBuffers, LineCapStyle::Round, LineJoinStyle::Miter, 1, 1}, + {"butt caps, bevel joins", LineGL2D::Flag::UniformBuffers, LineCapStyle::Butt, LineJoinStyle::Bevel, 1, 1}, + /* SwiftShader has 256 uniform vectors at most, per-draw is 4+1 in 3D case + and 3+1 in 2D, per-material 3 */ + {"multiple materials, draws", LineGL2D::Flag::UniformBuffers, {}, {}, 16, 41}, + {"object ID", LineGL2D::Flag::UniformBuffers|LineGL2D::Flag::ObjectId, {}, {}, 1, 1}, + {"instanced object ID", LineGL2D::Flag::UniformBuffers|LineGL2D::Flag::InstancedObjectId, {}, {}, 1, 1}, + {"multidraw with all the things", LineGL2D::Flag::MultiDraw|LineGL2D::Flag::ObjectId|LineGL2D::Flag::InstancedTransformation|LineGL2D::Flag::InstancedObjectId, {}, {}, 16, 41} +}; + +const struct { + const char* name; + LineGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + const char* message; +} ConstructUniformBuffersInvalidData[]{ + {"zero draws", LineGL2D::Flag::UniformBuffers, 1, 0, + "draw count can't be zero"}, + {"zero materials", LineGL2D::Flag::UniformBuffers, 0, 1, + "material count can't be zero"} +}; + +const struct { + const char* name; + LineJoinStyle joinStyle; + Float limit; + const char* message; +} SetMiterLengthLimitInvalidData[]{ + {"wrong join style", LineJoinStyle::Bevel, 1.0f, + "the shader was created with Shaders::LineJoinStyle::Bevel"}, + {"too short", LineJoinStyle::Miter, 0.9997f, + "expected a finite value greater than or equal to 1, got 0.9997"}, + {"too long", LineJoinStyle::Miter, Constants::inf(), + "expected a finite value greater than or equal to 1, got inf"}, +}; + +const struct { + const char* name; + LineJoinStyle joinStyle; + Rad limit; + const char* message; +} SetMiterAngleLimitInvalidData[]{ + {"wrong join style", LineJoinStyle::Bevel, 90.0_degf, + "the shader was created with Shaders::LineJoinStyle::Bevel"}, + {"too small", LineJoinStyle::Miter, 0.0_degf, + "expected a value greater than 0° and less than or equal to 180°, got 0°"}, + {"too large", LineJoinStyle::Miter, 180.1_degf, + "expected a value greater than 0° and less than or equal to 180°, got 180.1°"} +}; + +const struct { + const char* name; + Float width; + Float smoothness; + Containers::Optional miterLengthLimit; + Containers::Optional miterAngleLimit; + Containers::Optional capStyle; + Containers::Optional joinStyle; + const char* expected; +} RenderLineCapsJoins2DData[]{ + {"caps & joints default, flat", + 16.0f, 0.0f, {}, {}, {}, {}, + "caps-square-joins-miter-flat.tga"}, + {"caps butt, joins default, flat", + 16.0f, 0.0f, {}, {}, + LineCapStyle::Butt, {}, + "caps-butt-joins-miter-flat.tga"}, + {"caps butt, joins bevel", /** @todo bevel smoothing is off for 30° */ + 16.0f, 1.0f, {}, {}, + LineCapStyle::Butt, LineJoinStyle::Bevel, + "caps-butt-joins-bevel.tga"}, + {"caps square, joins miter", + 16.0f, 1.0f, {}, {}, + LineCapStyle::Square, LineJoinStyle::Miter, + "caps-square-joins-miter.tga"}, + {"caps square, joins bevel", /** @todo bevel smoothing is off for 30° */ + 16.0f, 1.0f, {}, {}, + LineCapStyle::Square, LineJoinStyle::Bevel, + "caps-square-joins-bevel.tga"}, + {"caps square, joins miter, limit 3.95", + 16.0f, 1.0f, 3.95f, {}, + LineCapStyle::Square, LineJoinStyle::Miter, + /* Same as default */ + "caps-square-joins-miter.tga"}, + {"caps square, joins miter, limit 3.6", + 16.0f, 1.0f, 3.6f, {}, /** @todo 3.85 should work but it doesn't */ + LineCapStyle::Square, LineJoinStyle::Miter, + /* The 30° join should get a bevel here */ + "caps-square-joins-miter-limit-36.tga"}, + {"caps square, joins miter, limit 59°", + 16.0f, 1.0f, {}, 59.0_degf, + LineCapStyle::Square, LineJoinStyle::Miter, + /* Same as limit 3.6, the 30° join gets a bevel */ + "caps-square-joins-miter-limit-36.tga"}, + {"caps square, joins miter, limit 70°", + 16.0f, 1.0f, {}, 70.0_degf, /** @todo 61° should work but it doesn't */ + LineCapStyle::Square, LineJoinStyle::Miter, + /* The 30° and 60° join should get a bevel here, 90° and 120° should + stay */ + "caps-square-joins-miter-limit-70deg.tga"}, + {"caps square, joins miter, limit 89°", + 16.0f, 1.0f, {}, 89.0_degf, + LineCapStyle::Square, LineJoinStyle::Miter, + /* Same as limit 61°, the 30° and 60° joins get a bevel, 90° and 120° + not */ + "caps-square-joins-miter-limit-70deg.tga"}, + {"caps square, joins miter, limit 91°", + 16.0f, 1.0f, {}, 91.0_degf, + LineCapStyle::Square, LineJoinStyle::Miter, + /* The 30°, 60° and 90° join should get a bevel here, 120° should + stay */ + "caps-square-joins-miter-limit-91deg.tga"}, + {"caps round, joins miter", + /** @todo use round joins instead once implemented */ + 16.0f, 1.0f, {}, {}, + LineCapStyle::Round, LineJoinStyle::Miter, + "caps-round-joins-miter.tga"}, + {"caps triangle, joins bevel", + 16.0f, 1.0f, {}, {}, + LineCapStyle::Triangle, LineJoinStyle::Bevel, + "caps-triangle-joins-bevel.tga"}, +}; + +const struct { + const char* name; + Float width; + Float smoothness; + Containers::Optional miterLengthLimit; + Containers::Optional capStyle; + Containers::Optional joinStyle; + bool renderSolidCube; + const char* expected; +} RenderCube3DData[]{ + {"caps & joins default, flat, single-pixel", + 1.0f, 0.0f, 8.0f, {}, {}, false, + "cube3D-flat-single-pixel.tga"}, + {"caps square, joins miter", + 10.0f, 1.0f, 8.0f, {}, {}, false, + "cube3D-caps-square-joins-miter.tga"}, + {"caps butt, joins bevel", /** @todo bevel smoothing is off */ + 10.0f, 1.0f, {}, LineCapStyle::Butt, LineJoinStyle::Bevel, false, + "cube3D-caps-butt-joins-bevel.tga"}, + {"depth", /** @todo depth is imprecise for wide lines */ + /* Not smooth, as the cut-off pieces are jaggy anyway */ + 10.0f, 0.0f, 8.0f, {}, {}, true, + "cube3D-depth.tga"}, +}; + +const struct { + const char* name; + Float width; + Float smoothness; + LineCapStyle capStyle; +} RenderObjectIdData[]{ + {"flat, square caps", 10.0f, 0.0f, LineCapStyle::Square}, + {"flat, round caps", 10.0f, 0.0f, LineCapStyle::Round}, + {"smooth, square caps", 4.0f, 3.0f, LineCapStyle::Square}, +}; + +const struct { + const char* name; + UnsignedInt expectedId[3]; + LineGL2D::Flags flags; +} RenderInstancedData[]{ + {"", + {}, {}}, + {"object ID", + {1000, 1000, 1000}, LineGL2D::Flag::ObjectId}, + {"instanced object ID", + {1211, 5627, 36363}, LineGL2D::Flag::InstancedObjectId}, +}; + +const struct { + const char* name; + UnsignedInt expectedId[3]; + LineGL2D::Flags flags; + UnsignedInt materialCount, drawCount; + UnsignedInt uniformIncrement; +} RenderMultiData[]{ + {"bind with offset", + {}, {}, 1, 1, 16}, + {"bind with offset, object ID", + {1211, 5627, 36363}, LineGL2D::Flag::ObjectId, 1, 1, 16}, + {"draw offset", + {}, {}, 2, 3, 1}, + {"draw offset, object ID", + {1211, 5627, 36363}, LineGL2D::Flag::ObjectId, 2, 3, 1}, + {"multidraw", + {}, LineGL2D::Flag::MultiDraw, 2, 3, 1}, + {"multidraw, object ID", + {1211, 5627, 36363}, LineGL2D::Flag::MultiDraw|LineGL2D::Flag::ObjectId, 2, 3, 1}, +}; + +LineGLTest::LineGLTest() { + addInstancedTests({ + &LineGLTest::construct<2>, + &LineGLTest::construct<3>}, + Containers::arraySize(ConstructData)); + + addTests({ + &LineGLTest::constructAsync<2>, + &LineGLTest::constructAsync<3>}); + + addInstancedTests({ + &LineGLTest::constructUniformBuffers<2>, + &LineGLTest::constructUniformBuffers<3>}, + Containers::arraySize(ConstructUniformBuffersData)); + + addTests({ + &LineGLTest::constructUniformBuffersAsync<2>, + &LineGLTest::constructUniformBuffersAsync<3>, + + &LineGLTest::constructMove<2>, + &LineGLTest::constructMove<3>, + + &LineGLTest::constructMoveUniformBuffers<2>, + &LineGLTest::constructMoveUniformBuffers<3>}); + + addInstancedTests({ + &LineGLTest::constructUniformBuffersInvalid<2>, + &LineGLTest::constructUniformBuffersInvalid<3>}, + Containers::arraySize(ConstructUniformBuffersInvalidData)); + + addTests({ + &LineGLTest::setUniformUniformBuffersEnabled<2>, + &LineGLTest::setUniformUniformBuffersEnabled<3>, + &LineGLTest::bindBufferUniformBuffersNotEnabled<2>, + &LineGLTest::bindBufferUniformBuffersNotEnabled<3>}); + + addInstancedTests({ + &LineGLTest::setMiterLengthLimitInvalid<2>, + &LineGLTest::setMiterLengthLimitInvalid<3>}, + Containers::arraySize(SetMiterLengthLimitInvalidData)); + + addInstancedTests({ + &LineGLTest::setMiterAngleLimitInvalid<2>, + &LineGLTest::setMiterAngleLimitInvalid<3>}, + Containers::arraySize(SetMiterAngleLimitInvalidData)); + + addTests({ + &LineGLTest::setObjectIdNotEnabled<2>, + &LineGLTest::setObjectIdNotEnabled<3>, + &LineGLTest::setWrongDrawOffset<2>, + &LineGLTest::setWrongDrawOffset<3>}); + + /* MSVC needs explicit type due to default template args */ + addTests({ + &LineGLTest::renderDefaults2D, + &LineGLTest::renderDefaults2D, + &LineGLTest::renderDefaults3D, + &LineGLTest::renderDefaults3D}, + &LineGLTest::renderSetupSmall, + &LineGLTest::renderTeardown); + + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &LineGLTest::renderLineCapsJoins2D, + &LineGLTest::renderLineCapsJoins2D, + &LineGLTest::renderLineCapsJoins2DReversed, + &LineGLTest::renderLineCapsJoins2DTransformed}, + Containers::arraySize(RenderLineCapsJoins2DData), + &LineGLTest::renderSetupLarge, + &LineGLTest::renderTeardown); + + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &LineGLTest::renderCube3D, + &LineGLTest::renderCube3D}, + Containers::arraySize(RenderCube3DData), + &LineGLTest::renderSetupLarge, + &LineGLTest::renderTeardown); + + addTests({&LineGLTest::renderPerspective3D}, + &LineGLTest::renderSetupSmall, + &LineGLTest::renderTeardown); + + /* MSVC needs explicit type due to default template args */ + addTests({ + &LineGLTest::renderVertexColor2D, + &LineGLTest::renderVertexColor2D, + &LineGLTest::renderVertexColor2D, + &LineGLTest::renderVertexColor2D, + &LineGLTest::renderVertexColor3D, + &LineGLTest::renderVertexColor3D, + &LineGLTest::renderVertexColor3D, + &LineGLTest::renderVertexColor3D}, + &LineGLTest::renderSetupSmall, + &LineGLTest::renderTeardown); + + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &LineGLTest::renderObjectId2D, + &LineGLTest::renderObjectId2D, + &LineGLTest::renderObjectId3D, + &LineGLTest::renderObjectId3D}, + Containers::arraySize(RenderObjectIdData), + &LineGLTest::renderSetupSmall, + &LineGLTest::renderTeardown); + + /* MSVC needs explicit type due to default template args */ + addInstancedTests({ + &LineGLTest::renderInstanced2D, + &LineGLTest::renderInstanced2D, + &LineGLTest::renderInstanced3D, + &LineGLTest::renderInstanced3D}, + Containers::arraySize(RenderInstancedData), + &LineGLTest::renderSetupSmall, + &LineGLTest::renderTeardown); + + addInstancedTests({&LineGLTest::renderMulti2D, + &LineGLTest::renderMulti3D}, + Containers::arraySize(RenderMultiData), + &LineGLTest::renderSetupSmall, + &LineGLTest::renderTeardown); + + /* 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 + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(ANYIMAGEIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif + #ifdef TGAIMPORTER_PLUGIN_FILENAME + CORRADE_INTERNAL_ASSERT_OUTPUT(_manager.load(TGAIMPORTER_PLUGIN_FILENAME) & PluginManager::LoadState::Loaded); + #endif +} + +template void LineGLTest::construct() { + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + typename LineGL::Configuration configuration; + configuration.setFlags(data.flags); + if(data.capStyle) configuration.setCapStyle(*data.capStyle); + if(data.joinStyle) configuration.setJoinStyle(*data.joinStyle); + LineGL shader{configuration}; + CORRADE_COMPARE(shader.flags(), data.flags); + if(data.capStyle) CORRADE_COMPARE(shader.capStyle(), data.capStyle); + if(data.joinStyle) CORRADE_COMPARE(shader.joinStyle(), data.joinStyle); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first()); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +template void LineGLTest::constructAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + typename LineGL::CompileState state = LineGL::compile(typename LineGL::Configuration{} + .setFlags(LineGL2D::Flag::VertexColor) + .setCapStyle(LineCapStyle::Butt) + .setJoinStyle(LineJoinStyle::Bevel)); + CORRADE_COMPARE(state.flags(), LineGL2D::Flag::VertexColor); + CORRADE_COMPARE(state.capStyle(), LineCapStyle::Butt); + CORRADE_COMPARE(state.joinStyle(), LineJoinStyle::Bevel); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + LineGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), LineGL2D::Flag::VertexColor); + CORRADE_COMPARE(shader.capStyle(), LineCapStyle::Butt); + CORRADE_COMPARE(shader.joinStyle(), LineJoinStyle::Bevel); + + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first()); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +template void LineGLTest::constructUniformBuffers() { + auto&& data = ConstructUniformBuffersData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if((data.flags & LineGL2D::Flag::UniformBuffers) && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= LineGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + typename LineGL::Configuration configuration; + configuration + .setFlags(data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount); + if(data.capStyle) configuration.setCapStyle(*data.capStyle); + if(data.joinStyle) configuration.setJoinStyle(*data.joinStyle); + LineGL shader{configuration}; + CORRADE_COMPARE(shader.flags(), data.flags); + if(data.capStyle) CORRADE_COMPARE(shader.capStyle(), data.capStyle); + if(data.joinStyle) CORRADE_COMPARE(shader.joinStyle(), data.joinStyle); + CORRADE_COMPARE(shader.materialCount(), data.materialCount); + CORRADE_COMPARE(shader.drawCount(), data.drawCount); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first()); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +template void LineGLTest::constructUniformBuffersAsync() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + typename LineGL::CompileState state = LineGL::compile(typename LineGL::Configuration{} + .setFlags(LineGL2D::Flag::UniformBuffers|LineGL2D::Flag::VertexColor) + .setCapStyle(LineCapStyle::Butt) + .setJoinStyle(LineJoinStyle::Bevel) + .setMaterialCount(16) + .setDrawCount(41)); + CORRADE_COMPARE(state.flags(), LineGL2D::Flag::UniformBuffers|LineGL2D::Flag::VertexColor); + CORRADE_COMPARE(state.capStyle(), LineCapStyle::Butt); + CORRADE_COMPARE(state.joinStyle(), LineJoinStyle::Bevel); + CORRADE_COMPARE(state.materialCount(), 16); + CORRADE_COMPARE(state.drawCount(), 41); + + while(!state.isLinkFinished()) + Utility::System::sleep(100); + + LineGL shader{std::move(state)}; + CORRADE_COMPARE(shader.flags(), LineGL2D::Flag::UniformBuffers|LineGL2D::Flag::VertexColor); + CORRADE_COMPARE(shader.capStyle(), LineCapStyle::Butt); + CORRADE_COMPARE(shader.joinStyle(), LineJoinStyle::Bevel); + CORRADE_COMPARE(shader.materialCount(), 16); + CORRADE_COMPARE(shader.drawCount(), 41); + CORRADE_VERIFY(shader.id()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(shader.validate().first()); + } + + MAGNUM_VERIFY_NO_GL_ERROR(); +} + +template void LineGLTest::constructMove() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + LineGL a{typename LineGL::Configuration{} + .setFlags(LineGL::Flag::VertexColor)}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + LineGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), LineGL::Flag::VertexColor); + CORRADE_VERIFY(!a.id()); + + LineGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), LineGL::Flag::VertexColor); + CORRADE_VERIFY(!b.id()); +} + +template void LineGLTest::constructMoveUniformBuffers() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + LineGL a{typename LineGL::Configuration{} + .setFlags(LineGL::Flag::UniformBuffers) + .setMaterialCount(2) + .setDrawCount(5)}; + const GLuint id = a.id(); + CORRADE_VERIFY(id); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + LineGL b{std::move(a)}; + CORRADE_COMPARE(b.id(), id); + CORRADE_COMPARE(b.flags(), LineGL::Flag::UniformBuffers); + CORRADE_COMPARE(b.materialCount(), 2); + CORRADE_COMPARE(b.drawCount(), 5); + CORRADE_VERIFY(!a.id()); + + LineGL c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.id(), id); + CORRADE_COMPARE(c.flags(), LineGL::Flag::UniformBuffers); + CORRADE_COMPARE(c.materialCount(), 2); + CORRADE_COMPARE(c.drawCount(), 5); + CORRADE_VERIFY(!b.id()); +} + +template void LineGLTest::constructUniformBuffersInvalid() { + auto&& data = ConstructUniformBuffersInvalidData[testCaseInstanceId()]; + setTestCaseTemplateName(Utility::format("{}", dimensions)); + setTestCaseDescription(data.name); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #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}; + LineGL{typename LineGL::Configuration{} + .setFlags(data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount)}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::LineGL: {}\n", data.message)); +} + +template void LineGLTest::setUniformUniformBuffersEnabled() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + LineGL shader{typename LineGL::Configuration{} + .setFlags(LineGL::Flag::UniformBuffers)}; + + /* This should work fine */ + shader.setViewportSize({}); + + std::ostringstream out; + Error redirectError{&out}; + shader.setTransformationProjectionMatrix({}) + .setBackgroundColor({}) + .setColor({}) + .setWidth({}) + .setSmoothness({}) + .setMiterLengthLimit({}) + .setMiterAngleLimit({}) + .setObjectId({}); + CORRADE_COMPARE(out.str(), + "Shaders::LineGL::setTransformationProjectionMatrix(): the shader was created with uniform buffers enabled\n" + "Shaders::LineGL::setBackgroundColor(): the shader was created with uniform buffers enabled\n" + "Shaders::LineGL::setColor(): the shader was created with uniform buffers enabled\n" + "Shaders::LineGL::setWidth(): the shader was created with uniform buffers enabled\n" + "Shaders::LineGL::setSmoothness(): the shader was created with uniform buffers enabled\n" + "Shaders::LineGL::setMiterLengthLimit(): the shader was created with uniform buffers enabled\n" + "Shaders::LineGL::setMiterAngleLimit(): the shader was created with uniform buffers enabled\n" + "Shaders::LineGL::setObjectId(): the shader was created with uniform buffers enabled\n"); +} + +template void LineGLTest::bindBufferUniformBuffersNotEnabled() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Buffer buffer; + LineGL shader; + + std::ostringstream out; + Error redirectError{&out}; + 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::LineGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::LineGL::bindTransformationProjectionBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::LineGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::LineGL::bindDrawBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::LineGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::LineGL::bindMaterialBuffer(): the shader was not created with uniform buffers enabled\n" + "Shaders::LineGL::setDrawOffset(): the shader was not created with uniform buffers enabled\n"); +} + +template void LineGLTest::setMiterLengthLimitInvalid() { + auto&& data = SetMiterLengthLimitInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Buffer buffer; + LineGL shader{typename LineGL::Configuration{} + .setJoinStyle(data.joinStyle) + }; + + std::ostringstream out; + Error redirectError{&out}; + shader.setMiterLengthLimit(data.limit); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::LineGL::setMiterLengthLimit(): {}\n", data.message)); +} + +template void LineGLTest::setMiterAngleLimitInvalid() { + auto&& data = SetMiterAngleLimitInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Buffer buffer; + LineGL shader{typename LineGL::Configuration{} + .setJoinStyle(data.joinStyle) + }; + + std::ostringstream out; + Error redirectError{&out}; + shader.setMiterAngleLimit(data.limit); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Shaders::LineGL::setMiterAngleLimit(): {}\n", data.message)); +} + +template void LineGLTest::setObjectIdNotEnabled() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + LineGL shader; + + std::ostringstream out; + Error redirectError{&out}; + shader.setObjectId(33376); + CORRADE_COMPARE(out.str(), + "Shaders::LineGL::setObjectId(): the shader was not created with object ID enabled\n"); +} + +template void LineGLTest::setWrongDrawOffset() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + LineGL shader{typename LineGL::Configuration{} + .setFlags(LineGL::Flag::UniformBuffers) + .setMaterialCount(2) + .setDrawCount(5)}; + + std::ostringstream out; + Error redirectError{&out}; + shader.setDrawOffset(5); + CORRADE_COMPARE(out.str(), + "Shaders::LineGL::setDrawOffset(): draw offset 5 is out of bounds for 5 draws\n"); +} + +constexpr Vector2i RenderSizeLarge{128, 128}; + +void LineGLTest::renderSetupLarge() { + /* The geometry should be generated in CCW order, enable face culling to + verify that */ + GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); + /* Depth test enabled only in certain cases */ + + _color = GL::Renderbuffer{}; + _color.setStorage(GL::RenderbufferFormat::RGBA8, RenderSizeLarge); + _objectId = GL::Renderbuffer{}; + _objectId.setStorage(GL::RenderbufferFormat::R32UI, RenderSizeLarge); + _depth = GL::Renderbuffer{}; + _depth.setStorage(GL::RenderbufferFormat::DepthComponent24, RenderSizeLarge); + _framebuffer = GL::Framebuffer{{{}, RenderSizeLarge}}; + _framebuffer + .attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _color) + .attachRenderbuffer(GL::Framebuffer::ColorAttachment{1}, _objectId) + .attachRenderbuffer(GL::Framebuffer::BufferAttachment::Depth, _depth) + .mapForDraw({ + {LineGL2D::ColorOutput, GL::Framebuffer::ColorAttachment{0}} + /* ObjectIdOutput mapped in test cases that actually draw to it, + otherwise it causes an error on WebGL due to the shader not + rendering to all outputs */ + }) + .clearDepth(1.0f) + .clearColor(0, 0x111111_rgbf) + .clearColor(1, Vector4ui{27}) + .bind(); +} + +constexpr Vector2i RenderSizeSmall{80, 80}; + +void LineGLTest::renderSetupSmall() { + /* The geometry should be generated in CCW order, enable face culling to + verify that */ + GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); + /* Depth test enabled only in certain cases */ + + _color = GL::Renderbuffer{}; + _color.setStorage(GL::RenderbufferFormat::RGBA8, RenderSizeSmall); + _objectId = GL::Renderbuffer{}; + _objectId.setStorage(GL::RenderbufferFormat::R32UI, RenderSizeSmall); + _depth = GL::Renderbuffer{}; + _depth.setStorage(GL::RenderbufferFormat::DepthComponent24, RenderSizeSmall); + _framebuffer = GL::Framebuffer{{{}, RenderSizeSmall}}; + _framebuffer + .attachRenderbuffer(GL::Framebuffer::ColorAttachment{0}, _color) + .attachRenderbuffer(GL::Framebuffer::ColorAttachment{1}, _objectId) + .attachRenderbuffer(GL::Framebuffer::BufferAttachment::Depth, _depth) + .mapForDraw({ + {LineGL2D::ColorOutput, GL::Framebuffer::ColorAttachment{0}} + /* ObjectIdOutput is mapped (and cleared) in test cases that + actually draw to it, otherwise it causes an error on WebGL due + to the shader not rendering to all outputs */ + }) + .clearDepth(1.0f) + .clearColor(0, 0x111111_rgbf) + .bind(); +} + +void LineGLTest::renderTeardown() { + _framebuffer = GL::Framebuffer{NoCreate}; + _color = GL::Renderbuffer{NoCreate}; + _objectId = GL::Renderbuffer{NoCreate}; + _depth = GL::Renderbuffer{NoCreate}; +} + +/* A barebones utility for generating a line mesh. Embedded directly in the + test (as opposed to using something from MeshTools) to have it easier to + modify, debug and iterate on. */ +template struct Vertex { + VectorTypeFor previousPosition; + VectorTypeFor position; + VectorTypeFor nextPosition; + LineVertexAnnotations annotation; +}; + +template Containers::Array> generateLineMeshVertices(Containers::StridedArrayView1D> lineSegments) { + CORRADE_INTERNAL_ASSERT(lineSegments.size() % 2 == 0); + + Containers::Array> vertices{ValueInit, lineSegments.size()*2}; + for(std::size_t i = 0; i != lineSegments.size(); ++i) { + vertices[i*2 + 0].position = + vertices[i*2 + 1].position = + lineSegments[i]; + if(i % 2 == 0) + vertices[i*2 + 0].annotation = + vertices[i*2 + 1].annotation = LineVertexAnnotation::Begin; + vertices[i*2 + 0].annotation |= LineVertexAnnotation::Up; + } + + /* Mark joins if the segment endpoints are the same */ + for(std::size_t i = 4; i < vertices.size(); i += 4) { + if(vertices[i - 2].position != vertices[i].position) + continue; + for(std::size_t j: {i - 2, i - 1, i + 0, i + 1}) + vertices[j].annotation |= LineVertexAnnotation::Join; + } + + /* Prev positions for segment last vertices -- the other segment point */ + for(std::size_t i = 2; i < Containers::arraySize(vertices); i += 4) { + vertices[i + 0].previousPosition = + vertices[i + 1].previousPosition = + vertices[i - 2].position; + } + /* Prev positions for segment first vertices -- a neighbor segment, if it's + a join */ + for(std::size_t i = 4; i < Containers::arraySize(vertices); i += 4) { + if(!(vertices[i].annotation & LineVertexAnnotation::Join)) + continue; + vertices[i + 0].previousPosition = + vertices[i + 1].previousPosition = + vertices[i - 4].position; + } + /* Next positions for segment first vertices -- the other segment point */ + for(std::size_t i = 0; i < Containers::arraySize(vertices) - 2; i += 4) { + vertices[i + 0].nextPosition = + vertices[i + 1].nextPosition = + vertices[i + 2].position; + } + /* Next positions for last vertices -- a neighbor segment, if any */ + for(std::size_t i = 2; i < Containers::arraySize(vertices) - 4; i += 4) { + if(!(vertices[i].annotation & LineVertexAnnotation::Join)) + continue; + vertices[i + 0].nextPosition = + vertices[i + 1].nextPosition = + vertices[i + 4].position; + } + + return vertices; +} + +Containers::Array generateLineMeshIndices(Containers::StridedArrayView1D vertexAnnotations) { + Containers::Array indices; + for(UnsignedInt i = 0; i != vertexAnnotations.size()/4; ++i) { + /* 0---2 2 + | / /| + | / / | + |/ / | + 1 1---3 */ + arrayAppend(indices, { + i*4 + 0, + i*4 + 1, + i*4 + 2, + i*4 + 2, + i*4 + 1, + i*4 + 3 + }); + + /* Add also indices for the bevel in both orientations (one will always + degenerate) + + 2 2 2---4 4 4-- + /| | / /| | + / | | / / | | / + | |/ / | |/ + --3 3 3---5 5 5 */ + if(vertexAnnotations[i*4 + 3] & LineVertexAnnotation::Join) { + arrayAppend(indices, { + i*4 + 2, + i*4 + 3, + i*4 + 4, + i*4 + 4, + i*4 + 3, + i*4 + 5 + }); + } + } + + return indices; +} + +template GL::Mesh generateLineMesh(Containers::StridedArrayView1D> lineSegments) { + Containers::Array> vertices = generateLineMeshVertices(lineSegments); + Containers::Array indices = generateLineMeshIndices(stridedArrayView(vertices).slice(&Vertex::annotation)); + + GL::Mesh mesh; + mesh.addVertexBuffer(GL::Buffer{GL::Buffer::TargetHint::Array, vertices}, 0, + typename LineGL::PreviousPosition{}, + typename LineGL::Position{}, + typename LineGL::NextPosition{}, + typename LineGL::Annotation{}) + .setIndexBuffer(GL::Buffer{GL::Buffer::TargetHint::ElementArray, indices}, 0, GL::MeshIndexType::UnsignedInt) + .setCount(indices.size()); + + return mesh; +} + +GL::Mesh generateLineMesh(std::initializer_list lineSegments) { + return generateLineMesh<2>(Containers::arrayView(lineSegments)); +} + +GL::Mesh generateLineMesh(std::initializer_list lineSegments) { + return generateLineMesh<3>(Containers::arrayView(lineSegments)); +} + +template void LineGLTest::renderDefaults2D() { + if(flag == LineGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Mesh lines = generateLineMesh({ + /* A / line from the top to bottom */ + {-0.0f, 0.5f}, {-0.5f, -0.5f}, + /* A / line from the bottom to top */ + {-0.5f, -0.5f}, {0.5f, -0.25f}, + /* A | line from the bottom to top */ + {-0.75f, -0.25f}, {-0.75f, 0.75f}, + /* A _ line from the left to right */ + {-0.25f, -0.75f}, {0.75f, -0.75f}, + /* A zero-size line that should be visible as a point */ + {0.5f, 0.5f}, {0.5f, 0.5f} + }); + + LineGL2D shader{LineGL2D::Configuration{} + .setFlags(flag)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + if(flag == LineGL2D::Flag{}) { + shader.draw(lines); + } else if(flag == LineGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + LineMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + GL::Renderer::disable(GL::Renderer::Feature::Blending); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/defaults.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +template void LineGLTest::renderDefaults3D() { + if(flag == LineGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* Same as in the 2D case, just with a varying Z coordinate added. As the + implicit projection is orthographic, this should result in the exact + same output as 2D. */ + GL::Mesh lines = generateLineMesh({ + /* A / line from the top to bottom, front to back */ + {-0.0f, 0.5f, 1.0f}, {-0.5f, -0.5f, -1.0f}, + /* A / line from the bottom to top, back to front */ + {-0.5f, -0.5f, -1.0f}, {0.5f, -0.25f, 1.0f}, + /* A | line from the bottom to top, on the back */ + {-0.75f, -0.25f, -1.0f}, {-0.75f, 0.75f, -1.0f}, + /* A _ line from the left to right, on the front */ + {-0.25f, -0.75f, 1.0f}, {0.75f, -0.75f, 1.0f}, + /* A zero-size line that should be visible as a point, in the middle */ + {0.5f, 0.5f, 0.0f}, {0.5f, 0.5f, 0.0f} + }); + + LineGL3D shader{LineGL3D::Configuration{} + .setFlags(flag)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + if(flag == LineGL3D::Flag{}) { + shader.draw(lines); + } else if(flag == LineGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + LineMaterialUniform{} + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + GL::Renderer::disable(GL::Renderer::Feature::Blending); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/defaults.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +const Vector2 RenderLineCapsJoins2DLineData[]{ + /* A single point (a zero-length line) */ + {0.2f, 0.8f}, {0.2f, 0.8f}, + /* A rotated point (i.e., a line of a very small length). Should ideally be + symmetric. */ + /** @todo it isn't, why? */ + {-0.4f, 0.15f}, {-0.4f + Math::TypeTraits::epsilon(), 0.15f + Math::TypeTraits::epsilon()}, + /* A 90° join with a large length ratio. Caps should look the same on both + ends, independently on the length */ + {-0.8f, 0.7f}, {-0.8f, -0.25f}, + {-0.8f, -0.25f}, {-0.6f, -0.25f}, + /* A four-segment line with a 60°, 120° and a 30° join. All should be + miters in the default setup. */ + Vector2{0.25f, 0.3f} + Complex::rotation(-60.0_degf).transformVector(Vector2::yAxis(0.6f)) + Vector2::yAxis(-0.3f), + Vector2{0.25f, 0.3f} + Complex::rotation(-60.0_degf).transformVector(Vector2::yAxis(0.6f)), + Vector2{0.25f, 0.3f} + Complex::rotation(-60.0_degf).transformVector(Vector2::yAxis(0.6f)), + {0.2f, 0.35f}, + {0.2f, 0.35f}, + Vector2{0.25f, 0.3f} + Complex::rotation(60.0_degf).transformVector(Vector2::yAxis(0.6f)), + Vector2{0.25f, 0.3f} + Complex::rotation(60.0_degf).transformVector(Vector2::yAxis(0.6f)), + Vector2{0.25f, 0.3f} + Complex::rotation(60.0_degf).transformVector(Vector2::yAxis(0.6f)) + + Complex::rotation(30.0_degf).transformVector(Vector2::yAxis(-0.5f)), + /* A completely ordinary line segment, to test the case when everything + goes wrong */ + {0.4f, -0.05f}, {0.8f, -0.05f}, + /* A 180° join, with one part shorter. Should be always beveled, should not + overlap and should not disappear. */ + /** @todo the other end disappears, fix */ + {0.8f, -0.4f}, {0.0f, -0.4f}, + {0.0f, -0.4f}, {0.8f, -0.40001f}, /** @todo otherwise disappears, fix */ + /* A join where the other line touches the edge (either slightly above for + square/round caps, or slightly below for butt caps). The caps should not + get distorted in any way. */ + /** @todo they do, fix */ + {-0.45f, -0.8f}, {-0.7f, -0.8f}, + {-0.7f, -0.8f}, + Vector2{-0.7f, -0.8f} + Complex::rotation(60.0_degf).transformVector(Vector2::xAxis(0.2f)), + /* A join where the other line endpoint is inside the line. The caps should + not get distorted and it shouldn't overlap. */ + /** @todo it's distorted heavily, fix */ + {0.25f, -0.8f}, {0.0f, -0.8f}, + {0.0f, -0.8f}, + Vector2{0.0f, -0.8f} + Complex::rotation(60.0_degf).transformVector(Vector2::xAxis(0.01f)), + /* Like above, but with the first line short as well */ + /** @todo here's both a heavy distortion and an overlap, fix */ + {0.725f, -0.8f}, {0.7f, -0.8f}, + {0.7f, -0.8f}, + Vector2{0.7f, -0.8f} + Complex::rotation(60.0_degf).transformVector(Vector2::xAxis(0.01f)) +}; + +template void LineGLTest::renderLineCapsJoins2D() { + auto&& data = RenderLineCapsJoins2DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(flag == LineGL2D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Mesh lines = generateLineMesh<2>(RenderLineCapsJoins2DLineData); + + LineGL2D::Configuration configuration; + configuration.setFlags(flag); + if(data.capStyle) configuration.setCapStyle(*data.capStyle); + if(data.joinStyle) configuration.setJoinStyle(*data.joinStyle); + LineGL2D shader{configuration}; + shader.setViewportSize(Vector2{RenderSizeLarge}); + + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + if(flag == LineGL2D::Flag{}) { + shader + .setWidth(data.width) + .setSmoothness(data.smoothness) + .setColor(0x80808080_rgbaf); + if(data.miterLengthLimit) + shader.setMiterLengthLimit(*data.miterLengthLimit); + if(data.miterAngleLimit) + shader.setMiterAngleLimit(*data.miterAngleLimit); + shader.draw(lines); + } else if(flag == LineGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + }}; + + LineMaterialUniform materialUniformData[1]; + (*materialUniformData) + .setWidth(data.width) + .setSmoothness(data.smoothness) + .setColor(0x80808080_rgbaf); + if(data.miterLengthLimit) + materialUniformData->setMiterLengthLimit(*data.miterLengthLimit); + if(data.miterAngleLimit) + materialUniformData->setMiterAngleLimit(*data.miterAngleLimit); + GL::Buffer materialUniform{materialUniformData}; + + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + GL::Renderer::disable(GL::Renderer::Feature::Blending); + + 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."); + + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(image.pixels()), + Utility::Path::join({SHADERS_TEST_DIR, "LineTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); + + { + /** @todo drop this when fixed */ + CORRADE_EXPECT_FAIL("Rendered with overlapping geometry at the moment."); + CORRADE_COMPARE(Math::max(image.pixels().asContiguous()), 0x888888ff_rgba); + } +} + +void LineGLTest::renderLineCapsJoins2DReversed() { + auto&& data = RenderLineCapsJoins2DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* As this verifies mainly the algorithm, there's no variant with UBOs -- + those are sufficiently tested elsewhere */ + + GL::Mesh lines = generateLineMesh<2>(Containers::stridedArrayView(RenderLineCapsJoins2DLineData).flipped<0>()); + + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + LineGL2D::Configuration configuration; + if(data.capStyle) configuration.setCapStyle(*data.capStyle); + if(data.joinStyle) configuration.setJoinStyle(*data.joinStyle); + LineGL2D shader{configuration}; + shader + .setViewportSize(Vector2{RenderSizeLarge}) + .setWidth(data.width) + .setSmoothness(data.smoothness) + .setColor(0x80808080_rgbaf); + if(data.miterLengthLimit) shader.setMiterLengthLimit(*data.miterLengthLimit); + if(data.miterAngleLimit) shader.setMiterAngleLimit(*data.miterAngleLimit); + + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + shader.draw(lines); + + GL::Renderer::disable(GL::Renderer::Feature::Blending); + + 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."); + + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(image.pixels()), + Utility::Path::join({SHADERS_TEST_DIR, "LineTestFiles", data.expected}), + /** @todo sync this with render2D() once the overlaps are fixed */ + (DebugTools::CompareImageToFile{_manager, 1.0f, 0.0005f})); +} + +void LineGLTest::renderLineCapsJoins2DTransformed() { + auto&& data = RenderLineCapsJoins2DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* As this verifies mainly the algorithm, there's no variant with UBOs -- + those are sufficiently tested elsewhere */ + + const Matrix3 transformation = Matrix3::scaling({100.0f, 2.0f})*Matrix3::rotation(45.0_degf); + + Containers::Array transformedLineSegments{NoInit, Containers::arraySize(RenderLineCapsJoins2DLineData)}; + for(std::size_t i = 0; i != transformedLineSegments.size(); ++i) + transformedLineSegments[i] = transformation.transformPoint(RenderLineCapsJoins2DLineData[i]); + + GL::Mesh lines = generateLineMesh<2>(transformedLineSegments); + + LineGL2D::Configuration configuration; + if(data.capStyle) configuration.setCapStyle(*data.capStyle); + if(data.joinStyle) configuration.setJoinStyle(*data.joinStyle); + LineGL2D shader{configuration}; + shader + .setViewportSize(Vector2{RenderSizeLarge}) + .setWidth(data.width) + .setSmoothness(data.smoothness) + .setTransformationProjectionMatrix(transformation.inverted()) + .setColor(0x80808080_rgbaf); + + if(data.miterLengthLimit) shader.setMiterLengthLimit(*data.miterLengthLimit); + if(data.miterAngleLimit) shader.setMiterAngleLimit(*data.miterAngleLimit); + + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + shader.draw(lines); + + GL::Renderer::disable(GL::Renderer::Feature::Blending); + + 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."); + + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(image.pixels()), + Utility::Path::join({SHADERS_TEST_DIR, "LineTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); +} + +template void LineGLTest::renderCube3D() { + auto&& data = RenderCube3DData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(flag == LineGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* A cube, with the top and bottom quad being a loop and the sides being + disconnected segments */ + Containers::Array> vertices = generateLineMeshVertices<3>(Containers::stridedArrayView({ + {-1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 1.0f}, // 0 to 3, loops to 12/13 + {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, -1.0f}, // 4 to 7 + {1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f}, // 8 to 11 + {-1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, 1.0f}, // 12 to 15, loops to 2/3 + + {-1.0f, -1.0f, 1.0f}, {1.0f, -1.0f, 1.0f}, // 16 to 19, loops to 28/29 + {1.0f, -1.0f, 1.0f}, {1.0f, -1.0f, -1.0f}, // 20 to 23 + {1.0f, -1.0f, -1.0f}, {-1.0f, -1.0f, -1.0f}, // 24 to 27 + {-1.0f, -1.0f, -1.0f}, {-1.0f, -1.0f, 1.0f}, // 28 to 31, loops to 18/19 + + {-1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f}, // 32 to 35 + {1.0f, 1.0f, 1.0f}, {1.0f, -1.0f, 1.0f}, // 36 to 39 + {1.0f, 1.0f, -1.0f}, {1.0f, -1.0f, -1.0f}, // 40 to 43 + {-1.0f, 1.0f, -1.0f}, {-1.0f, -1.0f, -1.0f}, // 43 to 47 + })); + CORRADE_COMPARE(vertices.size(), 48); + + /* Check prerequisites */ + for(std::size_t i: {0, 1, 16, 17}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(vertices[i].previousPosition, Vector3{}); + } + for(std::size_t i: {14, 15, 30, 31}) { + CORRADE_ITERATION(i); + CORRADE_COMPARE(vertices[i].nextPosition, Vector3{}); + } + + /* Manually loop the top & bottom */ + vertices[0].previousPosition = vertices[12].position; + vertices[1].previousPosition = vertices[13].position; + vertices[14].nextPosition = vertices[2].position; + vertices[15].nextPosition = vertices[3].position; + + vertices[16].previousPosition = vertices[28].position; + vertices[17].previousPosition = vertices[29].position; + vertices[30].nextPosition = vertices[18].position; + vertices[31].nextPosition = vertices[19].position; + + Containers::Array indices = generateLineMeshIndices(stridedArrayView(vertices).slice(&Vertex<3>::annotation)); + + /* Add line join annotation to the looped parts. Has to be done *after* + generating indices because otherwise it'd assume the next point of the + join is right after which it isn't. */ + for(std::size_t i: {0, 1, 14, 15, 16, 17, 30, 31}) { + CORRADE_ITERATION(i); + CORRADE_VERIFY(!(vertices[i].annotation & LineVertexAnnotation::Join)); + vertices[i].annotation |= LineVertexAnnotation::Join; + } + + /* Add indices for the two newly created joins */ + arrayAppend(indices, { + 14u, 15u, 0u, + 0u, 15u, 1u, + + 30u, 31u, 16u, + 16u, 31u, 17u + }); + + GL::Mesh lines; + lines.addVertexBuffer(GL::Buffer{GL::Buffer::TargetHint::Array, vertices}, 0, + LineGL3D::PreviousPosition{}, + LineGL3D::Position{}, + LineGL3D::NextPosition{}, + LineGL3D::Annotation{}) + .setIndexBuffer(GL::Buffer{GL::Buffer::TargetHint::ElementArray, indices}, 0, GL::MeshIndexType::UnsignedInt) + .setCount(indices.size()); + + const Matrix4 projection = Matrix4::perspectiveProjection(50.0_degf, 1.0f, 0.1f, 10.0f); + const Matrix4 transformation = + Matrix4::translation({-0.125f, 0.25f, -5.0f})* + Matrix4::rotationX(25.0_degf)* + Matrix4::rotationY(30.0_degf); + + if(data.renderSolidCube) { + GL::Renderer::enable(GL::Renderer::Feature::DepthTest); + + PhongGL shader; + shader.setLightPositions({{-1.0f, 2.0f, 3.0f, 0.0f}}) + .setProjectionMatrix(projection) + .setTransformationMatrix(transformation) + .setNormalMatrix(transformation.normalMatrix()) + .setDiffuseColor(0xff0000_rgbf) + .draw(MeshTools::compile(Primitives::cubeSolid())); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + GL::Renderer::setDepthFunction(GL::Renderer::DepthFunction::LessOrEqual); + GL::Renderer::setDepthMask(false); + } + + LineGL3D::Configuration configuration; + configuration.setFlags(flag); + if(data.capStyle) configuration.setCapStyle(*data.capStyle); + if(data.joinStyle) configuration.setJoinStyle(*data.joinStyle); + LineGL3D shader{configuration}; + shader.setViewportSize(Vector2{RenderSizeLarge}); + + /* Enabling blending and a half-transparent color -- there should be no + overlaps */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + if(flag == LineGL3D::Flag{}) { + shader + .setTransformationProjectionMatrix(projection*transformation) + .setWidth(data.width) + .setSmoothness(data.smoothness) + .setColor(0x80808080_rgbaf); + if(data.miterLengthLimit) + shader.setMiterLengthLimit(*data.miterLengthLimit); + shader.draw(lines); + } else if(flag == LineGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix(projection*transformation) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + }}; + + LineMaterialUniform materialUniformData[1]; + (*materialUniformData) + .setWidth(data.width) + .setSmoothness(data.smoothness) + .setColor(0x80808080_rgbaf); + if(data.miterLengthLimit) + materialUniformData->setMiterLengthLimit(*data.miterLengthLimit); + GL::Buffer materialUniform{materialUniformData}; + + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + if(data.renderSolidCube) { + GL::Renderer::disable(GL::Renderer::Feature::DepthTest); + GL::Renderer::setDepthFunction(GL::Renderer::DepthFunction::Less); + GL::Renderer::setDepthMask(true); + } + + GL::Renderer::disable(GL::Renderer::Feature::Blending); + + 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."); + + Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(image.pixels()), + Utility::Path::join({SHADERS_TEST_DIR, "LineTestFiles", data.expected}), + (DebugTools::CompareImageToFile{_manager})); +} + +void LineGLTest::renderPerspective3D() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* Verify that perspective-correct interpolation isn't used (which would + cause significant artifacts) */ + GL::Mesh lines = generateLineMesh({ + {0.0f, -1.0f, 10.0f}, {0.0f, 7.5f, -10.0f} + }); + + LineGL3D shader; + shader.setViewportSize(Vector2{RenderSizeSmall}) + .setTransformationProjectionMatrix( + Matrix4::perspectiveProjection(50.0_degf, 1.0f, 0.1f, 50.0f)* + Matrix4::translation({0.0f, 0.0f, -13.0f})) + .setWidth(10.0f) + .setSmoothness(1.0f) + .draw(lines); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/perspective3D.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +template void LineGLTest::renderVertexColor2D() { + if(flag == LineGL2D::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 { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Mesh lines = generateLineMesh({ + {-0.8f, 0.5f}, {-0.5f, -0.5f}, + {-0.5f, -0.5f}, {0.0f, 0.0f}, + {0.0f, 0.0f}, {0.5f, -0.5f}, + {0.5f, -0.5f}, {0.8f, 0.5f} + }); + + /* Each line segment from above is four points */ + T colors[]{ + 0xff0000_rgbf, 0xff0000_rgbf, 0xffff00_rgbf, 0xffff00_rgbf, + 0xffff00_rgbf, 0xffff00_rgbf, 0x00ffff_rgbf, 0x00ffff_rgbf, + 0x00ffff_rgbf, 0x00ffff_rgbf, 0x00ff00_rgbf, 0x00ff00_rgbf, + 0x00ff00_rgbf, 0x00ff00_rgbf, 0x0000ff_rgbf, 0x0000ff_rgbf + }; + if(std::is_same::value) + lines.addVertexBuffer(GL::Buffer{GL::Buffer::TargetHint::Array, colors}, 0, LineGL2D::Color3{}); + else + lines.addVertexBuffer(GL::Buffer{GL::Buffer::TargetHint::Array, colors}, 0, LineGL2D::Color4{}); + + LineGL2D shader{LineGL2D::Configuration{} + .setFlags(LineGL2D::Flag::VertexColor|flag) + .setCapStyle(LineCapStyle::Triangle)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Set background to blue as well so we don't have too much aliasing */ + GL::Renderer::setClearColor(0x000080_rgbf); + _framebuffer.clear(GL::FramebufferClear::Color); + + if(flag == LineGL2D::Flag{}) { + shader + /* Background should stay blue, foreground should have no blue */ + .setBackgroundColor(0x000080_rgbf) + .setColor(0x999900_rgbf) + .setWidth(4.0f) + .setSmoothness(1.0f) + .draw(lines); + } else if(flag == LineGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + LineMaterialUniform{} + /* Background should stay blue, foreground should have no blue */ + .setBackgroundColor(0x000080_rgbf) + .setColor(0x999900_rgbf) + .setWidth(4.0f) + .setSmoothness(1.0f) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/vertex-color.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +template void LineGLTest::renderVertexColor3D() { + if(flag == LineGL3D::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 { + setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* Same as renderVertexColor2D(), except that the positions are 3D with + varying Z. But the (default) projection is orthographic so the output is + the same -- nothing 3D-specific to test here. */ + GL::Mesh lines = generateLineMesh({ + {-0.8f, 0.5f, 1.0f}, {-0.5f, -0.5f, -1.0f}, + {-0.5f, -0.5f, -1.0f}, {0.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 0.0f}, {0.5f, -0.5f, 0.5f}, + {0.5f, -0.5f, 0.5f}, {0.8f, 0.5f, -1.0f} + }); + + /* Each line segment from above is four points */ + T colors[]{ + 0xff0000_rgbf, 0xff0000_rgbf, 0xffff00_rgbf, 0xffff00_rgbf, + 0xffff00_rgbf, 0xffff00_rgbf, 0x00ffff_rgbf, 0x00ffff_rgbf, + 0x00ffff_rgbf, 0x00ffff_rgbf, 0x00ff00_rgbf, 0x00ff00_rgbf, + 0x00ff00_rgbf, 0x00ff00_rgbf, 0x0000ff_rgbf, 0x0000ff_rgbf + }; + if(std::is_same::value) + lines.addVertexBuffer(GL::Buffer{GL::Buffer::TargetHint::Array, colors}, 0, LineGL3D::Color3{}); + else + lines.addVertexBuffer(GL::Buffer{GL::Buffer::TargetHint::Array, colors}, 0, LineGL3D::Color4{}); + + LineGL3D shader{LineGL3D::Configuration{} + .setFlags(LineGL3D::Flag::VertexColor|flag) + .setCapStyle(LineCapStyle::Triangle)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Set background to blue as well so we don't have too much aliasing */ + GL::Renderer::setClearColor(0x000080_rgbf); + _framebuffer.clear(GL::FramebufferClear::Color); + + if(flag == LineGL3D::Flag{}) { + shader + /* Background should stay blue, foreground should have no blue */ + .setBackgroundColor(0x000080_rgbf) + .setColor(0x999900_rgbf) + .setWidth(4.0f) + .setSmoothness(1.0f) + .draw(lines); + } else if(flag == LineGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + LineMaterialUniform{} + /* Background should stay blue, foreground should have no blue */ + .setBackgroundColor(0x000080_rgbf) + .setColor(0x999900_rgbf) + .setWidth(4.0f) + .setSmoothness(1.0f) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/vertex-color.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +template void LineGLTest::renderObjectId2D() { + auto&& data = RenderObjectIdData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(flag == LineGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + GL::Mesh lines = generateLineMesh({ + {-0.6f, 0.0f}, {0.6f, 0.0f} + }); + + LineGL2D shader{LineGL2D::Configuration{} + .setFlags(LineGL2D::Flag::ObjectId|flag) + .setCapStyle(data.capStyle)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Map ObjectIdOutput so we can draw to it. Mapping it always causes an + error on WebGL when the shader does not render to it; however if not + bound we can't even clear it on WebGL, so it has to be cleared after. */ + _framebuffer + .mapForDraw({ + {LineGL2D::ColorOutput, GL::Framebuffer::ColorAttachment{0}}, + {LineGL2D::ObjectIdOutput, GL::Framebuffer::ColorAttachment{1}} + }) + .clearColor(1, Vector4ui{27}); + + if(flag == LineGL2D::Flag{}) { + shader + .setWidth(data.width) + .setSmoothness(data.smoothness) + .setObjectId(47365) + .draw(lines); + } else if(flag == LineGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform2D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + .setObjectId(47365) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + LineMaterialUniform{} + .setWidth(data.width) + .setSmoothness(data.smoothness) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* No need to verify the whole image, just check that pixels at 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). */ + _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}); + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{0}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* Center of the line, should be set */ + CORRADE_COMPARE(image.pixels()[39][39], 47365); + /* Corner of the line, should be set as well, independently of the cap + style or smoothness */ + CORRADE_COMPARE(image.pixels()[35][11], 47365); + /* Outside of the object */ + CORRADE_COMPARE(image.pixels()[34][11], 27); + CORRADE_COMPARE(image.pixels()[35][10], 27); +} + +template void LineGLTest::renderObjectId3D() { + auto&& data = RenderObjectIdData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(flag == LineGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* Same as renderObjectId2D(), just with a varying Z coordinate (which + should have no effect as the projection is orthographic) */ + GL::Mesh lines = generateLineMesh({ + {-0.6f, 0.0f, 1.0f}, {0.6f, 0.0f, -1.0f} + }); + + LineGL3D shader{LineGL3D::Configuration{} + .setFlags(LineGL3D::Flag::ObjectId|flag) + .setCapStyle(data.capStyle)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Map ObjectIdOutput so we can draw to it. Mapping it always causes an + error on WebGL when the shader does not render to it; however if not + bound we can't even clear it on WebGL, so it has to be cleared after. */ + _framebuffer + .mapForDraw({ + {LineGL3D::ColorOutput, GL::Framebuffer::ColorAttachment{0}}, + {LineGL3D::ObjectIdOutput, GL::Framebuffer::ColorAttachment{1}} + }) + .clearColor(1, Vector4ui{27}); + + if(flag == LineGL3D::Flag{}) { + shader + .setWidth(data.width) + .setSmoothness(data.smoothness) + .setObjectId(47365) + .draw(lines); + } else if(flag == LineGL3D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + .setObjectId(47365) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + LineMaterialUniform{} + .setWidth(data.width) + .setSmoothness(data.smoothness) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* No need to verify the whole image, just check that pixels at 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). */ + _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}); + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{0}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* Center of the line, should be set */ + CORRADE_COMPARE(image.pixels()[39][39], 47365); + /* Corner of the line, should be set as well, independently of the cap + style or smoothness */ + CORRADE_COMPARE(image.pixels()[35][11], 47365); + /* Outside of the object */ + CORRADE_COMPARE(image.pixels()[34][11], 27); + CORRADE_COMPARE(image.pixels()[35][10], 27); +} + +template void LineGLTest::renderInstanced2D() { + auto&& data = RenderInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(flag == LineGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* A wave, important property is that it passes through origin to make + object ID verification easier */ + GL::Mesh lines = generateLineMesh({ + {-0.8f, -0.8f}, {-0.5f, 0.5f}, + {-0.5f, 0.5f}, {0.5f, -0.5f}, + {0.5f, -0.5f}, {0.8f, 0.8f} + }); + + /* Three circles, each in a different location */ + struct { + Matrix3 transformation; + Color3 color; + UnsignedInt objectId; + } instanceData[] { + {Matrix3::translation({-1.25f, -1.25f}), 0xffff00_rgbf, 211}, + {Matrix3::translation({ 1.25f, -1.25f}), 0x00ffff_rgbf, 4627}, + {Matrix3::translation({ 0.00f, 1.25f}), 0xff00ff_rgbf, 35363}, + }; + + lines + .addVertexBufferInstanced(GL::Buffer{GL::Buffer::TargetHint::Array, instanceData}, 1, 0, + LineGL2D::TransformationMatrix{}, + LineGL2D::Color3{}, + LineGL2D::ObjectId{}) + .setInstanceCount(3); + + LineGL2D shader{LineGL2D::Configuration{} + .setFlags(LineGL2D::Flag::InstancedTransformation|LineGL2D::Flag::VertexColor|data.flags|flag)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Map ObjectIdOutput so we can draw to it. Mapping it always causes an + error on WebGL when the shader does not render to it; however if not + bound we can't even clear it on WebGL, so it has to be cleared after. */ + if(data.flags & LineGL2D::Flag::ObjectId) _framebuffer + .mapForDraw({ + {LineGL2D::ColorOutput, GL::Framebuffer::ColorAttachment{0}}, + {LineGL2D::ObjectIdOutput, GL::Framebuffer::ColorAttachment{1}} + }) + .clearColor(1, Vector4ui{27}); + + if(flag == LineGL2D::Flag{}) { + shader + .setTransformationProjectionMatrix( + Matrix3::projection({2.1f, 2.1f})* + Matrix3::scaling(Vector2{0.4f})) + .setColor(0xffff00_rgbf) + .setWidth(5.0f) + .setSmoothness(1.0f); + if(data.flags & LineGL2D::Flag::ObjectId) + /* Gets added to the per-instance ID, if that's enabled as well */ + shader.setObjectId(1000); + shader.draw(lines); + + } else if(flag == LineGL2D::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, { + LineDrawUniform{} + /* Gets added to the per-instance ID, if that's enabled as + well */ + .setObjectId(1000) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + LineMaterialUniform{} + .setColor(0xffff00_rgbf) + .setWidth(5.0f) + .setSmoothness(1.0f) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* - First should be lower left, yellow with a yellow base color, so yellow + - Second lower right, cyan with a yellow base color, so green + - Third up center, magenta with a yellow base color, so red */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/instanced.tga"), + (DebugTools::CompareImageToFile{_manager})); + + /* 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). */ + if(data.flags & LineGL2D::Flag::ObjectId) { + _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}); + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{0}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* If instanced object IDs are enabled, the per-instance ID gets added + to the output as well */ + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[20][20], data.expectedId[0]); + CORRADE_COMPARE(image.pixels()[20][60], data.expectedId[1]); + CORRADE_COMPARE(image.pixels()[60][40], data.expectedId[2]); + } +} + +template void LineGLTest::renderInstanced3D() { + auto&& data = RenderInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + if(flag == LineGL3D::Flag::UniformBuffers) { + setTestCaseTemplateName("Flag::UniformBuffers"); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + } + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + #endif + + /* Same as in renderInstanced2D() except for a varying Z coordinate. Which + shouldn't affect the output as the projection is orthographic. */ + GL::Mesh lines = generateLineMesh({ + {-0.8f, -0.8f, 1.0f}, {-0.5f, 0.5f, -1.0f}, + {-0.5f, 0.5f, -1.0f}, {0.5f, -0.5f, 0.0f}, + {0.5f, -0.5f, 0.0f}, {0.8f, 0.8f, 1.0f} + }); + + /* Three circles, each in a different location */ + struct { + Matrix4 transformation; + Color3 color; + UnsignedInt objectId; + } instanceData[] { + {Matrix4::translation({-1.25f, -1.25f, 0.5f}), 0xffff00_rgbf, 211}, + {Matrix4::translation({ 1.25f, -1.25f, -0.5f}), 0x00ffff_rgbf, 4627}, + {Matrix4::translation({ 0.00f, 1.25f, 0.0f}), 0xff00ff_rgbf, 35363}, + }; + + lines + .addVertexBufferInstanced(GL::Buffer{GL::Buffer::TargetHint::Array, instanceData}, 1, 0, + LineGL3D::TransformationMatrix{}, + LineGL3D::Color3{}, + LineGL3D::ObjectId{}) + .setInstanceCount(3); + + LineGL3D shader{LineGL3D::Configuration{} + .setFlags(LineGL3D::Flag::InstancedTransformation|LineGL3D::Flag::VertexColor|data.flags|flag)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Map ObjectIdOutput so we can draw to it. Mapping it always causes an + error on WebGL when the shader does not render to it; however if not + bound we can't even clear it on WebGL, so it has to be cleared after. */ + if(data.flags & LineGL3D::Flag::ObjectId) _framebuffer + .mapForDraw({ + {LineGL3D::ColorOutput, GL::Framebuffer::ColorAttachment{0}}, + {LineGL3D::ObjectIdOutput, GL::Framebuffer::ColorAttachment{1}} + }) + .clearColor(1, Vector4ui{27}); + + if(flag == LineGL3D::Flag{}) { + shader + .setTransformationProjectionMatrix( + Matrix4::orthographicProjection({2.1f, 2.1f}, -1.0f, 1.0f)* + Matrix4::scaling(Vector3{0.4f})) + .setColor(0xffff00_rgbf) + .setWidth(5.0f) + .setSmoothness(1.0f); + if(data.flags & LineGL2D::Flag::ObjectId) + /* Gets added to the per-instance ID, if that's enabled as well */ + shader.setObjectId(1000); + shader.draw(lines); + + } else if(flag == LineGL2D::Flag::UniformBuffers) { + GL::Buffer transformationProjectionUniform{GL::Buffer::TargetHint::Uniform, { + TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::orthographicProjection({2.1f, 2.1f}, -1.0f, 1.0f)* + Matrix4::scaling(Vector3{0.4f}) + ) + }}; + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, { + LineDrawUniform{} + /* Gets added to the per-instance ID, if that's enabled as + well */ + .setObjectId(1000) + }}; + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, { + LineMaterialUniform{} + .setColor(0xffff00_rgbf) + .setWidth(5.0f) + .setSmoothness(1.0f) + }}; + shader + .bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform) + .draw(lines); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* - First should be lower left, yellow with a yellow base color, so yellow + - Second lower right, cyan with a yellow base color, so green + - Third up center, magenta with a yellow base color, so red */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/instanced.tga"), + (DebugTools::CompareImageToFile{_manager})); + + /* 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). */ + if(data.flags & LineGL2D::Flag::ObjectId) { + _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}); + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{0}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* If instanced object IDs are enabled, the per-instance ID gets added + to the output as well */ + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[20][20], data.expectedId[0]); + CORRADE_COMPARE(image.pixels()[20][60], data.expectedId[1]); + CORRADE_COMPARE(image.pixels()[60][40], data.expectedId[2]); + } +} + +void LineGLTest::renderMulti2D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= LineGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + /* All parts pass through origin to make object ID verification easier */ + GL::Mesh lines = generateLineMesh({ + /* A wave, same as in renderInstanced2D() */ + {-0.8f, -0.8f}, {-0.5f, 0.5f}, // 0 to 5 + 6 to 12 for the join + {-0.5f, 0.5f}, {0.5f, -0.5f}, // 12 to 17 + 18 to 23 for the join + {0.5f, -0.5f}, {0.8f, 0.8f}, // 24 to 29 + + /* A cross */ + {-0.8f, -0.8f}, {0.8f, 0.8f}, // 30 to 35 + {0.8f, -0.8f}, {-0.8f, 0.8f}, // 36 to 41 + + /* A single point */ + {0.0f, 0.0f}, {0.0f, 0.0f} // 42 to 47 + }); + CORRADE_COMPARE(lines.count(), 48); + + GL::MeshView wave{lines}; + wave.setCount(30); + GL::MeshView cross{lines}; + cross.setCount(12) + .setIndexRange(30); + GL::MeshView point{lines}; + point.setCount(6) + .setIndexRange(42); + + LineGL2D shader{LineGL2D::Configuration{} + .setFlags(LineGL2D::Flag::UniformBuffers|data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Map ObjectIdOutput so we can draw to it. Mapping it always causes an + error on WebGL when the shader does not render to it; however if not + bound we can't even clear it on WebGL, so it has to be cleared after. */ + if(data.flags & LineGL2D::Flag::ObjectId) _framebuffer + .mapForDraw({ + {LineGL2D::ColorOutput, GL::Framebuffer::ColorAttachment{0}}, + {LineGL2D::ObjectIdOutput, GL::Framebuffer::ColorAttachment{1}} + }) + .clearColor(1, Vector4ui{27}); + + /* 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] = LineMaterialUniform{} + .setColor(0x0000ff_rgbf) + .setWidth(3.0f) + .setSmoothness(3.0f); + materialData[1*data.uniformIncrement] = LineMaterialUniform{} + .setColor(0xff0000_rgbf) + .setWidth(5.0f) + .setSmoothness(1.0f); + 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] = LineDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setObjectId(1211); + drawData[1*data.uniformIncrement] = LineDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + .setObjectId(5627); + drawData[2*data.uniformIncrement] = LineDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setObjectId(36363); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + /* Enabling blending so the overlap in X is rendered alright */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(LineMaterialUniform), + sizeof(LineMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(LineDrawUniform), + sizeof(LineDrawUniform)); + shader.draw(wave); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(LineMaterialUniform), + sizeof(LineMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(LineDrawUniform), + sizeof(LineDrawUniform)); + shader.draw(cross); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(LineMaterialUniform), + sizeof(LineMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform2D), + sizeof(TransformationProjectionUniform2D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(LineDrawUniform), + sizeof(LineDrawUniform)); + shader.draw(point); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + + if(data.flags >= LineGL2D::Flag::MultiDraw) + shader.draw({wave, cross, point}); + else shader + .setDrawOffset(0) + .draw(wave) + .setDrawOffset(1) + .draw(cross) + .setDrawOffset(2) + .draw(point); + } + + GL::Renderer::disable(GL::Renderer::Feature::Blending); + + 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."); + + /* - Wave should be lower left, red + - Cross lower right, blue + - Point up center, red */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/multidraw.tga"), + (DebugTools::CompareImageToFile{_manager})); + + /* 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). */ + if(data.flags & LineGL2D::Flag::ObjectId) { + _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}); + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{0}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[20][20], data.expectedId[0]); /* Wave */ + CORRADE_COMPARE(image.pixels()[20][60], data.expectedId[1]); /* Cross */ + CORRADE_COMPARE(image.pixels()[60][40], data.expectedId[2]); /* Point */ + } +} + +void LineGLTest::renderMulti3D() { + auto&& data = RenderMultiData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::gpu_shader4::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::uniform_buffer_object::string() << "is not supported."); + #endif + + if(data.flags >= LineGL2D::Flag::MultiDraw) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::multi_draw::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw::string() << "is not supported."); + #endif + } + + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().detectedDriver() & GL::Context::DetectedDriver::SwiftShader) + CORRADE_SKIP("UBOs with dynamically indexed arrays are a crashy dumpster fire on SwiftShader, can't test."); + #endif + + /* Same as in renderMulti2D() except for an additional varying Z + coordinate. Which won't affect the output because the projection is + orthographic. */ + GL::Mesh lines = generateLineMesh({ + {-0.8f, -0.8f, 1.0f}, {-0.5f, 0.5f, -1.0f}, + {-0.5f, 0.5f, -1.0f}, {0.5f, -0.5f, 0.0f}, + {0.5f, -0.5f, 0.0f}, {0.8f, 0.8f, 1.0f}, + + {-0.8f, -0.8f, 1.0f}, {0.8f, 0.8f, 1.0f}, + {0.8f, -0.8f, -1.0f}, {-0.8f, 0.8f, -1.0f}, + + {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f} + }); + CORRADE_COMPARE(lines.count(), 48); + + GL::MeshView wave{lines}; + wave.setCount(30); + GL::MeshView cross{lines}; + cross.setCount(12) + .setIndexRange(30); + GL::MeshView point{lines}; + point.setCount(6) + .setIndexRange(42); + + LineGL3D shader{LineGL3D::Configuration{} + .setFlags(LineGL3D::Flag::UniformBuffers|data.flags) + .setMaterialCount(data.materialCount) + .setDrawCount(data.drawCount)}; + shader.setViewportSize(Vector2{RenderSizeSmall}); + + /* Map ObjectIdOutput so we can draw to it. Mapping it always causes an + error on WebGL when the shader does not render to it; however if not + bound we can't even clear it on WebGL, so it has to be cleared after. */ + if(data.flags & LineGL3D::Flag::ObjectId) _framebuffer + .mapForDraw({ + {LineGL3D::ColorOutput, GL::Framebuffer::ColorAttachment{0}}, + {LineGL3D::ObjectIdOutput, GL::Framebuffer::ColorAttachment{1}} + }) + .clearColor(1, Vector4ui{27}); + + /* 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] = LineMaterialUniform{} + .setColor(0x0000ff_rgbf) + .setWidth(3.0f) + .setSmoothness(3.0f); + materialData[1*data.uniformIncrement] = LineMaterialUniform{} + .setColor(0xff0000_rgbf) + .setWidth(5.0f) + .setSmoothness(1.0f); + GL::Buffer materialUniform{GL::Buffer::TargetHint::Uniform, materialData}; + + Containers::Array transformationProjectionData{2*data.uniformIncrement + 1}; + transformationProjectionData[0*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::orthographicProjection({2.1f, 2.1f}, -1.0f, 1.0f)* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({-1.25f, -1.25f, 0.5f}) + ); + transformationProjectionData[1*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::orthographicProjection({2.1f, 2.1f}, -1.0f, 1.0f)* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 1.25f, -1.25f, -0.5f}) + ); + transformationProjectionData[2*data.uniformIncrement] = TransformationProjectionUniform3D{} + .setTransformationProjectionMatrix( + Matrix4::orthographicProjection({2.1f, 2.1f}, -1.0f, 1.0f)* + Matrix4::scaling(Vector3{0.4f})* + Matrix4::translation({ 0.00f, 1.25f, 0.0f}) + ); + 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] = LineDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setObjectId(1211); + drawData[1*data.uniformIncrement] = LineDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 0) + .setObjectId(5627); + drawData[2*data.uniformIncrement] = LineDrawUniform{} + .setMaterialId(data.drawCount == 1 ? 0 : 1) + .setObjectId(36363); + GL::Buffer drawUniform{GL::Buffer::TargetHint::Uniform, drawData}; + + /* Enabling blending so the overlap in X is rendered alright */ + GL::Renderer::enable(GL::Renderer::Feature::Blending); + GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + + /* Just one draw, rebinding UBOs each time */ + if(data.drawCount == 1) { + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(LineMaterialUniform), + sizeof(LineMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 0*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 0*data.uniformIncrement*sizeof(LineDrawUniform), + sizeof(LineDrawUniform)); + shader.draw(wave); + + shader.bindMaterialBuffer(materialUniform, + 0*data.uniformIncrement*sizeof(LineMaterialUniform), + sizeof(LineMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 1*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 1*data.uniformIncrement*sizeof(LineDrawUniform), + sizeof(LineDrawUniform)); + shader.draw(cross); + + shader.bindMaterialBuffer(materialUniform, + 1*data.uniformIncrement*sizeof(LineMaterialUniform), + sizeof(LineMaterialUniform)); + shader.bindTransformationProjectionBuffer(transformationProjectionUniform, + 2*data.uniformIncrement*sizeof(TransformationProjectionUniform3D), + sizeof(TransformationProjectionUniform3D)); + shader.bindDrawBuffer(drawUniform, + 2*data.uniformIncrement*sizeof(LineDrawUniform), + sizeof(LineDrawUniform)); + shader.draw(point); + + /* Otherwise using the draw offset / multidraw */ + } else { + shader.bindTransformationProjectionBuffer(transformationProjectionUniform) + .bindDrawBuffer(drawUniform) + .bindMaterialBuffer(materialUniform); + + if(data.flags >= LineGL3D::Flag::MultiDraw) + shader.draw({wave, cross, point}); + else shader + .setDrawOffset(0) + .draw(wave) + .setDrawOffset(1) + .draw(cross) + .setDrawOffset(2) + .draw(point); + } + + GL::Renderer::disable(GL::Renderer::Feature::Blending); + + 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."); + + /* - Wave should be lower left, red + - Cross lower right, blue + - Point up center, red */ + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(SHADERS_TEST_DIR, "LineTestFiles/multidraw.tga"), + (DebugTools::CompareImageToFile{_manager})); + + /* 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). */ + if(data.flags & LineGL3D::Flag::ObjectId) { + _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}); + _framebuffer.mapForRead(GL::Framebuffer::ColorAttachment{0}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(image.pixels()[5][5], 27); /* Outside */ + CORRADE_COMPARE(image.pixels()[20][20], data.expectedId[0]); /* Wave */ + CORRADE_COMPARE(image.pixels()[20][60], data.expectedId[1]); /* Cross */ + CORRADE_COMPARE(image.pixels()[60][40], data.expectedId[2]); /* Point */ + } +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::LineGLTest) diff --git a/src/Magnum/Shaders/Test/LineGL_Test.cpp b/src/Magnum/Shaders/Test/LineGL_Test.cpp new file mode 100644 index 000000000..a60bb8327 --- /dev/null +++ b/src/Magnum/Shaders/Test/LineGL_Test.cpp @@ -0,0 +1,157 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 +#include +#include + +#include "Magnum/Shaders/LineGL.h" +/* Yes, really */ +#include "Magnum/Shaders/generic.glsl" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +/* 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. */ +struct LineGL_Test: TestSuite::Tester { + explicit LineGL_Test(); + + void glslAttributeMatch(); + + template void constructConfigurationDefault(); + template void constructConfigurationSetters(); + + template void constructNoCreate(); + template void constructCopy(); + + void debugFlag(); + void debugFlags(); + void debugFlagsSupersets(); +}; + +LineGL_Test::LineGL_Test() { + addTests({&LineGL_Test::glslAttributeMatch, + + &LineGL_Test::constructConfigurationDefault<2>, + &LineGL_Test::constructConfigurationDefault<3>, + &LineGL_Test::constructConfigurationSetters<2>, + &LineGL_Test::constructConfigurationSetters<3>, + + &LineGL_Test::constructNoCreate<2>, + &LineGL_Test::constructNoCreate<3>, + &LineGL_Test::constructCopy<2>, + &LineGL_Test::constructCopy<3>, + + &LineGL_Test::debugFlag, + &LineGL_Test::debugFlags, + &LineGL_Test::debugFlagsSupersets}); +} + +void LineGL_Test::glslAttributeMatch() { + /* Position, Color and ObjectId tested in GenericGL_Test */ + + CORRADE_COMPARE(LINE_PREVIOUS_POSITION_ATTRIBUTE_LOCATION, LineGL2D::PreviousPosition::Location); + CORRADE_COMPARE(LINE_PREVIOUS_POSITION_ATTRIBUTE_LOCATION, LineGL3D::PreviousPosition::Location); + + CORRADE_COMPARE(LINE_NEXT_POSITION_ATTRIBUTE_LOCATION, LineGL2D::NextPosition::Location); + CORRADE_COMPARE(LINE_NEXT_POSITION_ATTRIBUTE_LOCATION, LineGL3D::NextPosition::Location); + + CORRADE_COMPARE(LINE_ANNOTATION_ATTRIBUTE_LOCATION, LineGL2D::Annotation::Location); + CORRADE_COMPARE(LINE_ANNOTATION_ATTRIBUTE_LOCATION, LineGL3D::Annotation::Location); +} + +template void LineGL_Test::constructConfigurationDefault() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + typename LineGL::Configuration configuration; + CORRADE_COMPARE(configuration.flags(), typename LineGL::Flags{}); + CORRADE_COMPARE(configuration.materialCount(), 1); + CORRADE_COMPARE(configuration.drawCount(), 1); +} + +template void LineGL_Test::constructConfigurationSetters() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + typename LineGL::Configuration configuration = typename LineGL::Configuration{} + .setFlags(LineGL::Flag::VertexColor) + .setMaterialCount(17) + .setDrawCount(266); + CORRADE_COMPARE(configuration.flags(), LineGL::Flag::VertexColor); + CORRADE_COMPARE(configuration.materialCount(), 17); + CORRADE_COMPARE(configuration.drawCount(), 266); +} + +template void LineGL_Test::constructNoCreate() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + { + LineGL shader{NoCreate}; + CORRADE_COMPARE(shader.id(), 0); + CORRADE_COMPARE(shader.flags(), typename LineGL::Flags{}); + } + + CORRADE_VERIFY(true); +} + +template void LineGL_Test::constructCopy() { + setTestCaseTemplateName(Utility::format("{}", dimensions)); + + CORRADE_VERIFY(!std::is_copy_constructible>{}); + CORRADE_VERIFY(!std::is_copy_assignable>{}); +} + +void LineGL_Test::debugFlag() { + std::ostringstream out; + Debug{&out} << LineGL3D::Flag::VertexColor << LineGL3D::Flag(0xf00d); + CORRADE_COMPARE(out.str(), "Shaders::LineGL::Flag::VertexColor Shaders::LineGL::Flag(0xf00d)\n"); +} + +void LineGL_Test::debugFlags() { + std::ostringstream out; + Debug{&out} << (LineGL3D::Flag::VertexColor|LineGL3D::Flag::InstancedTransformation) << LineGL3D::Flags{}; + CORRADE_COMPARE(out.str(), "Shaders::LineGL::Flag::VertexColor|Shaders::LineGL::Flag::InstancedTransformation Shaders::LineGL::Flags{}\n"); +} + +void LineGL_Test::debugFlagsSupersets() { + /* InstancedObjectId is a superset of ObjectId so only one should be + printed */ + { + std::ostringstream out; + Debug{&out} << (LineGL3D::Flag::ObjectId|LineGL3D::Flag::InstancedObjectId); + CORRADE_COMPARE(out.str(), "Shaders::LineGL::Flag::InstancedObjectId\n"); + + /* MultiDraw is a superset of UniformBuffers so only one should be printed */ + } { + std::ostringstream out; + Debug{&out} << (LineGL3D::Flag::MultiDraw|LineGL3D::Flag::UniformBuffers); + CORRADE_COMPARE(out.str(), "Shaders::LineGL::Flag::MultiDraw\n"); + } +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::LineGL_Test) diff --git a/src/Magnum/Shaders/Test/LineTest.cpp b/src/Magnum/Shaders/Test/LineTest.cpp new file mode 100644 index 000000000..f05e74956 --- /dev/null +++ b/src/Magnum/Shaders/Test/LineTest.cpp @@ -0,0 +1,306 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 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 +#include +#include + +#include "Magnum/Shaders/Line.h" +#include "Magnum/Shaders/Implementation/lineMiterLimit.h" + +namespace Magnum { namespace Shaders { namespace Test { namespace { + +struct LineTest: TestSuite::Tester { + explicit LineTest(); + + template void uniformSizeAlignment(); + + void drawUniformConstructDefault(); + void drawUniformConstructNoInit(); + void drawUniformSetters(); + void drawUniformMaterialIdPacking(); + + void materialUniformConstructDefault(); + void materialUniformConstructNoInit(); + void materialUniformSetters(); + + void materialUniformSetMiterLengthLimitInvalid(); + void materialUniformSetMiterAngleLimitInvalid(); + + void debugCapStyle(); + void debugJoinStyle(); +}; + +using namespace Math::Literals; + +const struct { + const char* name; + Float limit; + const char* message; +} MaterialUniformSetMiterLengthLimitInvalidData[]{ + {"too short", 0.9997f, + "expected a finite value greater than or equal to 1, got 0.9997"}, + {"too long", Constants::inf(), + "expected a finite value greater than or equal to 1, got inf"}, +}; + +const struct { + const char* name; + Rad limit; + const char* message; +} MaterialUniformSetMiterAngleLimitInvalidData[]{ + {"too small", 0.0_degf, + "expected a value greater than 0° and less than or equal to 180°, got 0°"}, + {"too large", 180.1_degf, + "expected a value greater than 0° and less than or equal to 180°, got 180.1°"} +}; + +LineTest::LineTest() { + addTests({&LineTest::uniformSizeAlignment, + &LineTest::uniformSizeAlignment, + + &LineTest::drawUniformConstructDefault, + &LineTest::drawUniformConstructNoInit, + &LineTest::drawUniformSetters, + &LineTest::drawUniformMaterialIdPacking, + + &LineTest::materialUniformConstructDefault, + &LineTest::materialUniformConstructNoInit, + &LineTest::materialUniformSetters}); + + addInstancedTests({&LineTest::materialUniformSetMiterLengthLimitInvalid}, + Containers::arraySize(MaterialUniformSetMiterLengthLimitInvalidData)); + + addInstancedTests({&LineTest::materialUniformSetMiterAngleLimitInvalid}, + Containers::arraySize(MaterialUniformSetMiterAngleLimitInvalidData)); + + addTests({&LineTest::debugCapStyle, + &LineTest::debugJoinStyle}); +} + +template struct UniformTraits; +template<> struct UniformTraits { + static const char* name() { return "LineDrawUniform"; } +}; +template<> struct UniformTraits { + static const char* name() { return "LineMaterialUniform"; } +}; + +template void LineTest::uniformSizeAlignment() { + 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."); + + CORRADE_COMPARE(alignof(T), 4); +} + +void LineTest::drawUniformConstructDefault() { + LineDrawUniform a; + LineDrawUniform b{DefaultInit}; + CORRADE_COMPARE(a.materialId, 0); + CORRADE_COMPARE(b.materialId, 0); + CORRADE_COMPARE(a.objectId, 0); + CORRADE_COMPARE(b.objectId, 0); + + constexpr LineDrawUniform ca; + constexpr LineDrawUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.materialId, 0); + CORRADE_COMPARE(cb.materialId, 0); + CORRADE_COMPARE(ca.objectId, 0); + CORRADE_COMPARE(cb.objectId, 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 LineTest::drawUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + LineDrawUniform a; + a.materialId = 5; + a.objectId = 7; + + new(&a) LineDrawUniform{NoInit}; + { + /* Explicitly check we're not on Clang because certain Clang-based IDEs + inherit __GNUC__ if GCC is used instead of leaving it at 4 like + Clang itself does */ + #if defined(CORRADE_TARGET_GCC) && !defined(CORRADE_TARGET_CLANG) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.objectId, 7); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void LineTest::drawUniformSetters() { + LineDrawUniform a; + a.setMaterialId(5) + .setObjectId(7); + CORRADE_COMPARE(a.materialId, 5); + CORRADE_COMPARE(a.objectId, 7); +} + +void LineTest::drawUniformMaterialIdPacking() { + LineDrawUniform 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 LineTest::materialUniformConstructDefault() { + LineMaterialUniform a; + LineMaterialUniform b{DefaultInit}; + CORRADE_COMPARE(a.backgroundColor, 0x00000000_rgbaf); + CORRADE_COMPARE(b.backgroundColor, 0x00000000_rgbaf); + CORRADE_COMPARE(a.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(b.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(a.width, 1.0f); + CORRADE_COMPARE(b.width, 1.0f); + CORRADE_COMPARE(a.smoothness, 0.0f); + CORRADE_COMPARE(b.smoothness, 0.0f); + CORRADE_COMPARE(a.miterLimit, Implementation::lineMiterLengthLimit("", 4.0f)); + CORRADE_COMPARE(b.miterLimit, Implementation::lineMiterLengthLimit("", 4.0f)); + + constexpr LineMaterialUniform ca; + constexpr LineMaterialUniform cb{DefaultInit}; + CORRADE_COMPARE(ca.backgroundColor, 0x00000000_rgbaf); + CORRADE_COMPARE(cb.backgroundColor, 0x00000000_rgbaf); + CORRADE_COMPARE(ca.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(cb.color, 0xffffffff_rgbaf); + CORRADE_COMPARE(ca.width, 1.0f); + CORRADE_COMPARE(cb.width, 1.0f); + CORRADE_COMPARE(ca.smoothness, 0.0f); + CORRADE_COMPARE(cb.smoothness, 0.0f); + CORRADE_COMPARE(ca.miterLimit, Implementation::lineMiterLengthLimit("", 4.0f)); + CORRADE_COMPARE(cb.miterLimit, Implementation::lineMiterLengthLimit("", 4.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 LineTest::materialUniformConstructNoInit() { + /* Testing only some fields, should be enough */ + LineMaterialUniform a; + a.color = 0x354565fc_rgbaf; + a.smoothness = 7.0f; + + new(&a) LineMaterialUniform{NoInit}; + { + /* Explicitly check we're not on Clang because certain Clang-based IDEs + inherit __GNUC__ if GCC is used instead of leaving it at 4 like + Clang itself does */ + #if defined(CORRADE_TARGET_GCC) && !defined(CORRADE_TARGET_CLANG) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.smoothness, 7.0f); + } + + CORRADE_VERIFY(std::is_nothrow_constructible::value); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} + +void LineTest::materialUniformSetters() { + LineMaterialUniform a; + a.setBackgroundColor(0x01020304_rgbaf) + .setColor(0x354565fc_rgbaf) + .setWidth(2.5f) + .setSmoothness(7.0f) + .setMiterLengthLimit(25.0f); + CORRADE_COMPARE(a.backgroundColor, 0x01020304_rgbaf); + CORRADE_COMPARE(a.color, 0x354565fc_rgbaf); + CORRADE_COMPARE(a.width, 2.5f); + CORRADE_COMPARE(a.smoothness, 7.0f); + CORRADE_COMPARE(a.miterLimit, 0.9968f); + + a.setMiterAngleLimit(35.0_degf); + CORRADE_COMPARE(a.miterLimit, 0.819152f); +} + +void LineTest::materialUniformSetMiterLengthLimitInvalid() { + auto&& data = MaterialUniformSetMiterLengthLimitInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + CORRADE_SKIP_IF_NO_ASSERT(); + + LineMaterialUniform a; + + std::ostringstream out; + Error redirectError{&out}; + a.setMiterLengthLimit(data.limit); + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::LineMaterialUniform::setMiterLengthLimit(): {}\n", data.message)); +} + +void LineTest::materialUniformSetMiterAngleLimitInvalid() { + auto&& data = MaterialUniformSetMiterAngleLimitInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + CORRADE_SKIP_IF_NO_ASSERT(); + + LineMaterialUniform a; + + std::ostringstream out; + Error redirectError{&out}; + a.setMiterAngleLimit(data.limit); + CORRADE_COMPARE(out.str(), Utility::formatString("Shaders::LineMaterialUniform::setMiterAngleLimit(): {}\n", data.message)); +} + +void LineTest::debugCapStyle() { + std::ostringstream out; + Debug{&out} << LineCapStyle::Square << LineCapStyle(0xb0); + CORRADE_COMPARE(out.str(), "Shaders::LineCapStyle::Square Shaders::LineCapStyle(0xb0)\n"); +} + +void LineTest::debugJoinStyle() { + std::ostringstream out; + Debug{&out} << LineJoinStyle::Bevel << LineJoinStyle(0xb0); + CORRADE_COMPARE(out.str(), "Shaders::LineJoinStyle::Bevel Shaders::LineJoinStyle(0xb0)\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Shaders::Test::LineTest) diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-butt-joins-bevel.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-butt-joins-bevel.tga new file mode 100644 index 000000000..61996e73d Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-butt-joins-bevel.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-butt-joins-miter-flat.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-butt-joins-miter-flat.tga new file mode 100644 index 000000000..4d055d579 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-butt-joins-miter-flat.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-round-joins-miter.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-round-joins-miter.tga new file mode 100644 index 000000000..c00f89271 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-round-joins-miter.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-bevel.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-bevel.tga new file mode 100644 index 000000000..b63894021 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-bevel.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-flat.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-flat.tga new file mode 100644 index 000000000..9773d51d0 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-flat.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-36.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-36.tga new file mode 100644 index 000000000..f59b8f368 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-36.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-70deg.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-70deg.tga new file mode 100644 index 000000000..ee401e207 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-70deg.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-91deg.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-91deg.tga new file mode 100644 index 000000000..d9b5ad436 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-91deg.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter.tga new file mode 100644 index 000000000..4f22c49c9 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/caps-triangle-joins-bevel.tga b/src/Magnum/Shaders/Test/LineTestFiles/caps-triangle-joins-bevel.tga new file mode 100644 index 000000000..0bad05c75 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/caps-triangle-joins-bevel.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/cube3D-caps-butt-joins-bevel.tga b/src/Magnum/Shaders/Test/LineTestFiles/cube3D-caps-butt-joins-bevel.tga new file mode 100644 index 000000000..dea2bd922 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/cube3D-caps-butt-joins-bevel.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/cube3D-caps-square-joins-miter.tga b/src/Magnum/Shaders/Test/LineTestFiles/cube3D-caps-square-joins-miter.tga new file mode 100644 index 000000000..4efb08056 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/cube3D-caps-square-joins-miter.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/cube3D-depth.tga b/src/Magnum/Shaders/Test/LineTestFiles/cube3D-depth.tga new file mode 100644 index 000000000..54260cb15 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/cube3D-depth.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/cube3D-flat-single-pixel.tga b/src/Magnum/Shaders/Test/LineTestFiles/cube3D-flat-single-pixel.tga new file mode 100644 index 000000000..55c3aab60 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/cube3D-flat-single-pixel.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/defaults.tga b/src/Magnum/Shaders/Test/LineTestFiles/defaults.tga new file mode 100644 index 000000000..7dedb17f9 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/defaults.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/instanced.tga b/src/Magnum/Shaders/Test/LineTestFiles/instanced.tga new file mode 100644 index 000000000..b2b5cb129 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/instanced.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/multidraw.tga b/src/Magnum/Shaders/Test/LineTestFiles/multidraw.tga new file mode 100644 index 000000000..dc9694bcb Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/multidraw.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/perspective3D.tga b/src/Magnum/Shaders/Test/LineTestFiles/perspective3D.tga new file mode 100644 index 000000000..4e5a89a22 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/perspective3D.tga differ diff --git a/src/Magnum/Shaders/Test/LineTestFiles/vertex-color.tga b/src/Magnum/Shaders/Test/LineTestFiles/vertex-color.tga new file mode 100644 index 000000000..6ead16ec0 Binary files /dev/null and b/src/Magnum/Shaders/Test/LineTestFiles/vertex-color.tga differ diff --git a/src/Magnum/Shaders/generic.glsl b/src/Magnum/Shaders/generic.glsl index 485474cce..abf2ee748 100644 --- a/src/Magnum/Shaders/generic.glsl +++ b/src/Magnum/Shaders/generic.glsl @@ -27,12 +27,15 @@ /* Kept consistent with GenericGL.h (tested in ShadersGenericGL_Test) */ #define POSITION_ATTRIBUTE_LOCATION 0 -#define TEXTURECOORDINATES_ATTRIBUTE_LOCATION 1 +#define TEXTURECOORDINATES_ATTRIBUTE_LOCATION 1 /* also LineAnnotation */ +#define LINE_ANNOTATION_ATTRIBUTE_LOCATION 1 /* also LineAnnotation */ #define COLOR_ATTRIBUTE_LOCATION 2 -#define TANGENT_ATTRIBUTE_LOCATION 3 +#define TANGENT_ATTRIBUTE_LOCATION 3 /* also LinePreviousPosition */ +#define LINE_PREVIOUS_POSITION_ATTRIBUTE_LOCATION 3 /* also Tangent */ #define BITANGENT_ATTRIBUTE_LOCATION 4 /* also ObjectId */ #define OBJECT_ID_ATTRIBUTE_LOCATION 4 /* also Bitangent */ -#define NORMAL_ATTRIBUTE_LOCATION 5 +#define NORMAL_ATTRIBUTE_LOCATION 5 /* also LineNextPosition */ +#define LINE_NEXT_POSITION_ATTRIBUTE_LOCATION 5 /* also Normal */ #define JOINTIDS_ATTRIBUTE_LOCATION 6 #define WEIGHTS_ATTRIBUTE_LOCATION 7 diff --git a/src/Magnum/Shaders/resources-gl.conf b/src/Magnum/Shaders/resources-gl.conf index 1e28aaf8b..f4cdab46b 100644 --- a/src/Magnum/Shaders/resources-gl.conf +++ b/src/Magnum/Shaders/resources-gl.conf @@ -13,6 +13,12 @@ filename=FullScreenTriangle.glsl [file] filename=generic.glsl +[file] +filename=Line.vert + +[file] +filename=Line.frag + [file] filename=MeshVisualizer.vert diff --git a/src/Magnum/Shaders/resources-gles2.conf b/src/Magnum/Shaders/resources-gles2.conf index de8685a25..dbcb2f2a8 100644 --- a/src/Magnum/Shaders/resources-gles2.conf +++ b/src/Magnum/Shaders/resources-gles2.conf @@ -13,6 +13,8 @@ filename=FullScreenTriangle.glsl [file] filename=generic.glsl +# Line.vert and Line.frag missing + [file] filename=MeshVisualizer.vert