From c2ecaa6abf641c7cf44668bb46afa53667770b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 2 Aug 2021 23:38:40 +0200 Subject: [PATCH] GL: AbstractShaderProgram::draw() that takes plain array views. Because a MeshView might not be the best thing to have when you are submitting a batch of thousand draws. It takes a strided array views to allow for more flexibility, but can also detect if the input is already contiguous and use it as-is. UNFORTUNATELY the GL 1.0 legacy still continues to stink and so there has to be a 64-bit-specific overload which is the *actual* variant that doesn't allocate because glMultiDrawElements takes a `void**` for INDEX OFFSETS and it's IN BYTES! Which foolish soul designed such a thing back in the 1860s, I wonder. There's no reason to not have an index offset in elements because all indices have to have the same type ANYWAY. And yes, I wasted about three hours debugging driver crashes because I THOUGHT this parameter takes offset in elements, not bytes. Also note: on 32-bit platforms this depends on latest Corrade with the CORRADE_TARGET_32BIT definition. Spent an embarrassing amount of time wondering why all local builds but Emscripten work. --- doc/changelog.dox | 3 + src/Magnum/GL/AbstractShaderProgram.cpp | 35 +- src/Magnum/GL/AbstractShaderProgram.h | 91 +++- src/Magnum/GL/Implementation/MeshState.cpp | 14 +- src/Magnum/GL/Implementation/MeshState.h | 6 +- src/Magnum/GL/Mesh.cpp | 201 +++++++++ src/Magnum/GL/Mesh.h | 19 + src/Magnum/GL/MeshView.cpp | 79 ++-- src/Magnum/GL/Test/MeshGLTest.cpp | 458 ++++++++++++++++++++- src/Magnum/Shaders/DistanceFieldVectorGL.h | 11 + src/Magnum/Shaders/FlatGL.h | 11 + src/Magnum/Shaders/MeshVisualizerGL.h | 22 + src/Magnum/Shaders/PhongGL.h | 11 + src/Magnum/Shaders/VectorGL.h | 11 + src/Magnum/Shaders/VertexColorGL.h | 11 + 15 files changed, 895 insertions(+), 88 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 7648e2d92..568d6f97d 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -61,6 +61,9 @@ See also: @subsubsection changelog-latest-new-gl GL library +- New @ref GL::AbstractShaderProgram::draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + overload for data-oriented multi-draw workflows without @ref GL::MeshView + and internal temporary allocations - New @ref GL::Context::Configuration class providing runtime alternatives to the `--magnum-log`, `--magnum-gpu-validation`, `--magnum-disable-extensions` and `--magnum-disable-workarounds` command line options. The class is then diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index 06603814a..c6d39a995 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -26,6 +26,7 @@ #include "AbstractShaderProgram.h" #include +#include #include #include @@ -382,6 +383,38 @@ AbstractShaderProgram& AbstractShaderProgram::draw(MeshView& mesh) { return *this; } +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + if(!counts.size()) return *this; + + use(); + + #ifndef MAGNUM_TARGET_GLES + Mesh::multiDrawImplementationDefault(mesh, counts, vertexOffsets, indexOffsets); + #else + Context::current().state().mesh.multiDrawImplementation(mesh, counts, vertexOffsets, indexOffsets); + #endif + return *this; +} + +#ifndef CORRADE_TARGET_32BIT +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + if(!counts.size()) return *this; + + use(); + + #ifndef MAGNUM_TARGET_GLES + Mesh::multiDrawImplementationDefault(mesh, counts, vertexOffsets, indexOffsets); + #else + Context::current().state().mesh.multiDrawLongImplementation(mesh, counts, vertexOffsets, indexOffsets); + #endif + return *this; +} + +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return draw(mesh, counts, vertexOffsets, Containers::StridedArrayView1D{}); +} +#endif + AbstractShaderProgram& AbstractShaderProgram::draw(Containers::ArrayView> meshes) { if(meshes.empty()) return *this; @@ -396,7 +429,7 @@ AbstractShaderProgram& AbstractShaderProgram::draw(Containers::ArrayView&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * overload below. + * * @see @ref draw(MeshView&), @fn_gl{UseProgram}, * @fn_gl_keyword{EnableVertexAttribArray}, @fn_gl{BindBuffer}, * @fn_gl_keyword{VertexAttribPointer}, @fn_gl_keyword{DisableVertexAttribArray} * or @fn_gl{BindVertexArray}, @fn_gl_keyword{MultiDrawArrays} or * @fn_gl_keyword{MultiDrawElements}/@fn_gl_keyword{MultiDrawElementsBaseVertex} * @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex} + * if the mesh is indexed and the @p baseVertex view is not empty. + * @requires_es_extension OpenGL ES 3.0 and extension + * @gl_extension{OES,draw_elements_base_vertex} or + * @gl_extension{EXT,draw_elements_base_vertex} if the mesh is + * indexed and the @p baseVertex view is not empty. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} + * if the mesh is indexed and the @p baseVertex view is not empty. + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); + + #ifndef CORRADE_TARGET_32BIT + /** + * @brief Draw multiple mesh views at once + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Defined only on 64-bit builds. Compared to @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * this overload can avoid allocating an array of 64-bit pointers for + * the @fn_gl{MultiDrawElements} function and can instead directly + * reuse the @p indexOffsets view if it's contiguous. + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); + + /** + * @overload + * @m_since_latest + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t); + #endif + + /** + * @brief Draw multiple mesh views at once + * @return Reference to self (for method chaining) + * @m_since{2020,06} + * + * Extracts the vertex/index counts, vertex offsets and index offsets + * out of the mesh list and then calls + * @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * (or @ref draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) on 64-bit builds). + * + * On OpenGL ES, if neither @gl_extension{EXT,multi_draw_arrays} nor + * @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * is present, and on WebGL if @webgl_extension{WEBGL,multi_draw} is + * not present, the functionality is instead emulated using a sequence + * of @ref draw(MeshView&) calls. Note that + * @webgl_extension{WEBGL,multi_draw} is only implemented since + * Emscripten 2.0.0, so it's not even advertised on older versions. + * + * @attention All meshes must be views of the same original mesh and + * must not be instanced. + * + * @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex} * if the mesh is indexed and @ref MeshView::baseVertex() is not * `0` * @requires_es_extension OpenGL ES 3.0 and extension diff --git a/src/Magnum/GL/Implementation/MeshState.cpp b/src/Magnum/GL/Implementation/MeshState.cpp index 5fdbd3378..7387d7dba 100644 --- a/src/Magnum/GL/Implementation/MeshState.cpp +++ b/src/Magnum/GL/Implementation/MeshState.cpp @@ -301,9 +301,19 @@ MeshState::MeshState(Context& context, ContextState& contextState, Containers::S } #endif - multiDrawImplementation = &MeshView::multiDrawImplementationDefault; + multiDrawImplementation = &Mesh::multiDrawImplementationDefault; + #ifndef CORRADE_TARGET_32BIT + multiDrawLongImplementation = &Mesh::multiDrawImplementationDefault; + #endif + multiDrawViewImplementation = &MeshView::multiDrawImplementationDefault; - } else multiDrawImplementation = &MeshView::multiDrawImplementationFallback; + } else { + multiDrawImplementation = &Mesh::multiDrawImplementationFallback; + #ifndef CORRADE_TARGET_32BIT + multiDrawLongImplementation = &Mesh::multiDrawImplementationFallback; + #endif + multiDrawViewImplementation = &MeshView::multiDrawImplementationFallback; + } #endif #ifdef MAGNUM_TARGET_GLES2 diff --git a/src/Magnum/GL/Implementation/MeshState.h b/src/Magnum/GL/Implementation/MeshState.h index 5ed0463c2..da8a6ec13 100644 --- a/src/Magnum/GL/Implementation/MeshState.h +++ b/src/Magnum/GL/Implementation/MeshState.h @@ -77,7 +77,11 @@ struct MeshState { #endif #ifdef MAGNUM_TARGET_GLES - void(*multiDrawImplementation)(Containers::ArrayView>); + void(*multiDrawImplementation)(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); + #ifndef CORRADE_TARGET_32BIT + void(*multiDrawLongImplementation)(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); + #endif + void(*multiDrawViewImplementation)(Containers::ArrayView>); void(APIENTRY *multiDrawArraysImplementation)(GLenum, const GLint*, const GLsizei*, GLsizei); void(APIENTRY *multiDrawElementsImplementation)(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei); #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) diff --git a/src/Magnum/GL/Mesh.cpp b/src/Magnum/GL/Mesh.cpp index c236f51eb..9cc3c7de3 100644 --- a/src/Magnum/GL/Mesh.cpp +++ b/src/Magnum/GL/Mesh.cpp @@ -26,6 +26,8 @@ #include "Mesh.h" #include +#include +#include #include #include "Magnum/Mesh.h" @@ -401,6 +403,205 @@ Mesh& Mesh::setIndexBuffer(Buffer& buffer, const GLintptr offset, const MeshInde return *this; } +void Mesh::drawInternal(const Containers::ArrayView& counts, const Containers::ArrayView& vertexOffsets, + #ifdef CORRADE_TARGET_32BIT + const Containers::ArrayView& indexOffsets + #else + const Containers::ArrayView& indexOffsets + #endif +) { + /* Not asserting for _instanceCount == 1, as this is *not* taken from the + original mesh, the counts/vertexOffsets/indexOffsets completely describe + the range being drawn */ + + const Implementation::MeshState& state = Context::current().state().mesh; + (this->*state.bindImplementation)(); + + /* Non-indexed meshes */ + if(!_indexBuffer.id()) { + CORRADE_ASSERT(vertexOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "vertex offset items but got" << vertexOffsets.size(), ); + + #ifndef MAGNUM_TARGET_GLES + glMultiDrawArrays + #else + state.multiDrawArraysImplementation + #endif + (GLenum(_primitive), reinterpret_cast(vertexOffsets.data()), reinterpret_cast(counts.data()), counts.size()); + + /* Indexed meshes */ + } else { + CORRADE_ASSERT(indexOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "index offset items but got" << indexOffsets.size(), ); + + /* Indexed meshes */ + if(vertexOffsets.empty()) { + #ifndef MAGNUM_TARGET_GLES + glMultiDrawElements + #else + state.multiDrawElementsImplementation + #endif + (GLenum(_primitive), reinterpret_cast(counts.data()), GLenum(_indexType), reinterpret_cast(indexOffsets.data()), counts.size()); + + /* Indexed meshes with base vertex */ + } else { + CORRADE_ASSERT(vertexOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "vertex offset items but got" << vertexOffsets.size(), ); + + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + #ifndef MAGNUM_TARGET_GLES + glMultiDrawElementsBaseVertex + #else + state.multiDrawElementsBaseVertexImplementation + #endif + (GLenum(_primitive), reinterpret_cast(counts.data()), GLenum(_indexType), reinterpret_cast(indexOffsets.data()), counts.size(), reinterpret_cast(vertexOffsets.data())); + #else + CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): indexed mesh multi-draw with base vertex specification possible only since WebGL 2.0", ); + #endif + } + } + + (this->*state.unbindImplementation)(); +} + +void Mesh::multiDrawImplementationDefault(Mesh& self, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + #ifdef CORRADE_TARGET_32BIT + /* If all views are contiguous and we're on 32-bit, call the implementation + directly */ + if(counts.isContiguous() && vertexOffsets.isContiguous() && indexOffsets.isContiguous()) + return self.drawInternal(counts.asContiguous(), vertexOffsets.asContiguous(), indexOffsets.asContiguous()); + #endif + + /* Otherwise allocate contiguous copies. While it's possible that some + views could have been contigous already and some not, such scenario is + unlikely to make a practical sense, so we'll allocate & copy always. */ + Containers::ArrayView countsContiguous; + Containers::ArrayView vertexOffsetsContiguous; + #ifdef CORRADE_TARGET_32BIT + Containers::ArrayView + #else + Containers::ArrayView + #endif + indexOffsetsContiguous; + Containers::ArrayTuple data{ + {NoInit, counts.size(), countsContiguous}, + {NoInit, vertexOffsets.size(), vertexOffsetsContiguous}, + /* On 64-bit we'll be filling just the lower 32 bits so zero-init the + array. On 32-bit we'll overwrite it completely, so NoInit is fine. */ + { + #ifdef CORRADE_TARGET_32BIT + NoInit + #else + ValueInit + #endif + , indexOffsets.size(), indexOffsetsContiguous} + }; + Utility::copy(counts, countsContiguous); + Utility::copy(vertexOffsets, vertexOffsetsContiguous); + Utility::copy(indexOffsets, + #ifdef CORRADE_TARGET_32BIT + indexOffsetsContiguous + #else + /* Write to the lower 32 bits of the index offsets, which is the + leftmost bits on Little-Endian and rightmost on Big-Endian. On LE it + could be just Containers::arrayCast(indexOffsets) + but to minimize a chance of error on BE platforms that are hard to + test on, the same code is used for both. */ + Containers::arrayCast<2, UnsignedInt>(stridedArrayView(indexOffsetsContiguous)).transposed<0, 1>()[ + #ifndef CORRADE_TARGET_BIG_ENDIAN + 0 + #else + 1 + #endif + ] + #endif + ); + + self.drawInternal(countsContiguous, vertexOffsetsContiguous, indexOffsetsContiguous); +} + +#ifndef CORRADE_TARGET_32BIT +void Mesh::multiDrawImplementationDefault(Mesh& self, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + /* If all views are contiguous, call the implementation directly */ + if(counts.isContiguous() && vertexOffsets.isContiguous() && indexOffsets.isContiguous()) + return self.drawInternal(counts.asContiguous(), vertexOffsets.asContiguous(), indexOffsets.asContiguous()); + + /* Otherwise delegate into the 32-bit variant, which will allocate a + contiguous copy */ + multiDrawImplementationDefault(self, counts, vertexOffsets, + /* Get the lower 32 bits of the index offsets, which is the leftmost + bits on Little-Endian and rightmost on Big-Endian. On LE it could be + just Containers::arrayCast(indexOffsets) but to + minimize a chance of error on BE platforms that are hard to test on, + the same code is used for both. */ + Containers::arrayCast<2, const UnsignedInt>(indexOffsets).transposed<0, 1>()[ + #ifndef CORRADE_TARGET_BIG_ENDIAN + 0 + #else + 1 + #endif + ] + ); +} +#endif + +#ifdef MAGNUM_TARGET_GLES +void Mesh::multiDrawImplementationFallback(Mesh& self, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + const UnsignedInt zero[1]{}; + Containers::StridedArrayView1D indexOffsetsNeverEmpty; + Containers::StridedArrayView1D vertexOffsetsNeverEmpty; + if(self._indexBuffer.id()) { + CORRADE_ASSERT(indexOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "index offset items but got" << indexOffsets.size(), ); + indexOffsetsNeverEmpty = indexOffsets; + + if(!vertexOffsets.empty()) { + CORRADE_ASSERT(vertexOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "vertex offset items but got" << vertexOffsets.size(), ); + vertexOffsetsNeverEmpty = vertexOffsets; + } else vertexOffsetsNeverEmpty = Containers::StridedArrayView1D{zero, counts.size(), 0}; + } else { + CORRADE_ASSERT(vertexOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "vertex offset items but got" << vertexOffsets.size(), ); + vertexOffsetsNeverEmpty = vertexOffsets; + indexOffsetsNeverEmpty = Containers::StridedArrayView1D{zero, counts.size(), 0}; + } + + for(std::size_t i = 0; i != counts.size(); ++i) { + if(!counts[i]) continue; + + self.drawInternal( + counts[i], vertexOffsetsNeverEmpty[i], 1, + #ifndef MAGNUM_TARGET_GLES2 + 0, indexOffsetsNeverEmpty[i], 0, 0 + #else + indexOffsetsNeverEmpty[i] + #endif + ); + } +} + +#ifndef CORRADE_TARGET_32BIT +void Mesh::multiDrawImplementationFallback(Mesh& self, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + /* Delegate straight into the 32-bit variant */ + multiDrawImplementationFallback(self, counts, vertexOffsets, + /* Get the lower 32 bits of the index offsets, which is the leftmost + bits on Little-Endian and rightmost on Big-Endian. On LE it could be + just Containers::arrayCast(indexOffsets) but to + minimize a chance of error on BE platforms that are hard to test on, + the same code is used for both. */ + Containers::arrayCast<2, const UnsignedInt>(indexOffsets).transposed<0, 1>()[ + #ifndef CORRADE_TARGET_BIG_ENDIAN + 0 + #else + 1 + #endif + ] + ); +} +#endif +#endif + #ifndef MAGNUM_TARGET_GLES2 void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, UnsignedInt baseInstance, GLintptr indexOffset, Int indexStart, Int indexEnd) #else diff --git a/src/Magnum/GL/Mesh.h b/src/Magnum/GL/Mesh.h index 7b77a0e4e..e919bed5d 100644 --- a/src/Magnum/GL/Mesh.h +++ b/src/Magnum/GL/Mesh.h @@ -1089,6 +1089,14 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject { void drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr indexOffset); #endif + void drawInternal(const Containers::ArrayView& counts, const Containers::ArrayView& vertexOffsets, + #ifdef CORRADE_TARGET_32BIT + const Containers::ArrayView& indexOffsets + #else + const Containers::ArrayView& indexOffsets + #endif + ); + #ifndef MAGNUM_TARGET_GLES void drawInternal(TransformFeedback& xfb, UnsignedInt stream, Int instanceCount); #endif @@ -1146,6 +1154,17 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject { void MAGNUM_GL_LOCAL unbindImplementationDefault(); void MAGNUM_GL_LOCAL unbindImplementationVAO(); + MAGNUM_GL_LOCAL static void multiDrawImplementationDefault(Mesh& self, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); + #ifndef CORRADE_TARGET_32BIT + MAGNUM_GL_LOCAL static void multiDrawImplementationDefault(Mesh& self, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); + #endif + #ifdef MAGNUM_TARGET_GLES + MAGNUM_GL_LOCAL static void multiDrawImplementationFallback(Mesh& self, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); + #ifndef CORRADE_TARGET_32BIT + MAGNUM_GL_LOCAL static void multiDrawImplementationFallback(Mesh& self, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); + #endif + #endif + #ifdef MAGNUM_TARGET_GLES #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) #if defined(MAGNUM_TARGET_WEBGL) && __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 13915 diff --git a/src/Magnum/GL/MeshView.cpp b/src/Magnum/GL/MeshView.cpp index 98aa215ad..04f847e59 100644 --- a/src/Magnum/GL/MeshView.cpp +++ b/src/Magnum/GL/MeshView.cpp @@ -88,66 +88,37 @@ MeshView& MeshView::draw(AbstractShaderProgram&& shader, TransformFeedback& xfb, void MeshView::multiDrawImplementationDefault(Containers::ArrayView> meshes) { CORRADE_INTERNAL_ASSERT(meshes.size()); - const Implementation::MeshState& state = Context::current().state().mesh; - - Mesh& original = meshes.begin()->get()._original; - Containers::Array count{meshes.size()}; - Containers::Array indices{meshes.size()}; - Containers::Array baseVertex{meshes.size()}; + Mesh& original = meshes[0]->_original; /* Gather the parameters */ - bool hasBaseVertex = false; - std::size_t i = 0; - for(MeshView& mesh: meshes) { - CORRADE_ASSERT(mesh._instanceCount == 1, "GL::AbstractShaderProgram::draw(): cannot multi-draw instanced meshes", ); - - count[i] = mesh._count; - indices[i] = reinterpret_cast(mesh._indexOffset); - baseVertex[i] = mesh._baseVertex; - - if(mesh._baseVertex) hasBaseVertex = true; - - ++i; - } - - (original.*state.bindImplementation)(); - - /* Non-indexed meshes */ - if(!original._indexBuffer.id()) { - #ifndef MAGNUM_TARGET_GLES - glMultiDrawArrays + Containers::ArrayView counts; + Containers::ArrayView vertexOffsets; + Containers::ArrayView< + #ifndef CORRADE_TARGET_32BIT + UnsignedLong #else - state.multiDrawArraysImplementation + UnsignedInt #endif - (GLenum(original._primitive), baseVertex, count, meshes.size()); - - /* Indexed meshes */ - } else { - /* Indexed meshes with base vertex */ - if(hasBaseVertex) { - #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) - #ifndef MAGNUM_TARGET_GLES - glMultiDrawElementsBaseVertex - #else - state.multiDrawElementsBaseVertexImplementation - #endif - (GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size(), baseVertex); - #else - CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): indexed mesh multi-draw with base vertex specification possible only since WebGL 2.0", ); - #endif - - /* Indexed meshes */ - } else { - #ifndef MAGNUM_TARGET_GLES - glMultiDrawElements - #else - state.multiDrawElementsImplementation - #endif - (GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size()); - } + > indexOffsets; + Containers::ArrayTuple data{ + {NoInit, meshes.size(), counts}, + {NoInit, meshes.size(), vertexOffsets}, + {NoInit, meshes.size(), indexOffsets} + }; + + /* The vertexOffsets array is used for non-indexed meshes or if the base + vertex is specified for any of the meshes */ + bool useVertexOffsets = !original.isIndexed(); + for(std::size_t i = 0; i != meshes.size(); ++i) { + CORRADE_ASSERT(meshes[i]->_instanceCount == 1, "GL::AbstractShaderProgram::draw(): cannot multi-draw instanced meshes", ); + + counts[i] = meshes[i]->_count; + vertexOffsets[i] = meshes[i]->_baseVertex; + indexOffsets[i] = meshes[i]->_indexOffset; + if(meshes[i]->_baseVertex) useVertexOffsets = true; } - (original.*state.unbindImplementation)(); + original.drawInternal(counts, useVertexOffsets ? vertexOffsets : nullptr, indexOffsets); } #ifdef MAGNUM_TARGET_GLES diff --git a/src/Magnum/GL/Test/MeshGLTest.cpp b/src/Magnum/GL/Test/MeshGLTest.cpp index d58ee93b2..991c76dc5 100644 --- a/src/Magnum/GL/Test/MeshGLTest.cpp +++ b/src/Magnum/GL/Test/MeshGLTest.cpp @@ -184,12 +184,20 @@ struct MeshGLTest: OpenGLTester { void resetDivisorAfterInstancedDraw(); void multiDraw(); + void multiDrawSparseArrays(); + void multiDrawViews(); void multiDrawIndexed(); + void multiDrawIndexedSparseArrays(); + void multiDrawIndexedViews(); + void multiDrawWrongVertexOffsetSize(); + void multiDrawIndexedWrongIndexOffsetSize(); + void multiDrawIndexedWrongVertexOffsetSize(); #ifdef MAGNUM_TARGET_GLES void multiDrawIndexedBaseVertexNoExtensionAvailable(); + void multiDrawIndexedViewsBaseVertexNoExtensionAvailable(); #endif - void multiDrawInstanced(); - void multiDrawDifferentMeshes(); + void multiDrawInstancedViews(); + void multiDrawViewsDifferentMeshes(); }; const struct { @@ -280,7 +288,12 @@ const struct { Vector4 values[4]; UnsignedInt indices[4]; UnsignedInt counts[4]; - UnsignedInt indexOffsets[4]; + #ifdef CORRADE_TARGET_32BIT + UnsignedInt + #else + UnsignedLong + #endif + indexOffsetsInBytes[4]; UnsignedInt vertexOffsets[4]; Vector4 expected; } MultiDrawIndexedData[] { @@ -301,7 +314,7 @@ const struct { {1.0f, 0.0f, 0.0f, 0.0f}}, {0, 1, 2, 3}, {1, 1, 1, 1}, - {0, 1, 2, 3}, + {0, 4, 8, 12}, {0, 0, 0, 0}, {0.25f, 0.5f, 0.75f, 1.0f}}, #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) @@ -312,7 +325,7 @@ const struct { {1.0f, 0.0f, 0.0f, 0.0f}}, {0, 1, 0, 1}, {1, 1, 1, 1}, - {0, 1, 2, 3}, + {0, 4, 8, 12}, {0, 0, 2, 2}, {0.25f, 0.5f, 0.75f, 1.0f}}, #ifndef MAGNUM_TARGET_GLES2 @@ -325,7 +338,7 @@ const struct { {0.0f, 0.0f, 0.0f, 1.0f}}, {0, 1, 0, 1}, {1, 1, 1, 1}, - {0, 1, 2, 3}, + {0, 4, 8, 12}, {0, 0, 2, 2}, {0.25f, 0.5f, 0.75f, 1.0f}}, #endif @@ -337,7 +350,7 @@ const struct { {1.0f, 0.0f, 0.0f, 0.0f}}, {5, 1, 0, 3}, {1, 0, 1, 1}, - {3, 0, 2, 1}, + {12, 0, 8, 4}, {0, 0, 0, 0}, /* The positions are fixed so this still renders in the same order */ {0.25f, 0.5f, 0.0f, 1.0f}} @@ -482,18 +495,26 @@ MeshGLTest::MeshGLTest() { #endif &MeshGLTest::resetDivisorAfterInstancedDraw}); - addInstancedTests({&MeshGLTest::multiDraw}, + addInstancedTests({&MeshGLTest::multiDraw, + &MeshGLTest::multiDrawSparseArrays, + &MeshGLTest::multiDrawViews}, Containers::arraySize(MultiDrawData)); - addInstancedTests({&MeshGLTest::multiDrawIndexed}, + addInstancedTests({&MeshGLTest::multiDrawIndexed, + &MeshGLTest::multiDrawIndexedSparseArrays, + &MeshGLTest::multiDrawIndexedViews}, Containers::arraySize(MultiDrawIndexedData)); addTests({ + &MeshGLTest::multiDrawWrongVertexOffsetSize, + &MeshGLTest::multiDrawIndexedWrongIndexOffsetSize, + &MeshGLTest::multiDrawIndexedWrongVertexOffsetSize, #ifdef MAGNUM_TARGET_GLES &MeshGLTest::multiDrawIndexedBaseVertexNoExtensionAvailable, + &MeshGLTest::multiDrawIndexedViewsBaseVertexNoExtensionAvailable, #endif - &MeshGLTest::multiDrawInstanced, - &MeshGLTest::multiDrawDifferentMeshes + &MeshGLTest::multiDrawInstancedViews, + &MeshGLTest::multiDrawViewsDifferentMeshes }); /* Reset clear color to something trivial first */ @@ -3550,6 +3571,148 @@ void MeshGLTest::multiDraw() { Mesh mesh{MeshPrimitive::Points}; mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawShader::Position{}, MultiDrawShader::Value{}); + MAGNUM_VERIFY_NO_GL_ERROR(); + + MultiDrawChecker checker; + MultiDrawShader{data.vertexId, data.drawId}.draw(mesh, data.counts, data.vertexOffsets, nullptr); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); + #else + CORRADE_COMPARE_WITH(value, data.expected, /* it's only RGBA4 */ + TestSuite::Compare::around(Vector4{1.0f/15.0f})); + #endif +} + +void MeshGLTest::multiDrawSparseArrays() { + auto&& data = MultiDrawData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(data.vertexId && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("gl_VertexID not supported"); + #endif + + if(data.drawId) { + #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 + } + + #ifdef MAGNUM_TARGET_GLES + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported() && + !Context::current().isExtensionSupported()) + CORRADE_INFO("Neither" << Extensions::EXT::multi_draw_arrays::string() << "nor" << Extensions::ANGLE::multi_draw::string() << "is supported, using fallback implementation"); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_INFO(Extensions::WEBGL::multi_draw::string() << "is not supported, using fallback implementation"); + #endif + #endif + + const struct { + Vector2 position; + Vector4 value; + } vertexData[] { + {}, /* initial offset */ + {{-1.0f/3.0f, -1.0f/3.0f}, data.values[0]}, + {{ 1.0f/3.0f, -1.0f/3.0f}, data.values[1]}, + {{-1.0f/3.0f, 1.0f/3.0f}, data.values[2]}, + {{ 1.0f/3.0f, 1.0f/3.0f}, data.values[3]}, + }; + + Mesh mesh{MeshPrimitive::Points}; + mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawShader::Position{}, MultiDrawShader::Value{}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* The signature accepted by glMultiDrawArraysIndirect() */ + struct Command { + UnsignedInt count; + UnsignedInt instanceCount; + UnsignedInt first; + UnsignedInt baseInstance; + } commands[] { + {data.counts[0], 0, data.vertexOffsets[0], 0}, + {data.counts[1], 0, data.vertexOffsets[1], 0}, + {data.counts[2], 0, data.vertexOffsets[2], 0}, + {data.counts[3], 0, data.vertexOffsets[3], 0}, + }; + + MultiDrawChecker checker; + MultiDrawShader{data.vertexId, data.drawId}.draw(mesh, + Containers::stridedArrayView(commands).slice(&Command::count), + Containers::stridedArrayView(commands).slice(&Command::first), + nullptr); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); + #else + CORRADE_COMPARE_WITH(value, data.expected, /* it's only RGBA4 */ + TestSuite::Compare::around(Vector4{1.0f/15.0f})); + #endif +} + +void MeshGLTest::multiDrawViews() { + auto&& data = MultiDrawData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(data.vertexId && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("gl_VertexID not supported"); + #endif + + if(data.drawId) { + #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 + } + + #ifdef MAGNUM_TARGET_GLES + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported() && + !Context::current().isExtensionSupported()) + CORRADE_INFO("Neither" << Extensions::EXT::multi_draw_arrays::string() << "nor" << Extensions::ANGLE::multi_draw::string() << "is supported, using fallback implementation"); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_INFO(Extensions::WEBGL::multi_draw::string() << "is not supported, using fallback implementation"); + #endif + #endif + + const struct { + Vector2 position; + Vector4 value; + } vertexData[] { + {}, /* initial offset */ + {{-1.0f/3.0f, -1.0f/3.0f}, data.values[0]}, + {{ 1.0f/3.0f, -1.0f/3.0f}, data.values[1]}, + {{-1.0f/3.0f, 1.0f/3.0f}, data.values[2]}, + {{ 1.0f/3.0f, 1.0f/3.0f}, data.values[3]}, + }; + + Mesh mesh{MeshPrimitive::Points}; + mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawShader::Position{}, MultiDrawShader::Value{}); + MeshView a{mesh}, b{mesh}, c{mesh}, d{mesh}; a.setCount(data.counts[0]) .setBaseVertex(data.vertexOffsets[0]); @@ -3585,7 +3748,76 @@ void MeshGLTest::multiDrawIndexed() { CORRADE_SKIP("gl_VertexID not supported"); #endif - if(data.vertexOffsets[0] || data.vertexOffsets[1] || data.vertexOffsets[2] || data.vertexOffsets[3]) { + bool hasBaseVertex = data.vertexOffsets[0] || data.vertexOffsets[1] || data.vertexOffsets[2] || data.vertexOffsets[3]; + if(hasBaseVertex) { + #ifndef MAGNUM_TARGET_GLES + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!Context::current().isExtensionSupported() && + !Context::current().isExtensionSupported()) + CORRADE_SKIP(std::string{"Neither "} + Extensions::OES::draw_elements_base_vertex::string() + " nor " + Extensions::EXT::draw_elements_base_vertex::string() + " is available."); + #elif !defined(MAGNUM_TARGET_GLES2) + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."}); + #else + CORRADE_FAIL_IF(false, "Can't do base vertex here."); + #endif + } + + #ifdef MAGNUM_TARGET_GLES + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported() && + !Context::current().isExtensionSupported()) + CORRADE_INFO("Neither" << Extensions::EXT::multi_draw_arrays::string() << "nor" << Extensions::ANGLE::multi_draw::string() << "is supported, using fallback implementation"); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_INFO(Extensions::WEBGL::multi_draw::string() << "is not supported, using fallback implementation"); + #endif + #endif + + const struct { + Vector2 position; + Vector4 value; + } vertexData[] { + {}, /* initial offset */ + {{-1.0f/3.0f, -1.0f/3.0f}, data.values[0]}, + {{ 1.0f/3.0f, -1.0f/3.0f}, data.values[1]}, + {{-1.0f/3.0f, 1.0f/3.0f}, data.values[2]}, + {{ 1.0f/3.0f, 1.0f/3.0f}, data.values[3]}, + }; + + Mesh mesh{MeshPrimitive::Points}; + mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawShader::Position{}, MultiDrawShader::Value{}) + .setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, data.indices}, 0, MeshIndexType::UnsignedInt); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + MultiDrawChecker checker; + MultiDrawShader{data.vertexId, false}.draw(mesh, data.counts, hasBaseVertex ? Containers::arrayView(data.vertexOffsets) : nullptr, data.indexOffsetsInBytes); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); + #else + CORRADE_COMPARE_WITH(value, data.expected, /* it's only RGBA4 */ + TestSuite::Compare::around(Vector4{1.0f/15.0f})); + #endif +} + +void MeshGLTest::multiDrawIndexedSparseArrays() { + auto&& data = MultiDrawIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(data.vertexId && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("gl_VertexID not supported"); + #endif + + bool hasBaseVertex = data.vertexOffsets[0] || data.vertexOffsets[1] || data.vertexOffsets[2] || data.vertexOffsets[3]; + if(hasBaseVertex) { #ifndef MAGNUM_TARGET_GLES if(!Context::current().isExtensionSupported()) CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() << "is not supported."); @@ -3593,9 +3825,96 @@ void MeshGLTest::multiDrawIndexed() { if(!Context::current().isExtensionSupported() && !Context::current().isExtensionSupported()) CORRADE_SKIP(std::string{"Neither "} + Extensions::OES::draw_elements_base_vertex::string() + " nor " + Extensions::EXT::draw_elements_base_vertex::string() + " is available."); + #elif !defined(MAGNUM_TARGET_GLES2) + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."}); #else + CORRADE_FAIL_IF(false, "Can't do base vertex here."); + #endif + } + + #ifdef MAGNUM_TARGET_GLES + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported() && + !Context::current().isExtensionSupported()) + CORRADE_INFO("Neither" << Extensions::EXT::multi_draw_arrays::string() << "nor" << Extensions::ANGLE::multi_draw::string() << "is supported, using fallback implementation"); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_INFO(Extensions::WEBGL::multi_draw::string() << "is not supported, using fallback implementation"); + #endif + #endif + + const struct { + Vector2 position; + Vector4 value; + } vertexData[] { + {}, /* initial offset */ + {{-1.0f/3.0f, -1.0f/3.0f}, data.values[0]}, + {{ 1.0f/3.0f, -1.0f/3.0f}, data.values[1]}, + {{-1.0f/3.0f, 1.0f/3.0f}, data.values[2]}, + {{ 1.0f/3.0f, 1.0f/3.0f}, data.values[3]}, + }; + + Mesh mesh{MeshPrimitive::Points}; + mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawShader::Position{}, MultiDrawShader::Value{}) + .setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, data.indices}, 0, MeshIndexType::UnsignedInt); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* The signature accepted by glMultiDrawElementsIndirect() EXCEPT that + here we need firstIndex to be in bytes */ + struct Command { + UnsignedInt count; + UnsignedInt instanceCount; + UnsignedInt firstIndexInBytes; /* !! */ + UnsignedInt baseVertex; + UnsignedInt baseInstance; + } commands[] { + {data.counts[0], 0, UnsignedInt(data.indexOffsetsInBytes[0]), data.vertexOffsets[0], 0}, + {data.counts[1], 0, UnsignedInt(data.indexOffsetsInBytes[1]), data.vertexOffsets[1], 0}, + {data.counts[2], 0, UnsignedInt(data.indexOffsetsInBytes[2]), data.vertexOffsets[2], 0}, + {data.counts[3], 0, UnsignedInt(data.indexOffsetsInBytes[3]), data.vertexOffsets[3], 0} + }; + + MultiDrawChecker checker; + MultiDrawShader{data.vertexId, false}.draw(mesh, + Containers::stridedArrayView(commands).slice(&Command::count), + hasBaseVertex ? Containers::stridedArrayView(commands).slice(&Command::baseVertex) : nullptr, + Containers::stridedArrayView(commands).slice(&Command::firstIndexInBytes)); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); + #else + CORRADE_COMPARE_WITH(value, data.expected, /* it's only RGBA4 */ + TestSuite::Compare::around(Vector4{1.0f/15.0f})); + #endif +} + +void MeshGLTest::multiDrawIndexedViews() { + auto&& data = MultiDrawIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES2 + if(data.vertexId && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("gl_VertexID not supported"); + #endif + + if(data.vertexOffsets[0] || data.vertexOffsets[1] || data.vertexOffsets[2] || data.vertexOffsets[3]) { + #ifndef MAGNUM_TARGET_GLES + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ARB::draw_elements_base_vertex::string() << "is not supported."); + #elif !defined(MAGNUM_TARGET_WEBGL) + if(!Context::current().isExtensionSupported() && + !Context::current().isExtensionSupported()) + CORRADE_SKIP(std::string{"Neither "} + Extensions::OES::draw_elements_base_vertex::string() + " nor " + Extensions::EXT::draw_elements_base_vertex::string() + " is available."); + #elif !defined(MAGNUM_TARGET_GLES2) if(!Context::current().isExtensionSupported()) CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() + std::string{" is not available."}); + #else + CORRADE_FAIL_IF(false, "Can't do base vertex here."); #endif } @@ -3627,16 +3946,16 @@ void MeshGLTest::multiDrawIndexed() { MeshView a{mesh}, b{mesh}, c{mesh}, d{mesh}; a.setCount(data.counts[0]) - .setIndexRange(data.indexOffsets[0]) + .setIndexRange(data.indexOffsetsInBytes[0]/sizeof(UnsignedInt)) .setBaseVertex(data.vertexOffsets[0]); b.setCount(data.counts[1]) - .setIndexRange(data.indexOffsets[1]) + .setIndexRange(data.indexOffsetsInBytes[1]/sizeof(UnsignedInt)) .setBaseVertex(data.vertexOffsets[1]); c.setCount(data.counts[2]) - .setIndexRange(data.indexOffsets[2]) + .setIndexRange(data.indexOffsetsInBytes[2]/sizeof(UnsignedInt)) .setBaseVertex(data.vertexOffsets[2]); d.setCount(data.counts[3]) - .setIndexRange(data.indexOffsets[3]) + .setIndexRange(data.indexOffsetsInBytes[3]/sizeof(UnsignedInt)) .setBaseVertex(data.vertexOffsets[3]); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -3655,6 +3974,64 @@ void MeshGLTest::multiDrawIndexed() { #endif } +void MeshGLTest::multiDrawWrongVertexOffsetSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh mesh; + MultiDrawShader shader; + UnsignedInt counts[3]{}; + UnsignedInt vertexOffsets[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, vertexOffsets, nullptr); + shader.draw(mesh, counts, nullptr, nullptr); + CORRADE_COMPARE(out.str(), + "GL::AbstractShaderProgram::draw(): expected 3 vertex offset items but got 2\n" + "GL::AbstractShaderProgram::draw(): expected 3 vertex offset items but got 0\n"); +} + +void MeshGLTest::multiDrawIndexedWrongIndexOffsetSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh mesh; + mesh.setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, {2, 1, 0}}, 0, MeshIndexType::UnsignedInt); + MultiDrawShader shader; + UnsignedInt counts[3]{}; + UnsignedInt indexOffsets[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, nullptr, indexOffsets); + shader.draw(mesh, counts, nullptr, nullptr); + CORRADE_COMPARE(out.str(), + "GL::AbstractShaderProgram::draw(): expected 3 index offset items but got 2\n" + "GL::AbstractShaderProgram::draw(): expected 3 index offset items but got 0\n"); +} + +void MeshGLTest::multiDrawIndexedWrongVertexOffsetSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh mesh; + mesh.setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, {2, 1, 0}}, 0, MeshIndexType::UnsignedInt); + MultiDrawShader shader; + UnsignedInt counts[3]{}; + UnsignedInt vertexOffsets[2]{}; + UnsignedInt indexOffsets[3]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, vertexOffsets, indexOffsets); + CORRADE_COMPARE(out.str(), + "GL::AbstractShaderProgram::draw(): expected 3 vertex offset items but got 2\n"); +} + #ifdef MAGNUM_TARGET_GLES void MeshGLTest::multiDrawIndexedBaseVertexNoExtensionAvailable() { #ifdef MAGNUM_TARGET_GLES @@ -3680,12 +4057,49 @@ void MeshGLTest::multiDrawIndexedBaseVertexNoExtensionAvailable() { CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() + std::string{" is available."}); #endif - constexpr UnsignedShort indexData[] = { 2, 1, 0 }; - Buffer indices{Buffer::TargetHint::ElementArray}; - indices.setData(indexData, BufferUsage::StaticDraw); + Mesh mesh; + mesh.setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, {2, 1, 0}}, 0, MeshIndexType::UnsignedInt); + + UnsignedInt counts[]{3}; + UnsignedInt vertexOffsets[]{0}; + UnsignedInt indexOffsets[]{0}; + + std::ostringstream out; + Error redirectError{&out}; + MultiDrawShader{}.draw(mesh, counts, vertexOffsets, indexOffsets); + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for indexed mesh multi-draw with base vertex specification\n"); + #else + CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): indexed mesh multi-draw with base vertex specification possible only since WebGL 2.0\n"); + #endif +} + +void MeshGLTest::multiDrawIndexedViewsBaseVertexNoExtensionAvailable() { + #ifdef MAGNUM_TARGET_GLES + /* If the multidraw extensions aren't available, we can't test this assert, + only the assert in the fallback path, which is already tested above. */ + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported() && + !Context::current().isExtensionSupported()) + CORRADE_SKIP(std::string{"Neither "} + Extensions::EXT::multi_draw_arrays::string() + " nor " + Extensions::ANGLE::multi_draw::string() + " is available."); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw::string() + std::string{" is not available."}); + #endif + #endif + + #ifndef MAGNUM_TARGET_WEBGL + if(Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::EXT::draw_elements_base_vertex::string() + std::string{" is available."}); + if(Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::OES::draw_elements_base_vertex::string() + std::string{" is available."}); + #elif !defined(MAGNUM_TARGET_GLES2) + if(Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() + std::string{" is available."}); + #endif Mesh mesh; - mesh.setIndexBuffer(indices, 0, MeshIndexType::UnsignedShort); + mesh.setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, {2, 1, 0}}, 0, MeshIndexType::UnsignedInt); MeshView view{mesh}; view.setCount(3) @@ -3702,7 +4116,7 @@ void MeshGLTest::multiDrawIndexedBaseVertexNoExtensionAvailable() { } #endif -void MeshGLTest::multiDrawInstanced() { +void MeshGLTest::multiDrawInstancedViews() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -3718,7 +4132,7 @@ void MeshGLTest::multiDrawInstanced() { CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): cannot multi-draw instanced meshes\n"); } -void MeshGLTest::multiDrawDifferentMeshes() { +void MeshGLTest::multiDrawViewsDifferentMeshes() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif diff --git a/src/Magnum/Shaders/DistanceFieldVectorGL.h b/src/Magnum/Shaders/DistanceFieldVectorGL.h index 69290b331..5ee1c464b 100644 --- a/src/Magnum/Shaders/DistanceFieldVectorGL.h +++ b/src/Magnum/Shaders/DistanceFieldVectorGL.h @@ -580,6 +580,17 @@ template class MAGNUM_SHADERS_EXPORT DistanceFieldVector DistanceFieldVectorGL& draw(GL::MeshView&& mesh) { return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); } + DistanceFieldVectorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + #ifndef CORRADE_TARGET_32BIT + DistanceFieldVectorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + DistanceFieldVectorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, nullptr)); + } + #endif DistanceFieldVectorGL& draw(Containers::ArrayView> meshes) { return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); } diff --git a/src/Magnum/Shaders/FlatGL.h b/src/Magnum/Shaders/FlatGL.h index d0bcc08fb..a7353d067 100644 --- a/src/Magnum/Shaders/FlatGL.h +++ b/src/Magnum/Shaders/FlatGL.h @@ -911,6 +911,17 @@ template class MAGNUM_SHADERS_EXPORT FlatGL: public GL:: FlatGL& draw(GL::MeshView&& mesh) { return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); } + FlatGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + #ifndef CORRADE_TARGET_32BIT + FlatGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + FlatGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, nullptr)); + } + #endif FlatGL& draw(Containers::ArrayView> meshes) { return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); } diff --git a/src/Magnum/Shaders/MeshVisualizerGL.h b/src/Magnum/Shaders/MeshVisualizerGL.h index ca7197f55..c066e2165 100644 --- a/src/Magnum/Shaders/MeshVisualizerGL.h +++ b/src/Magnum/Shaders/MeshVisualizerGL.h @@ -611,6 +611,17 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL2D: public Implementation::MeshVisua MeshVisualizerGL2D& draw(GL::MeshView&& mesh) { return static_cast(GL::AbstractShaderProgram::draw(mesh)); } + MeshVisualizerGL2D& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + #ifndef CORRADE_TARGET_32BIT + MeshVisualizerGL2D& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + MeshVisualizerGL2D& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, nullptr)); + } + #endif MeshVisualizerGL2D& draw(Containers::ArrayView> meshes) { return static_cast(GL::AbstractShaderProgram::draw(meshes)); } @@ -1644,6 +1655,17 @@ class MAGNUM_SHADERS_EXPORT MeshVisualizerGL3D: public Implementation::MeshVisua MeshVisualizerGL3D& draw(GL::MeshView&& mesh) { return static_cast(GL::AbstractShaderProgram::draw(mesh)); } + MeshVisualizerGL3D& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + #ifndef CORRADE_TARGET_32BIT + MeshVisualizerGL3D& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + MeshVisualizerGL3D& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, nullptr)); + } + #endif MeshVisualizerGL3D& draw(Containers::ArrayView> meshes) { return static_cast(GL::AbstractShaderProgram::draw(meshes)); } diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 37a06aa0f..754d34cc3 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -1645,6 +1645,17 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { PhongGL& draw(GL::MeshView&& mesh) { return static_cast(GL::AbstractShaderProgram::draw(mesh)); } + PhongGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + #ifndef CORRADE_TARGET_32BIT + PhongGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + PhongGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return static_cast(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, nullptr)); + } + #endif PhongGL& draw(Containers::ArrayView> meshes) { return static_cast(GL::AbstractShaderProgram::draw(meshes)); } diff --git a/src/Magnum/Shaders/VectorGL.h b/src/Magnum/Shaders/VectorGL.h index ef3d2bd33..ac0281b1f 100644 --- a/src/Magnum/Shaders/VectorGL.h +++ b/src/Magnum/Shaders/VectorGL.h @@ -532,6 +532,17 @@ template class MAGNUM_SHADERS_EXPORT VectorGL: public GL VectorGL& draw(GL::MeshView&& mesh) { return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); } + VectorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + #ifndef CORRADE_TARGET_32BIT + VectorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + VectorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, nullptr)); + } + #endif VectorGL& draw(Containers::ArrayView> meshes) { return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); } diff --git a/src/Magnum/Shaders/VertexColorGL.h b/src/Magnum/Shaders/VertexColorGL.h index 73b23dfa2..80ce69f96 100644 --- a/src/Magnum/Shaders/VertexColorGL.h +++ b/src/Magnum/Shaders/VertexColorGL.h @@ -384,6 +384,17 @@ template class MAGNUM_SHADERS_EXPORT VertexColorGL: publ VertexColorGL& draw(GL::MeshView&& mesh) { return static_cast&>(GL::AbstractShaderProgram::draw(mesh)); } + VertexColorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + #ifndef CORRADE_TARGET_32BIT + VertexColorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, indexOffsets)); + } + VertexColorGL& draw(GL::Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return static_cast&>(GL::AbstractShaderProgram::draw(mesh, counts, vertexOffsets, nullptr)); + } + #endif VertexColorGL& draw(Containers::ArrayView> meshes) { return static_cast&>(GL::AbstractShaderProgram::draw(meshes)); }