From 506841f4a9fbc17235f091ad83e889821bcd4aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 12 Oct 2022 23:06:12 +0200 Subject: [PATCH] Shaders: initial implementation of a line shader. The shader requires the input data to be laid out in a rather specific way, and there will be a dedicated MeshTools utility for it in the following commits. For independence though, the shader tests use a custom helper. The initial implementation has certain corner cases which will be eventually resolved. For now they are pinned down with repro cases in the test. But apart from that, it's pretty much usable in practice. Remaining join styles (round and miter-clip) as well as stipple support will eventually follow as well. --- doc/artwork/line-annotation.svg | 331 ++ doc/artwork/line-caps.svg | 247 ++ doc/artwork/line-joins.svg | 395 +++ doc/artwork/line-quad-data-neighbor.svg | 547 ++++ doc/artwork/line-quad-data-other.svg | 252 ++ doc/artwork/line-quad-data-overlap.svg | 2287 ++++++++++++++ doc/artwork/line-quad-data.svg | 149 + doc/artwork/line-quad-expansion.svg | 408 +++ doc/snippets/MagnumShaders-gl.cpp | 64 + doc/snippets/README.md | 24 +- doc/snippets/line-annotation.svg | 61 + doc/snippets/line-cap-butt.svg | 17 + doc/snippets/line-cap-round.svg | 35 + doc/snippets/line-cap-square.svg | 21 + doc/snippets/line-cap-triangle.svg | 21 + doc/snippets/line-join-bevel.svg | 29 + doc/snippets/line-join-miter.svg | 32 + doc/snippets/line-quad-data-neighbor.svg | 70 + doc/snippets/line-quad-data-other.svg | 34 + .../line-quad-data-overlap-fixedjoin.svg | 107 + .../line-quad-data-overlap-generic.svg | 116 + .../line-quad-data-overlap-miterjoin.svg | 76 + .../line-quad-data-overlap-nojoin.svg | 90 + doc/snippets/line-quad-data.svg | 18 + .../line-quad-expansion-joins-caps.svg | 40 + .../line-quad-expansion-joins-miter-caps.svg | 37 + doc/snippets/line-quad-expansion.svg | 33 + src/Magnum/Shaders/CMakeLists.txt | 13 + .../Shaders/Implementation/lineMiterLimit.h | 55 + src/Magnum/Shaders/Line.cpp | 76 + src/Magnum/Shaders/Line.frag | 257 ++ src/Magnum/Shaders/Line.h | 472 +++ src/Magnum/Shaders/Line.vert | 666 ++++ src/Magnum/Shaders/LineGL.cpp | 432 +++ src/Magnum/Shaders/LineGL.h | 1150 +++++++ src/Magnum/Shaders/Shaders.h | 9 + src/Magnum/Shaders/Test/CMakeLists.txt | 65 + src/Magnum/Shaders/Test/GenericGL_Test.cpp | 2 + src/Magnum/Shaders/Test/LineGLTest.cpp | 2760 +++++++++++++++++ src/Magnum/Shaders/Test/LineGL_Test.cpp | 157 + src/Magnum/Shaders/Test/LineTest.cpp | 306 ++ .../LineTestFiles/caps-butt-joins-bevel.tga | Bin 0 -> 5689 bytes .../caps-butt-joins-miter-flat.tga | Bin 0 -> 2228 bytes .../LineTestFiles/caps-round-joins-miter.tga | Bin 0 -> 7618 bytes .../LineTestFiles/caps-square-joins-bevel.tga | Bin 0 -> 7184 bytes .../caps-square-joins-miter-flat.tga | Bin 0 -> 2809 bytes .../caps-square-joins-miter-limit-36.tga | Bin 0 -> 7227 bytes .../caps-square-joins-miter-limit-70deg.tga | Bin 0 -> 7160 bytes .../caps-square-joins-miter-limit-91deg.tga | Bin 0 -> 7198 bytes .../LineTestFiles/caps-square-joins-miter.tga | Bin 0 -> 7524 bytes .../caps-triangle-joins-bevel.tga | Bin 0 -> 6288 bytes .../cube3D-caps-butt-joins-bevel.tga | Bin 0 -> 8576 bytes .../cube3D-caps-square-joins-miter.tga | Bin 0 -> 9277 bytes .../Test/LineTestFiles/cube3D-depth.tga | Bin 0 -> 3286 bytes .../cube3D-flat-single-pixel.tga | Bin 0 -> 2739 bytes .../Shaders/Test/LineTestFiles/defaults.tga | Bin 0 -> 830 bytes .../Shaders/Test/LineTestFiles/instanced.tga | Bin 0 -> 4184 bytes .../Shaders/Test/LineTestFiles/multidraw.tga | Bin 0 -> 3947 bytes .../Test/LineTestFiles/perspective3D.tga | Bin 0 -> 1528 bytes .../Test/LineTestFiles/vertex-color.tga | Bin 0 -> 3258 bytes src/Magnum/Shaders/generic.glsl | 9 +- src/Magnum/Shaders/resources-gl.conf | 6 + src/Magnum/Shaders/resources-gles2.conf | 2 + 63 files changed, 11969 insertions(+), 9 deletions(-) create mode 100644 doc/artwork/line-annotation.svg create mode 100644 doc/artwork/line-caps.svg create mode 100644 doc/artwork/line-joins.svg create mode 100644 doc/artwork/line-quad-data-neighbor.svg create mode 100644 doc/artwork/line-quad-data-other.svg create mode 100644 doc/artwork/line-quad-data-overlap.svg create mode 100644 doc/artwork/line-quad-data.svg create mode 100644 doc/artwork/line-quad-expansion.svg create mode 100644 doc/snippets/line-annotation.svg create mode 100644 doc/snippets/line-cap-butt.svg create mode 100644 doc/snippets/line-cap-round.svg create mode 100644 doc/snippets/line-cap-square.svg create mode 100644 doc/snippets/line-cap-triangle.svg create mode 100644 doc/snippets/line-join-bevel.svg create mode 100644 doc/snippets/line-join-miter.svg create mode 100644 doc/snippets/line-quad-data-neighbor.svg create mode 100644 doc/snippets/line-quad-data-other.svg create mode 100644 doc/snippets/line-quad-data-overlap-fixedjoin.svg create mode 100644 doc/snippets/line-quad-data-overlap-generic.svg create mode 100644 doc/snippets/line-quad-data-overlap-miterjoin.svg create mode 100644 doc/snippets/line-quad-data-overlap-nojoin.svg create mode 100644 doc/snippets/line-quad-data.svg create mode 100644 doc/snippets/line-quad-expansion-joins-caps.svg create mode 100644 doc/snippets/line-quad-expansion-joins-miter-caps.svg create mode 100644 doc/snippets/line-quad-expansion.svg create mode 100644 src/Magnum/Shaders/Implementation/lineMiterLimit.h create mode 100644 src/Magnum/Shaders/Line.cpp create mode 100644 src/Magnum/Shaders/Line.frag create mode 100644 src/Magnum/Shaders/Line.h create mode 100644 src/Magnum/Shaders/Line.vert create mode 100644 src/Magnum/Shaders/LineGL.cpp create mode 100644 src/Magnum/Shaders/LineGL.h create mode 100644 src/Magnum/Shaders/Test/LineGLTest.cpp create mode 100644 src/Magnum/Shaders/Test/LineGL_Test.cpp create mode 100644 src/Magnum/Shaders/Test/LineTest.cpp create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-butt-joins-bevel.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-butt-joins-miter-flat.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-round-joins-miter.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-bevel.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-flat.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-36.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-70deg.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter-limit-91deg.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-square-joins-miter.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/caps-triangle-joins-bevel.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/cube3D-caps-butt-joins-bevel.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/cube3D-caps-square-joins-miter.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/cube3D-depth.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/cube3D-flat-single-pixel.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/defaults.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/instanced.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/multidraw.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/perspective3D.tga create mode 100644 src/Magnum/Shaders/Test/LineTestFiles/vertex-color.tga 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 0000000000000000000000000000000000000000..61996e73da9616224276c86604f9dfb6d2b8976f GIT binary patch literal 5689 zcmeI0SBPCz6o${a=bDHQLi(il-h1zvRMLCzz4xBXot`$yO)+syViJjFh%M$pkf^AG zN<>i<1u-Ck;EN!F4}zlKzxTa!&aE>61s@!^bI#s-t+m%)>tAIb$9c+$|K&M(PLA_e zojP@bzw?Zpwr$&-6oi)W~Q{rY3Zj5&1Z(3wai;xJpz ztlPM8A96}|w!C_}AUwOY7vVR3PBl8A8U&Yjn+SyNeAi3Ik% zAr;tk+qP|Gz6w_)qF%jvBS(%raNvMhuv%y#%%)SOOv%g3vr;1PT^B=(7A@w@n^#m+ zlqAXR*sBI+vsln~obd?(pHmaVbTxnd4ee<>%)I1Q`}^wRrL3l9G}n zS#H;^UDvN)FEyl2F;BCx#n^dYor~HSjEx>W8YG(qtC3k?-)YmPVaK?j5;)BYU|X^&0uhEv2O7(? zj_km@4!>*HE{XJ*!gOd(=-koT@#Duy7m@f7)ZvUMD}p~Lhe3=#ix%sG8tL+jh6D$p zcaq_+8X{up(xt*|2%(D>Edt#d{l?-Gy?ggQc<>;RL52a`(EiS}(#VV%Gr%+v`v((F zhYlT-^{kr$_``1)-u8#zGQ8yvA2EF1AAZm9sz3aJ zVYxs2k>O>3D5yJt;54Rn(CIzK7yV@ue=H9jI&{yTJtskzi^9-VLYEvERdlB$z|wsL zT_8^x99Pku_URr%XV?<4Y4$I%nm}jpXX(CV zL4J(UolBrI`Nq{cFAN4qA<*Q$fQ zSC|&Qq;zJg03ZT{GP^^bEAZVO7n_;EB~HP4K@3kdV<2MQk13p8^8bWBR+Bs>!~ZMh zQOqj;HEY)FZcUwJN(el3P5h6uT~Q8C1fvOUtGS8gDxQj8xpF0S zrD4N{AExClOW}6urDzY;b9nR~bLY;bJ&k46RISe>@N4AKvlJEvZ)Vg)+ z57KTfWqs8RGvP@es-mijxpCvh5hF%WG?n^OHagJn(`dI0CP# z>c5dus=8AM45}y|lzNGLn(lMJfB|q(vgsUV&z?;Zr`l8dSFBjEdi83c%7H2Jc<}YK z($&$ZnSfIDBaDDfojPG+Ch(ce9;T~ggcCt3dcTrc(1ZySVoi!i-$8#thYz<(Wtas_ zz|E&yw{Emfin3JynT{9*cJAD%Qsp2;YI$5ZXeMdT@VrprV6}5tL?%y_q6>-39vI|B zaRYr6p{3-YChFj3qKs2H0|ySwBE&X?cMuZB8e9-)!|*?zE7cxWjtN)9Z6L*tBWWJ*h-!;4VPKy%rw} zqcuI9x@dK%lP6E+VE2($i{_G;C7qHeEW|N)wPunaT}$Xl-qhLl@81s`$yGIBHhCX^ z!0{H$&e5YsO@o!qFNTK*B90!y<7T5(jc_qCx_nY2T5L(Ot(I1GL|0OBx~|^pU@EGU z9~BQ6DPN>LbClk`Y^qzXdr zdUcY8k}xRY%-l!0!68|R%;QN!Pyi@>QHVTtATN^Q zjLn4@_$YQf%*^EuAF#2xM6sMPDH#q5$Kb((DH+6?;!`olEszY%$<22iV8)2p2B4^T zNkn926vuiQGqBQ&2Z%T#Umx(cslVV9Ptx)r^qMFz#;8%FC{#$)bwd|vU`!4&Y1-Zs z0v(YOsl-wPK`Uk1v;~AZC*b7Z;HD_R1yRi$6iNaf6wm`C&@I2FmJCUjx2X^SiVe?4JSmzlP?R_yQXPK(cIV)`J8z8@ZnewTjZbq5pZfDZ@EZIatoM8T{sx6# BcdGyZ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d055d57902edaa88b536dc630e8156db470c770 GIT binary patch literal 2228 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9773d51d05bdc6b6d8eed4afd64e289f5a3798a0 GIT binary patch literal 2809 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 O%8Ka!5Y+Dn{QDdEf^mlc literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ee401e207461736dc7b08a2bef54d610507a54f3 GIT binary patch literal 7160 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d9b5ad4361c843d5c2512531345a7e3d1ad0d341 GIT binary patch literal 7198 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4f22c49c9ff50d672c9b149465b12fd5933fe4ec GIT binary patch literal 7524 zcmc(k*N+@U5XNVA);8kJIp>^n&N=6tbIv*27wxlgI>6Xq3>=o>OcWrIg#_FKf(Ild zo{$hcgT$WzzpuK_J9oRDT^Z|I$Ab2{c{Z|we1+9X=8#ito|6j^? z(4j+zH--)!TBhIo^y%}E--B-5y6O8azhCEPIA+Y4ix)3GC@wAzylB{@Nt4;LXKN@t zapHtu5VmaDa`o!fwQr0Xs#U8sYu3D8QBe^x%<^&}cJADH_wL;`>XvIX)xUp# zzG`Y}oLtQD&siJEnK*; zK2Gb_tv77g5W_Ka4BEGEU$K@0bIZfr)W+Srci+E%|7#3+gDz`y$dDmt&YZDPr;d$I zo;%bm)+cI&Exp`0(N9&YcrX2{2bZOr;i#7%}4f`SY(Z_q!NiFZOG--aG38CO1uit} z!?@6tgGKAWZrm6{xh8BEnKtT_u~AZv$kb`erl6{->b8W34I9SrOBqEh zTZziDvN9WuLAa=V23ehW2n4^HInA3lU$$(S<;R{qd#>!+s>j7Q^6JhH*UR;@-V`A31X5rc~lM=ggUNg;O#L7&&qz;b}7v5rOb{#)YWO z5HbW#CC2I4XU&=gh)ob?ef##kBdZ}O0#v((h|ilhPky)!GlkqK5aOV2rOfx9Mhsji^MW1 zvVZ#Y>DctA#8Knm!Gk)#6lcD8@nZPlFHKCFHjM;lydolC;X!B&l>5wmfmSa_h61sD z`*vklghOF%n3u?ub|2#powvDm^ytwSE?iK~q;NRJfn=&i=tD+)teYGbQ4o|A(b=F1x+;L|rtiN>E+w8kgJ}*Gj#a(|Dc0waoG67Y-o1Yd&1^ONU645TAQZ zpRB|GFR11xAjL#d@@igP#oQ^VXX(z%xLQ=_C2v0z0suTs` z)RshW#OkTXU@|1_ni#x4cI;RVKpPZv2&(nK&uUdN(6*;*q_t;6W3g@9wo#)-QR!r=(5uxN)Jsy#ocv!CSzF8H-{#)5HzkcHxw1LM7crjm>j6w8&}bFKn6+^hvKhhKus+Scp|Pv z3#}KX>-ascd6nJBIWwJpOIJ5=BTS%4rJ-e8^($Wcyc=1@xQ(oKJFnPAcoN<)1Mma1 zqO)t%rArsuS$aCuqHTq6XaEtl{>-^7@22KUw<##K*4c4GfxatkNLR{LkUe2q%)}M+ zd?GsaYV?P6wgi&3{sr!9q~`Xu9>f`I`_fo*&@whnUqo_nZ(@Km5?`e0`}m?oB?IZK zD*uItjn&+yYDoQ2TEfnpIn&<@qGQL7kxclRoy0F`CU7!+JIy`Krii=HGc}5KJF2yf z@91tkMiDRpPNX=<=!lSlvS@5+r&aX|-ODQE3dFH=BGQ)Y`Pw{X#pf&zsgVUQg9eD9 z@3kz6H0|P!pTYg1AX$-TX=z?rdj}%&U?APp0=)PrAAY5Bh;XEy=mjlJ9AG(7N~~go zHLOXt*m`&8)OGMFatAUd>^Uo-p+pFZgkan*lSx?i#s@`HYxT&a>oti=?O{wY8Ikjx zBKZ#sJjiF6p}7mTZl)0)VOQ>v3K{_ndH7fSec6&g%@X0P<@DFvWvxr;yk~R}z}o$G=(#Eyc4C z&v=A`@+!iG)IX{qwa{+WNl-?tt}oEdT?s_*3@2luingAN`65vXHGu4TpBKIEk>B#|YlO;CV?T=8x4441%zXuSrothG!T~v`T24FsaBG4?b$^@}s#& zCUtKSWu0!2^)Tu~8A~LiDGuNbjw4d3NCdQ!;U2)$m@4q-B54>1Uv(6fsBYy5Z$j#v zHxEPzS!0&uMYRQWpXnk9lEt_VI?=wm7u+QL0`^b>M8qq$VGvHT4lUmXp?guRZNg~@ ziBPPun8<2YMLCKsCZG+Pe{0T?_OPCcgCZ2QM+&DBN92vASJnoi<20d20pMOidSJ0S zox~HcJzxsLOg2a8MT0`!#p6Z`S=*?LDSxSCkS#nx6zFc3xQ+Fs?pax_kfIu^;1r_T zxHxMYy{$zwZPZy<5_x+)N^+O&!TG``c4ec!@Yf>hJub9Tm9>W+O&bxrG$OoO(jO4B zXqU8!b@A@89?6P`B!qNuATU>7}@!)sdP%10uw|HFq4OWDAyGhGeY@Dl5@@1L(T8u&bn=Zdw0Sj$7VnqPnYb>{gd#~@lDm^t{*JruPy?})~xPB2B+hB|n z2*zM+Cm#dBHc1E}IKThwx!Uuj<<-^Iw%~xY z5ccTNW7DQhIm9zJp`O+Pj^Vyx5fEqSzIC}Kx zBS(%r;(_v7Xx6OR^y$;joH=6)4$Bref<=oKv2|sAI6IdvUAnAYyY?}U^#E95zkdC8 z?%Zj!4(N(@VC#`1M;<HaI&LXtr(Jw#QivI8a#-4j(@J;K752fJ+upS5moG@Vm zWWNPOd(N3N2MX3|PY}c{qX-;awrm+3l!~mea61AfP;7Sa;K6(M?iK5e5{`38Ju%S{ z?oot7=Y>>dz|y5lW2!5M!ua?X;|t^y1om{G;)*%ZFy5it$6XDDJyLou-51YN(C zLncP>VVAT5tSu*aN?BQ*{2gG6iR0>l0|&I6VTveWYL3Ja880y#o;`b(3R0e&f^kGY zkAx@)${6B#NUW*`}gk;_Qs7HUu5D9tPY6vH~fBG zI_{MI%ILl-DT0jKaeLRJvdgf7Ow$bE^NqMO*qgB2E#`wBS)IaSWim5q@58adUY zRJnpd$o&a9U$5kz-`1+HgZo>(g@V`UD?VPsN|4j|kp+r}fT$dWf0p)r`XEJNntX;cy(N?H=31-ZgVZlpBpqomV zWc;A~(~DYruw%!LyVIo!qRW$RNN#hn+X0udwQ-oDLAR@l9H4V|y0lr9m6fKuaN)v| zb*l+lNHrjnteEWIzyGy(ioP(fpQV^zB|&!s+eN_^p*G*cvM-UD0aV03!LvzHBK!ZT}30haL*2Tk`)EuiVN zK)VPSp8-p+vlc3LC!i|v5xIwnbmK(Dw&y&Vu(Cl!h7B8*Uf2!CHPx@kIb?Zb(almd zA%>NO5~1|kQb*5msyEDXIaAseELZ?Npq8cE-9X_CmE;Y1NBat4C@}?wm58G^LCC9$ zYWAi~0*|wVhkQJM6H0-wL@0Z6| ztL@sgqh_pMzn=LQ?*yj;o~1mJEIciFE&{4%B7#(uGBS)smA{BQMu?myv!A~cY+kUp z^aAR_Bf`3Q^Jd`hLvdDKE5<&GYuyni_ZHnz6`%fDOSOoO{;#E22xI^>!vqH>N$EwvcW zRROcmj1jpFK`~KJ8x?*GCwgp{UOZJ^B1nXbe7E7WbpfbY(bdt70jjS65y2Qgemr*_ zhetNQ#vaupnR{;ARAzpp1}L&=ey` z0~#PM0U2R$8N}m9x4yK7fRbWjqgG{3ys9lD6uCplNNg6VBx9|#+wq-vN=Fr|sYwd} zm8X(srLa_xR!n2*cVnr@P|P?1!|lmFjh{C}OGRL}qb literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dea2bd922708c11b86020dc45d2476b9224a32aa GIT binary patch literal 8576 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4efb080567556115b91a8a4b114e5167d5cf4ec6 GIT binary patch literal 9277 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..54260cb158e6957a053f06ed0646666596fe4c49 GIT binary patch literal 3286 zcmd6qO>Y!O5QfJ&a`TNpwZDkVu6OO7wT-cjoqz*k5qKe(?+=k2mH@Iy4mqGf;(!q1 z07ADzvyKj68`^T&9e=lMf$YIMu@^!3-(uH7DY+U?OC&r6KG zYiK>R77ri3N_r1{h4Gi@z0l{vwjZ`HLYu)B(#h=f+oW+(i#%^t`@wXW3aS;6m zaabF3($+fX_Udj^wI<^mAFtYIKQ7eymPUlFq z@f>N^FLAdc*2M5-t@kINpO&xo_voqi-}3Q)GrvX7E9BFh>rUi+`2b9+i)Wti+VGlhXIdwdMKQy~tyfakUXr^38WQB?~jxqKm~0t&Tx7$0YQzOjC5&j;@{p^gQPa#t|sS&>r<+nOK1Zw0S! zW1wyob(?I^x=f7K>?-U@*z`TOF7jNBs#CPYUAerhMa{3nufcofcqChMY$oU6hP1F% zk8$sTo5sL%T#qI@Xek!!m;2)^_ar%BU?mMtcUv zvs?!M<3G`t>Gw0;^F^Po897bZ-IX;k7w-u}eDs4kL2JBQFdIBxKyyg@En4>id#2X> zwj{f4G~;u9y0H4ckFJ0359<0bw!au3MxUni0=yZ0V|+{B8p&(#o!`5RwK$CbIrQPx F@n1fLjRpV! literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..55c3aab602e1806c2e996dd3654bdfec7c83da9e GIT binary patch literal 2739 zcmds(%S)9}6vfYN=(F6 zYAv!u-%zLoUG25k*=O&4&e>n343zu~mcg>W>~y=`z52bW*qwgA-|O|h`JaMenO}uN z!V%ls!hNy@`g_3am)}^Ntlbh8;gE6C{Ab(4#zW>mRs0e2AI*~k@s64QV0+Yf!u)$7 zIftJyzhNC&6X%Tib=zacbLQU)i4%U-{2L+b4;n9-e^v4G=3fel6a9ku7uM4+;w_qg zZhPFw>Cn$-LgIv9mfex9gIlw7!gx#g>zJOJL87BT)~9ig%ugGy8B=)er*W)Lp%1M;Y`kKBs(oOd3&Z{#2^Un7 z)qP`QOm*jN->dv|fpt7w@NeuNzc&Bfs=lmG{dfLz|83WoN0Rz^Je=?HKjLqk zXI@jCf197_O`X*5&vIJ8n8C$20c?akFdo{NFjuP9F<;Q}(9vbI);b zw4!4GFjkAlYp!_mWBj2@>BJ*$o@{iCTXb_m>Pns&yV3CtMVGNmy~NA7Uh;Yd6uQf{ z>EG^p7R@uh(ecS)U!EBCxa~Jg^Is2SS8X$&nS(hg<}v;S`zdc!HZPfQi9=iASJAm; vd`;PU+P*d6gzU787P98G`Fs;75&oTUQkWLdVshIQjtjHTj_keqT~X}s5d&_~ literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/defaults.tga b/src/Magnum/Shaders/Test/LineTestFiles/defaults.tga new file mode 100644 index 0000000000000000000000000000000000000000..7dedb17f999bbf1cb7233d7f8948c7e1399abfc5 GIT binary patch literal 830 zcmbu-xeCHi5QX7e+okid@=S__;J$B&rKS6>AXo^3K82m{5NF_@W-yh3aQMzZl6!AL z$OOCM5Ql6?qA2qBN4jYuSIR8N=q0nf-O!*^W3H(bP5T*7Bu!w1~N8{EZ9JirrN zCdc$6Tp=$huF~6gc6! zKm;^s@fHQ~0sKA4ex8!wV>;cc6`eXUU;xqR@`f=9JPWRc=B$*lW_0h)uwfJz^YY8=+Qqtcv~61= zGfFToAiBa3m<%t%XYetc8e$_96i`u7OnrTXy?g1_?J>beG%!0`LnE9AUm^e^`~+80 zBBO8$3&U(q4jVUG_~FA09jc>I4Iem=E?tbFHN5T&|G`~YibCk+B(WHa7EKE^Oqn9F z^MxeCNy^H&@kZm3vSuMLsiXv=EC{)TXA6vW*0E!$t+jequcl?oM+KAFL{(qUq)GB$ zBfK5{^}^nTlS1AYMpzFEG>I*Ky zh*l9XV!=xN>!7Vu=jE||yHOuFK)-%-6R2%s#|}DnH0qP#1E>BQhWScD&4Lx`z0l36 zTeM)+Dr2atW5R?v3Dnl`sw(>QF=}b~liuHAZJENoYU}!880yqAJ#nHjEL}=w=Bxzj z>7gN#bKE#v`n^!=Rlg#lW_AP?&I(7~wBfNf6wI}AW8acaQ| z^-jol>g;URtTF1E8b*(von7I7)5-saowKT8kw8R=FOJ* z(4kbTjH*>8=d{RdSmQZs(i&)KsW;janp!e5S+c}-`IuI^g}4=3n3(q*-{N$>N@6NI z2II$5S7&QWXRfd>K99wWfNEb=vdP-{4nnn)+_x|L_Zxqr!+t0iP2&z3wg4)eH`!s$ zk^--pPkz3v#(0zM)hsDU-mp2a(RmZ?bQHkxd#GTNY2y_h15ZF2JT#u|9-=nokp=f>(y@2a5>hV2bi z-iKsYwd*zTnvHesdQ2;|>oxEiHZP5Z#>AY&Le-N4tG+iP$l*+}%nX8Mno$_6MH-8Y zA{H5_^i2{GPfm5hC^v+cN?~}n$Z<&Hc}3!(_BuMIp%uC(r*kaG$tej11V7TgMddjp zcb;1kv<~mL2x2(pXtSb-(O0VHtdz)Ra^H%duwesFKh2mi6c&<~w=B#AzhE;gf$`8+ zU!fY4Ay+Y%T}J!%#9}MLfUK@3OC1cK?jgSm9bR@nn6eC1LI&ZY=ZZo zozBQ3>tgs$L{|iI8u%72Dv=3k--&O_zcSPg-j@f9&i(_p`V007=@vA>y@qq)3&;KpSDEz%>@356Bwr4WZ4IwE_6Xc<_S;y4^y@31{Zabp zF;dwKU(MsP_jWZj#>nh8c+1I!`&0Acsx>- zW4CQ%r0lRH>>uPbTs{UIxjt1}f)s7t5|An!Z?i@_=cVw2$A6P5J*iPbk|KQ_?S;=H z7jknstPA`T`z>+94jh4+Z(DXb=@e| z)pRC&>ewWe#?{~|6$5?Vu}LcR)hMs4|B)u0Ka*CaiuF@9NFJKbo1{`{kDdv|`e{8s zL7kWD+N7O=nITX^n%i?if*|iuK}JCYsch!<8kb&r{oRX(H)-n~*Ou&0yAmOj*hJ-A k;D)HGK)*0n!&JBl!iasU7T literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/multidraw.tga b/src/Magnum/Shaders/Test/LineTestFiles/multidraw.tga new file mode 100644 index 0000000000000000000000000000000000000000..dc9694bcbd9bf1ebd517a456f890b784f52f9ab2 GIT binary patch literal 3947 zcmbVPU5FM{7@j$E=J(8*pOsCmAhREhAd0%mejxfWDB38x2ui^&vLCt#GK8ql-E~*X z@-tWHLQT6Zj3p{7qHsmKDk=zc?aJ(?y0JjPu;+cx%*It_EOY(e*UWf90S)PA_R|lgvrs=g=hPiiX0COv|?4~ru zinnPX%dSk4+v4~M?B9<8*KB!6>wA*KZ0ypo*?3oyjA(7PZ^y8gBzG&dAqcj6-n8TV z?7ClN*~)#G@hdwoN71uNRo332$;#RWr8Y%TWpf)QZLRMU;`A2Fx@Ze=DoO6_V${mA z6@(bp?e6vcZV=S1@1P}h>pOzrfbZ8aCb7g$(^XQ=FotdaqtyDHG#bZGCdnFdLN#D! zr2N8l8(?qJ!UouLr!zqGaIuQQ4ECV?W?0-6Cc9PugI42och%;KR-D=#S2Am*ZDUYVK zN_on0@YWy}5))gg=Pc_S+cU(!r58{D5M&+%Q-xF5HAI+cg}Gj-uJ3;%Qc|;*$mrgHiN?Wt}e^JB(s ztZn~o+Wkq!OZS`qhdnHzxsB zFBBh(DB29>;joMp5Czb2=$9B^B19wkqFk;OHH6>%iLp0aJy> ziO38p(>fH@^A0g8#2>UyYIBLp3W`=og-;#0rW%ZJ$%{xA77JGA^KH-x2M$f}kfC&P zsO^=Zl(aSuFxA%B-8jHvtJFFiS`Oc^`(mywrL?#4ffdHkJzr*w4}8T*k~?~7o*J-i z9?C0D8y`ryq+x$_f$@OO5E}C8UcxV}F5G&UMGur2YwLl492jhtdmQ0j;xDt>2D`lD zw!x-IJJ1I1hCb#eR5hIEtJ8F?)-Od7*zj8^8$FM3scq8g3mh}U{$rR38;}k8BpXCJb+87^0W9$h<4bwO_xDB7D_W0$)u@Pm z-G2SXUov%Nx$gQw>~z5FTHl+d*F$nB&u(R^lG`o~SMF7jPt$g@+O!DqEPYY}%;bhX xI&G}L*c83uiHwyWs)L>vuw^_9-}nwsD+nf8eenF>{|CWNvNH&`K0gEO{{W^K!ZiQ@ literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/LineTestFiles/perspective3D.tga b/src/Magnum/Shaders/Test/LineTestFiles/perspective3D.tga new file mode 100644 index 0000000000000000000000000000000000000000..4e5a89a2286e35594ad1353a6fbbc3233ab76925 GIT binary patch literal 1528 zcmZQz;9`J*0EPet35NfIf`Tx-5yFuqj) literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6ead16ec08352048df7f2e2be455f48e65ffd981 GIT binary patch literal 3258 zcmai%YjYFl6~}+8m9(~D*T%wJ1U5#13044uxEr(Kk|l&K!hr?_Tq15pqyZU%3K2LC z4up*_vpO^4s>W2&~YJM@QC@CWmzg3zb7GM3rp-Gg5AF!cAR z*eKhH*o!Fd;&-}`Q9-FP!mN1FMCk9(*WV_~21fOmrF054Gt1d}t}FjhlWzoh=0&$d zzcg;Aer+UkzzBRz{s#5V*v6?82b_n~%&5%_GUY|rLw`WceyL{vbys=rsa=ZaI1i}w z^Ba}5PFU8^Tf=-QJXhOUl3u2rF)!sh$_7snVr;__reNeiG$X(>R z7mbAedWHIC9#lrRmDANH5aHgWx++Fn80-$L&t{nto=EZHs3y;g?jRG$P2`Ffy&L-L z6mVu9U{(P&E5!@&7(U1PgkUGXQa!JkZ7H4@Wm1U5dORB(3^bFPcQ5(~xrKa)ob#e` z=ob|Et9i&R39=?JIEXa`_hFo3nPwJ~r}o;C?%b70?g(kYkVuu$g_%-AL z@}3tR3H?>$w)J!#Me7`eimGdI@4_V9A$OkIl9(k^-Z+^4$qW<111XG-HC*?eDx0F$ zO7o&i$S86KdB=+mhW<+Bc`}bDHOFIYGYyx*MbO8ON!KYC`Gr1b$5VFc&_Z^wB&1&6g_3;VZf-y?_8Bbvf zHBdxFh+?&3b3ogE^)RS^~&1r9rgl20}llOkcc$$=GJJlk?}{CVYr< zj!G{-)8*_0*3iyjt_e4Vrj)`FP1NUeWb3@>F!H7{AzCCGLcc@F9=?Li*qT97dJAsA z2XK+0ZA^ga(@dk)EgT)=s>>#!Pu&Fq$JPc+ndP6SB#R)`Y&RWXAiK#n9Mh%A2sS z!WB42A)wKcFjbfJxo4*Mv2a~-mnLgVyG@LeY_0O5-MS&ALDqYb&fYe4`LkCr8QYAO zcwT-9dm{xevmV^fB^`a%A?X+@w*k-h4 ziyFX7a2D%5f(^{Z%juTlzERE#HL=)8Hq-NczQS7$t24hPJ@F zP=+5++GN_^jPFdnttm=Aeu)Q(s8_033zGI0vXK;khoy z-POk>6djjUVPHbyAC zBlhr&rpatR`PMK=R)jbjJf_izwRL-i4X_oqklswWlWC>Qj$3xAXM|nC9$`?1dQGhvgSd=V z!e-b68!4?|+#;^RdbTUSzR7lBhdA4&E1d&>G2T}?aQdML{p43u&m`Ivcfi>=%@$#+ zN`!o z<9YGIA^FB@&9T4L>HivApEcvfKL2^J8~p{CVR>>Qah0tX<9GE`}~xOs<2PooJVx z!Sv!8x`ZWCSTxp1xv_R5MZtqkSVX#mauzdnv*XyM)-x;;I)$9#1}3e0Tl5o3 zztX^^4K5<*d~7_YKoZoXY%{K|95<=dglJO G-v0qt$E^YY literal 0 HcmV?d00001 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