From b30d313ecded07a226f7beab81c304bb421045aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 19 Oct 2021 18:20:59 +0200 Subject: [PATCH] GL: implement ANGLE- and WebGL-specific instanced multidraw. Yes, this is only ever provided by an ANGLE extension, and as you might have guessed, The Great Google Overlords didn't bother caring to have this supported outside of their browser. --- doc/changelog.dox | 6 + doc/opengl-support.dox | 4 +- src/Magnum/GL/AbstractShaderProgram.cpp | 60 ++ src/Magnum/GL/AbstractShaderProgram.h | 190 ++++- src/Magnum/GL/Implementation/MeshState.cpp | 74 +- src/Magnum/GL/Implementation/MeshState.h | 8 + src/Magnum/GL/Mesh.cpp | 242 ++++++ src/Magnum/GL/Mesh.h | 30 + src/Magnum/GL/Test/MeshGLTest.cpp | 892 +++++++++++++++++++++ 9 files changed, 1501 insertions(+), 5 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index b1baf89ae..3aff4dcb3 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -64,6 +64,12 @@ See also: - 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 +- Implemented ANGLE- and WebGL-specific instanced multidraw with + @ref GL::AbstractShaderProgram::draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&). + It's added mainly in order to provide a fast rendering path on + resource-constrainted systems and as such doesn't have an overload taking + @ref GL::MeshView instances or a fallback path when the multidraw + extensions are not available. - 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/doc/opengl-support.dox b/doc/opengl-support.dox index a79d77175..c6c07001d 100644 --- a/doc/opengl-support.dox +++ b/doc/opengl-support.dox @@ -450,7 +450,7 @@ Extension | Status @gl_extension2{ANGLE,texture_compression_dxt3,ANGLE_texture_compression_dxt} | done @gl_extension2{ANGLE,texture_compression_dxt5,ANGLE_texture_compression_dxt} | done @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) (unlisted) | done -@m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_base_vertex_base_instance.txt) (unlisted) | done except instanced multi-draw +@m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_base_vertex_base_instance.txt) (unlisted) | done @gl_extension{APPLE,texture_format_BGRA8888} | done @gl_extension{APPLE,clip_distance} | done @gl_extension{ARM,shader_framebuffer_fetch} | missing renderer setup and limit query @@ -574,7 +574,7 @@ Extension | Status @webgl_extension{WEBGL,multi_draw} | done @webgl_extension{WEBGL,blend_equation_advanced_coherent} | done @webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance} | done -@webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} | done except instanced multi-draw +@webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} | done @section opengl-unsupported Unsupported OpenGL features diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index 7c747e78a..6bcae7b9f 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -407,6 +407,66 @@ AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers: } #endif +#ifdef MAGNUM_TARGET_GLES +#ifndef MAGNUM_TARGET_GLES2 +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets, const Containers::StridedArrayView1D& instanceOffsets) { + if(!counts.size()) return *this; + + use(); + + mesh.drawInternalStrided(counts, instanceCounts, vertexOffsets, indexOffsets, instanceOffsets); + return *this; +} + +#ifndef CORRADE_TARGET_32BIT +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets, const Containers::StridedArrayView1D& instanceOffsets) { + if(!counts.size()) return *this; + + use(); + + mesh.drawInternalStrided(counts, instanceCounts, vertexOffsets, indexOffsets, instanceOffsets); + return *this; +} + +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t, const Containers::StridedArrayView1D& instanceOffsets) { + return draw(mesh, counts, instanceCounts, vertexOffsets, Containers::StridedArrayView1D{}, instanceOffsets); +} +#endif +#endif + +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + if(!counts.size()) return *this; + + use(); + + mesh.drawInternalStrided(counts, instanceCounts, vertexOffsets, indexOffsets + #ifndef MAGNUM_TARGET_GLES2 + , Containers::StridedArrayView1D{} + #endif + ); + return *this; +} + +#ifndef CORRADE_TARGET_32BIT +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets) { + if(!counts.size()) return *this; + + use(); + + mesh.drawInternalStrided(counts, instanceCounts, vertexOffsets, indexOffsets + #ifndef MAGNUM_TARGET_GLES2 + , Containers::StridedArrayView1D{} + #endif + ); + return *this; +} + +AbstractShaderProgram& AbstractShaderProgram::draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t) { + return draw(mesh, counts, instanceCounts, vertexOffsets, Containers::StridedArrayView1D{}); +} +#endif +#endif + AbstractShaderProgram& AbstractShaderProgram::draw(Containers::ArrayView> meshes) { if(meshes.empty()) return *this; diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index 3c1aead66..040b01b24 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -922,6 +922,188 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t); #endif + #if defined(MAGNUM_TARGET_GLES) || defined(DOXYGEN_GENERATING_OUTPUT) + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Draw multiple instanced mesh views with instance offsets at once + * @param mesh The mesh from which to draw + * @param counts Vertex/index counts for each draw + * @param instanceCounts Instance counts for each draw. Expected to + * have the same size as @p counts. + * @param vertexOffsets Offsets into the vertex array for non-indexed + * meshes, base vertex for indexed meshes. Expected to have the + * same size as @p counts, for indexed meshes it can be also empty + * in which case the base vertex is assumed to be @cpp 0 @ce for + * all draws. + * @param indexOffsets Offsets into the index buffer for indexed + * meshes, *in bytes*. Expected to have the same size as @p counts + * for indexed meshes, ignored for non-indexed. + * @param instanceOffsets Offsets to be added to the instance index for + * each draw. Expected to either be empty or have the same size as + * @p counts. + * @return Reference to self (for method chaining) + * @m_since_latest + * + * If @p counts, @p instanceCounts, @p vertexOffsets, @p indexOffsets + * and @p instanceOffsets are contiguous views, they get passed + * directly to the underlying GL functions, otherwise a temporary + * contiguous copy is allocated. There are the following special cases, + * however: + * + * - On @ref CORRADE_TARGET_32BIT "64-bit builds" the + * @p indexOffsets additionally have to be 64-bit in order to + * avoid a copy because the @m_class{m-doc-external} [glMultiDrawArraysInstancedANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_multi_draw.txt) + * / @m_class{m-doc-external} [glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * functions accept them as pointers, see the @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * overload below. + * - If the @p mesh is indexed, @p vertexOffsets and + * @p instanceOffsets have to be either both specified or both + * empty in order to avoid a copy with the missing one filled with + * zeros, as there's only either @m_class{m-doc-external} [glMultiDrawElementsInstancedANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_multi_draw.txt) + * or @m_class{m-doc-external} [glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt), + * but no function accepting just one of them. + * + * @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}, + * @m_class{m-doc-external} [glMultiDrawArraysInstancedANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_multi_draw.txt) + * / @m_class{m-doc-external} [glMultiDrawArraysInstancedBaseInstanceANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) or + * @m_class{m-doc-external} [glMultiDrawElementsInstancedANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_multi_draw.txt) + * / @m_class{m-doc-external} [glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * @requires_gles_only Not available on desktop OpenGL. + * @requires_gles30 Not defined in OpenGL ES 2.0. Use + * @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) without the + * @p instanceOffsets parameter there instead. + * @requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * @requires_es_extension OpenGL ES 3.1 and extension + * @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * if the mesh is indexed and the @p vertexOffsets view is not + * empty + * @requires_es_extension OpenGL ES 3.1 and extension + * @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * if the @p instanceOffsets view is not empty + * @requires_webgl_extension Extension @webgl_extension{WEBGL,multi_draw}. + * Note that this extension is only implemented since Emscripten + * 2.0.0 and thus it's not even advertised on older versions. + * @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 vertexOffsets view is not + * empty. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} + * if the @p instanceOffsets view is not empty. + * @m_keywords{glMultiDrawArraysInstancedANGLE() glMultiDrawArraysInstancedBaseInstanceANGLE() glMultiDrawElementsInstancedANGLE() glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE()} + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets, const Containers::StridedArrayView1D& instanceOffsets); + + #ifndef CORRADE_TARGET_32BIT + /** + * @brief Draw multiple instanced mesh views with instance offsets at once + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Defined only on @ref CORRADE_TARGET_32BIT "64-bit builds". Compared + * to @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * this overload can avoid allocating an array of 64-bit pointers for + * the @m_class{m-doc-external} [glMultiDrawElementsInstancedANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_multi_draw.txt) + * / @m_class{m-doc-external} [glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * function and can instead directly reuse the @p indexOffsets view if + * it's contiguous. See the original overload for further information. + * @requires_gles_only Not available on desktop OpenGL. + * @requires_gles30 Not defined in OpenGL ES 2.0. Use + * @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) without the + * @p instanceOffsets parameter there instead. + * @requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * @requires_es_extension OpenGL ES 3.1 and extension + * @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * if the mesh is indexed and the @p vertexOffsets view is not + * empty + * @requires_es_extension OpenGL ES 3.1 and extension + * @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * if the @p instanceOffsets view is not empty + * @requires_webgl_extension Extension @webgl_extension{WEBGL,multi_draw} + * @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 vertexOffsets view is not + * empty. + * @requires_webgl_extension WebGL 2.0 and extension + * @webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} + * if the @p instanceOffsets view is not empty. + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets, const Containers::StridedArrayView1D& instanceOffsets); + + /** + * @overload + * @m_since_latest + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t, const Containers::StridedArrayView1D& instanceOffsets); + #endif + #endif + + /** + * @brief Draw multiple instanced mesh views at once + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Compared to @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * lacks the last @p instanceOffsets parameter and as such is available + * also in OpenGL ES 2.0 and WebGL 1.0. + * + * If OpenGL ES 3.0, WebGL 2.0, @gl_extension{OES,vertex_array_object} + * in OpenGL ES 2.0 or @webgl_extension{OES,vertex_array_object} in + * WebGL 1.0 is available, the associated vertex array object is bound + * instead of setting up the mesh from scratch. + * + * @requires_gles_only Not available on desktop OpenGL. + * @requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * @requires_es_extension OpenGL ES 3.1 and extension + * @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * if the mesh is indexed and the @p vertexOffsets view is not + * empty + * @requires_webgl_extension Extension @webgl_extension{WEBGL,multi_draw} + * @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 vertexOffsets view is not + * empty. + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); + + #ifndef CORRADE_TARGET_32BIT + /** + * @brief Draw multiple instanced mesh views at once + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Defined only on @ref CORRADE_TARGET_32BIT "64-bit builds". Compared + * to @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * this overload can avoid allocating an array of 64-bit pointers for + * the @m_class{m-doc-external} [glMultiDrawElementsInstancedANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_multi_draw.txt) + * / @m_class{m-doc-external} [glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE()](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * function and can instead directly reuse the @p indexOffsets view if + * it's contiguous. See the original overload for further information. + * @requires_gles_only Not available on desktop OpenGL. + * @requires_es_extension Extension @m_class{m-doc-external} [ANGLE_multi_draw](https://chromium.googlesource.com/angle/angle/+/master/extensions/ANGLE_multi_draw.txt) + * @requires_es_extension OpenGL ES 3.1 and extension + * @m_class{m-doc-external} [ANGLE_base_vertex_base_instance](https://chromium.googlesource.com/angle/angle/+/refs/heads/main/extensions/ANGLE_base_vertex_base_instance.txt) + * if the mesh is indexed and the @p vertexOffsets view is not + * empty + * @requires_webgl_extension Extension @webgl_extension{WEBGL,multi_draw} + * @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 vertexOffsets view is not + * empty. + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); + + /** + * @overload + * @m_since_latest + */ + AbstractShaderProgram& draw(Mesh& mesh, const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, std::nullptr_t); + #endif + #endif + /** * @brief Draw multiple mesh views at once * @return Reference to self (for method chaining) @@ -941,7 +1123,13 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * 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. + * must not be instanced. Instanced multidraw is available only + * through @ref draw(Mesh&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * which relies on GLES- and WebGL-specific extensions. It exists + * mainly in order to provide a fast rendering path on + * resource-constrainted systems and as such doesn't have an + * overload taking @ref MeshView instances or a fallback path when + * the multidraw extensions are not available. * * @requires_gl32 Extension @gl_extension{ARB,draw_elements_base_vertex} * if the mesh is indexed and @ref MeshView::baseVertex() is not diff --git a/src/Magnum/GL/Implementation/MeshState.cpp b/src/Magnum/GL/Implementation/MeshState.cpp index eefaf2abb..55a86458b 100644 --- a/src/Magnum/GL/Implementation/MeshState.cpp +++ b/src/Magnum/GL/Implementation/MeshState.cpp @@ -227,6 +227,7 @@ MeshState::MeshState(Context& context, ContextState& contextState, Containers::S if(context.isExtensionSupported()) #endif { + /* General multi-draw extension */ #ifndef MAGNUM_TARGET_WEBGL if(context.isExtensionSupported()) { extensions[Extensions::EXT::multi_draw_arrays::Index] = @@ -260,8 +261,9 @@ MeshState::MeshState(Context& context, ContextState& contextState, Containers::S } #endif - /* These function pointers make sense only if the general multi-draw - extension is supported. Also, not on WebGL 1 at all. */ + /* Base vertex specification. These function pointers make sense only + if the general multi-draw extension is supported. Also, not on WebGL + 1 at all. */ #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) #ifndef MAGNUM_TARGET_WEBGL if(context.isExtensionSupported()) { @@ -301,6 +303,74 @@ MeshState::MeshState(Context& context, ContextState& contextState, Containers::S } #endif + /* Instanced multi-draw. ES- and WebGL-only. */ + #ifdef MAGNUM_TARGET_GLES + #ifndef MAGNUM_TARGET_WEBGL + if(context.isExtensionSupported()) { + extensions[Extensions::ANGLE::multi_draw::Index] = + Extensions::ANGLE::multi_draw::string(); + + multiDrawArraysInstancedImplementation = glMultiDrawArraysInstancedANGLE; + multiDrawElementsInstancedImplementation = glMultiDrawElementsInstancedANGLE; + } else { + multiDrawArraysInstancedImplementation = nullptr; + multiDrawElementsInstancedImplementation = nullptr; + } + #else + { + extensions[Extensions::WEBGL::multi_draw::Index] = + Extensions::WEBGL::multi_draw::string(); + + /* The WEBGL extension uses the same entrypoints as the ANGLE + extension it was based on. Only available since 2.0.0: + https://github.com/emscripten-core/emscripten/pull/11650 */ + #if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 20000 + multiDrawArraysInstancedImplementation = glMultiDrawArraysInstancedANGLE; + multiDrawElementsInstancedImplementation = glMultiDrawElementsInstancedANGLE; + #else + /* In Context::setupDriverWorkarounds() we make sure the extension + is not even advertised, so this shouldn't be reached. */ + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + #endif + } + #endif + #endif + + /* Instanced multi-draw with base vertex / base instance. ES 3.1 and + WebGL 2 only. */ + #if defined(MAGNUM_TARGET_GLES) && !defined(MAGNUM_TARGET_GLES2) + #ifndef MAGNUM_TARGET_WEBGL + if(context.isExtensionSupported()) { + extensions[Extensions::ANGLE::base_vertex_base_instance::Index] = + Extensions::ANGLE::base_vertex_base_instance::string(); + + multiDrawArraysInstancedBaseInstanceImplementation = glMultiDrawArraysInstancedBaseInstanceANGLE; + multiDrawElementsInstancedBaseVertexBaseInstanceImplementation = glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE; + } else + #else + if(context.isExtensionSupported()) { + extensions[Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::Index] = + Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string(); + + /* The WEBGL extension uses the same entrypoints as the ANGLE + extension it was based on. Only available since 2.0.0: + https://github.com/emscripten-core/emscripten/pull/11650 */ + #if __EMSCRIPTEN_major__*10000 + __EMSCRIPTEN_minor__*100 + __EMSCRIPTEN_tiny__ >= 20000 + multiDrawArraysInstancedBaseInstanceImplementation = glMultiDrawArraysInstancedBaseInstanceANGLE; + multiDrawElementsInstancedBaseVertexBaseInstanceImplementation = glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE; + #else + /* In Context::setupDriverWorkarounds() we make sure the extension + is not even advertised, so this shouldn't be reached. */ + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + #endif + } else + #endif + { + multiDrawArraysInstancedBaseInstanceImplementation = Mesh::multiDrawArraysInstancedBaseInstanceImplementationAssert; + multiDrawElementsInstancedBaseVertexBaseInstanceImplementation = Mesh::multiDrawElementsInstancedBaseVertexBaseInstanceImplementationAssert; + } + #endif + multiDrawViewImplementation = &MeshView::multiDrawImplementationDefault; } else { diff --git a/src/Magnum/GL/Implementation/MeshState.h b/src/Magnum/GL/Implementation/MeshState.h index 5f40d1a2d..22b756256 100644 --- a/src/Magnum/GL/Implementation/MeshState.h +++ b/src/Magnum/GL/Implementation/MeshState.h @@ -83,6 +83,14 @@ struct MeshState { #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) void(APIENTRY *multiDrawElementsBaseVertexImplementation)(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*); #endif + #ifdef MAGNUM_TARGET_GLES + void(APIENTRY *multiDrawArraysInstancedImplementation)(GLenum, const GLint*, const GLsizei*, const GLsizei*, GLsizei); + void(APIENTRY *multiDrawElementsInstancedImplementation)(GLenum, const GLint*, GLenum, const void* const*, const GLsizei*, GLsizei); + #ifndef MAGNUM_TARGET_GLES2 + void(APIENTRY *multiDrawArraysInstancedBaseInstanceImplementation)(GLenum, const GLint*, const GLsizei*, const GLsizei*, const GLuint*, GLsizei); + void(APIENTRY *multiDrawElementsInstancedBaseVertexBaseInstanceImplementation)(GLenum, const GLint*, GLenum, const void* const*, const GLsizei*, const GLint*, const GLuint*, GLsizei); + #endif + #endif #endif void(*bindVAOImplementation)(GLuint); diff --git a/src/Magnum/GL/Mesh.cpp b/src/Magnum/GL/Mesh.cpp index 7804b8053..e629f2366 100644 --- a/src/Magnum/GL/Mesh.cpp +++ b/src/Magnum/GL/Mesh.cpp @@ -464,6 +464,83 @@ void Mesh::drawInternal(const Containers::ArrayView& counts, (this->*state.unbindImplementation)(); } +#ifdef MAGNUM_TARGET_GLES +void Mesh::drawInternal(const Containers::ArrayView& counts, const Containers::ArrayView& instanceCounts, const Containers::ArrayView& vertexOffsets, + #ifdef CORRADE_TARGET_32BIT + const Containers::ArrayView& indexOffsets + #else + const Containers::ArrayView& indexOffsets + #endif + #ifndef MAGNUM_TARGET_GLES2 + , const Containers::ArrayView& instanceOffsets + #endif +) { + const Implementation::MeshState& state = Context::current().state().mesh; + (this->*state.bindImplementation)(); + + CORRADE_ASSERT(instanceCounts.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "instance count items but got" << instanceCounts.size(), ); + + /* Non-indexed instanced meshes */ + if(!_indexBuffer.id()) { + CORRADE_ASSERT(vertexOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "vertex offset items but got" << vertexOffsets.size(), ); + + /* Non-indexed instanced meshes */ + #ifndef MAGNUM_TARGET_GLES2 + if(instanceOffsets.empty()) + #endif + { + state.multiDrawArraysInstancedImplementation(GLenum(_primitive), reinterpret_cast(vertexOffsets.data()), reinterpret_cast(counts.data()), reinterpret_cast(instanceCounts.data()), counts.size()); + } + + /* Non-indexed instanced meshes with base instance */ + #ifndef MAGNUM_TARGET_GLES2 + else { + CORRADE_ASSERT(instanceOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "instance offset items but got" << instanceOffsets.size(), ); + + state.multiDrawArraysInstancedBaseInstanceImplementation(GLenum(_primitive), reinterpret_cast(vertexOffsets.data()), reinterpret_cast(counts.data()), reinterpret_cast(instanceCounts.data()), + reinterpret_cast(instanceOffsets.data()), counts.size()); + } + #endif + + /* 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_GLES2 + && instanceOffsets.empty() + #endif + ) { + state.multiDrawElementsInstancedImplementation(GLenum(_primitive), reinterpret_cast(counts.data()), GLenum(_indexType), reinterpret_cast(indexOffsets.data()), reinterpret_cast(instanceCounts.data()), counts.size()); + + /* Indexed meshes with base vertex / base instance. According to the + extension spec both have to be present, not just one. */ + } else { + CORRADE_ASSERT(vertexOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "vertex offset items but got" << vertexOffsets.size(), ); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(instanceOffsets.size() == counts.size(), + "GL::AbstractShaderProgram::draw(): expected" << counts.size() << "instance offset items but got" << instanceOffsets.size(), ); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + state.multiDrawElementsInstancedBaseVertexBaseInstanceImplementation(GLenum(_primitive), reinterpret_cast(counts.data()), GLenum(_indexType), reinterpret_cast(indexOffsets.data()), reinterpret_cast(instanceCounts.data()), reinterpret_cast(vertexOffsets.data()), + reinterpret_cast(instanceOffsets.data()), counts.size()); + #else + CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): instanced indexed mesh multi-draw with base vertex specification possible only since OpenGL ES 3.0 and WebGL 2.0", ); + #endif + } + } + + (this->*state.unbindImplementation)(); +} +#endif + void Mesh::drawInternalStrided(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 @@ -545,6 +622,161 @@ void Mesh::drawInternalStrided(const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets + #ifndef MAGNUM_TARGET_GLES2 + , const Containers::StridedArrayView1D& instanceOffsets + #endif +) { + #ifdef CORRADE_TARGET_32BIT + /* If all views are contiguous, the mesh specifies either both base vertex + and base instance or neither and we're on 32-bit, call the + implementation directly */ + if(counts.isContiguous() && instanceCounts.isContiguous() && vertexOffsets.isContiguous() && indexOffsets.isContiguous() + #ifndef MAGNUM_TARGET_GLES2 + && instanceOffsets.isContiguous() && (!_indexBuffer.id() || vertexOffsets.size() == instanceOffsets.size()) + #endif + ) + return drawInternal(counts.asContiguous(), instanceCounts.asContiguous(), vertexOffsets.asContiguous(), indexOffsets.asContiguous() + #ifndef MAGNUM_TARGET_GLES2 + , instanceOffsets.asContiguous() + #endif + ); + #endif + + /* Expected vertex offset and instance offset count. If the mesh is + indexed, they either have to be both used or both empty. */ + std::size_t expectedVertexOffsetCount = vertexOffsets.size(); + #ifndef MAGNUM_TARGET_GLES2 + std::size_t expectedInstanceOffsetCount = instanceOffsets.size(); + if(_indexBuffer.id()) { + /* Use counts.size() instead of OffsetCount to avoid hitting + a wrong assert in case the vertex/instance count doesn't match */ + if(expectedVertexOffsetCount && !expectedInstanceOffsetCount) + expectedInstanceOffsetCount = counts.size(); + else if(expectedInstanceOffsetCount && !expectedVertexOffsetCount) + expectedVertexOffsetCount = counts.size(); + } + #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 instanceCountsContiguous; + Containers::ArrayView vertexOffsetsContiguous; + #ifdef CORRADE_TARGET_32BIT + Containers::ArrayView + #else + Containers::ArrayView + #endif + indexOffsetsContiguous; + #ifndef MAGNUM_TARGET_GLES2 + Containers::ArrayView instanceOffsetsContiguous; + #endif + Containers::ArrayTuple data{ + {NoInit, counts.size(), countsContiguous}, + {NoInit, instanceCounts.size(), instanceCountsContiguous}, + /* Zero init vertex offsets if we don't have them */ + vertexOffsets.size() ? + Containers::ArrayTuple::Item{NoInit, expectedVertexOffsetCount, vertexOffsetsContiguous} : + Containers::ArrayTuple::Item{ValueInit, expectedVertexOffsetCount, 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}, + #ifndef MAGNUM_TARGET_GLES2 + /* Zero init instance offsets if we don't have them */ + instanceOffsets.size() ? + Containers::ArrayTuple::Item{NoInit, expectedInstanceOffsetCount, instanceOffsetsContiguous} : + Containers::ArrayTuple::Item{ValueInit, expectedInstanceOffsetCount, instanceOffsetsContiguous}, + #endif + }; + Utility::copy(counts, countsContiguous); + Utility::copy(instanceCounts, instanceCountsContiguous); + /* Copy vertex offsets only if we have them and leave them zero-init'd + otherwise */ + if(vertexOffsets.size()) + 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 + ); + #ifndef MAGNUM_TARGET_GLES2 + /* Copy instance offsets only if we have them and leave them zero-init'd + otherwise */ + if(instanceOffsets.size()) + Utility::copy(instanceOffsets, instanceOffsetsContiguous); + #endif + + drawInternal(countsContiguous, instanceCountsContiguous, vertexOffsetsContiguous, indexOffsetsContiguous + #ifndef MAGNUM_TARGET_GLES2 + , instanceOffsetsContiguous + #endif + ); +} + +#ifndef CORRADE_TARGET_32BIT +void Mesh::drawInternalStrided(const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets + #ifndef MAGNUM_TARGET_GLES2 + , const Containers::StridedArrayView1D& instanceOffsets + #endif +) { + /* If all views are contiguous, the mesh specifies either both base vertex + and base instance or neither, call the implementation directly */ + if(counts.isContiguous() && instanceCounts.isContiguous() && vertexOffsets.isContiguous() && indexOffsets.isContiguous() + #ifndef MAGNUM_TARGET_GLES2 + && instanceOffsets.isContiguous() && (!_indexBuffer.id() || vertexOffsets.size() == instanceOffsets.size()) + #endif + ) + return drawInternal(counts.asContiguous(), instanceCounts.asContiguous(), vertexOffsets.asContiguous(), indexOffsets.asContiguous() + #ifndef MAGNUM_TARGET_GLES2 + , instanceOffsets.asContiguous() + #endif + ); + + /* Otherwise delegate into the 32-bit variant, which will allocate a + contiguous copy */ + drawInternalStrided(counts, instanceCounts, 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 + ] + #ifndef MAGNUM_TARGET_GLES2 + , instanceOffsets + #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 @@ -1120,6 +1352,16 @@ void Mesh::multiDrawElementsBaseVertexImplementationANGLE(const GLenum mode, con void Mesh::multiDrawElementsBaseVertexImplementationAssert(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*) { CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for indexed mesh multi-draw with base vertex specification", ); } + +#ifndef MAGNUM_TARGET_GLES2 +void Mesh::multiDrawArraysInstancedBaseInstanceImplementationAssert(GLenum, const GLint*, const GLsizei*, const GLsizei*, const GLuint*, GLsizei) { + CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for instanced mesh multi-draw with base instance specification", ); +} + +void Mesh::multiDrawElementsInstancedBaseVertexBaseInstanceImplementationAssert(GLenum, const GLint*, GLenum, const void* const*, const GLsizei*, const GLint*, const GLuint*, GLsizei) { + CORRADE_ASSERT_UNREACHABLE("GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh multi-draw with base vertex and base instance specification", ); +} +#endif #endif }} diff --git a/src/Magnum/GL/Mesh.h b/src/Magnum/GL/Mesh.h index 5de7d1347..7eb2cf68e 100644 --- a/src/Magnum/GL/Mesh.h +++ b/src/Magnum/GL/Mesh.h @@ -1096,10 +1096,36 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject { const Containers::ArrayView& indexOffsets #endif ); + #ifdef MAGNUM_TARGET_GLES + MAGNUM_GL_LOCAL void drawInternal(const Containers::ArrayView& counts, const Containers::ArrayView& instanceCounts, const Containers::ArrayView& vertexOffsets, + #ifdef CORRADE_TARGET_32BIT + const Containers::ArrayView& indexOffsets + #else + const Containers::ArrayView& indexOffsets + #endif + #ifndef MAGNUM_TARGET_GLES2 + , const Containers::ArrayView& instanceOffsets + #endif + ); + #endif MAGNUM_GL_LOCAL void drawInternalStrided(const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); #ifndef CORRADE_TARGET_32BIT MAGNUM_GL_LOCAL void drawInternalStrided(const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets); #endif + #ifdef MAGNUM_TARGET_GLES + MAGNUM_GL_LOCAL void drawInternalStrided(const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets + #ifndef MAGNUM_TARGET_GLES2 + , const Containers::StridedArrayView1D& instanceOffsets + #endif + ); + #ifndef CORRADE_TARGET_32BIT + MAGNUM_GL_LOCAL void drawInternalStrided(const Containers::StridedArrayView1D& counts, const Containers::StridedArrayView1D& instanceCounts, const Containers::StridedArrayView1D& vertexOffsets, const Containers::StridedArrayView1D& indexOffsets + #ifndef MAGNUM_TARGET_GLES2 + , const Containers::StridedArrayView1D& instanceOffsets + #endif + ); + #endif + #endif #ifndef MAGNUM_TARGET_GLES MAGNUM_GL_LOCAL void drawInternal(TransformFeedback& xfb, UnsignedInt stream, Int instanceCount); @@ -1193,6 +1219,10 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject { static MAGNUM_GL_LOCAL void multiDrawElementsBaseVertexImplementationANGLE(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawCount, const GLint* baseVertex); #endif static MAGNUM_GL_LOCAL void multiDrawElementsBaseVertexImplementationAssert(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*); + #ifndef MAGNUM_TARGET_GLES2 + static MAGNUM_GL_LOCAL void multiDrawArraysInstancedBaseInstanceImplementationAssert(GLenum, const GLint*, const GLsizei*, const GLsizei*, const GLuint*, GLsizei); + static MAGNUM_GL_LOCAL void multiDrawElementsInstancedBaseVertexBaseInstanceImplementationAssert(GLenum, const GLint*, GLenum, const void* const*, const GLsizei*, const GLint*, const GLuint*, GLsizei); + #endif #endif /* _id, _primitive, _flags set from constructors */ diff --git a/src/Magnum/GL/Test/MeshGLTest.cpp b/src/Magnum/GL/Test/MeshGLTest.cpp index 30a5c935f..4a0b968ae 100644 --- a/src/Magnum/GL/Test/MeshGLTest.cpp +++ b/src/Magnum/GL/Test/MeshGLTest.cpp @@ -198,6 +198,27 @@ struct MeshGLTest: OpenGLTester { #endif void multiDrawViewsInstanced(); void multiDrawViewsDifferentMeshes(); + #ifdef MAGNUM_TARGET_GLES + void multiDrawInstanced(); + void multiDrawInstancedSparseArrays(); + /* no MeshView support for instanced multidraw */ + template void multiDrawInstancedIndexed(); + template void multiDrawInstancedIndexedSparseArrays(); + /* no MeshView support for instanced multidraw */ + void multiDrawInstancedWrongInstanceCountSize(); + void multiDrawInstancedWrongVertexOffsetSize(); + #ifndef MAGNUM_TARGET_GLES2 + void multiDrawInstancedWrongInstanceOffsetSize(); + #endif + void multiDrawInstancedIndexedWrongInstanceCountSize(); + void multiDrawInstancedIndexedWrongVertexOffsetSize(); + void multiDrawInstancedIndexedWrongIndexOffsetSize(); + #ifndef MAGNUM_TARGET_GLES2 + void multiDrawInstancedIndexedWrongInstanceOffsetSize(); + void multiDrawInstancedBaseVertexNoExtensionAvailable(); + void multiDrawInstancedBaseInstanceNoExtensionAvailable(); + #endif + #endif }; const struct { @@ -351,6 +372,167 @@ const struct { {0.25f, 0.5f, 0.0f, 1.0f}} }; +#ifdef MAGNUM_TARGET_GLES +const struct { + const char* name; + bool vertexId; + bool drawId; + Vector3 values[2]; + UnsignedInt counts[2]; + UnsignedInt instanceCounts[2]; + UnsignedInt vertexOffsets[2]; + UnsignedInt instanceOffsets[2]; + Vector4 expected; +} MultiDrawInstancedData[] { + {"all zero vertex counts", false, false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 0}, + {1, 1}, + {0, 0}, + {0, 0}, + {0.0f, 0.0f, 0.0f, 0.0f}}, + {"all zero instance counts", false, false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {1, 1}, + {0, 0}, + {0, 0}, + {0, 0}, + {0.0f, 0.0f, 0.0f, 0.0f}}, + {"single draw", false, false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {2, 0}, + {2, 0}, + {0, 0}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + #ifndef MAGNUM_TARGET_GLES2 + {"single draw, vertex ID", true, false, + {{0.25f, 0.75f, 0.0f}, + {0.0f, 0.5f, 1.0f}}, + {2, 0}, + {2, 0}, + {0, 0}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + #endif + {"single draw, draw ID", false, true, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {2, 0}, + {2, 0}, + {0, 0}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + {"multi draw", false, false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {1, 1}, + {2, 2}, + {0, 1}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + #ifndef MAGNUM_TARGET_GLES2 + {"multi draw, vertex ID", true, false, + {{0.25f, 0.75f, 0.0f}, + {0.0f, 0.5f, 1.0f}}, + {1, 1}, + {2, 2}, + {0, 1}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + #endif + {"multi draw, draw ID", false, true, + {{0.25f, 0.75f, 0.0f}, + {0.0f, 0.5f, 1.0f}}, + {1, 1}, + {2, 2}, + {0, 1}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + #ifndef MAGNUM_TARGET_GLES2 + {"multi draw, instance offset", false, false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {2, 2}, + {1, 1}, + {0, 0}, + {0, 1}, + {0.25f, 0.5f, 0.75f, 1.0f}} + #endif +}; + +const struct { + const char* name; + bool vertexId; + Vector3 values[2]; + UnsignedInt indices[2]; + UnsignedInt counts[2]; + UnsignedInt instanceCounts[2]; + UnsignedInt indexOffsetsInBytes[2]; + UnsignedInt vertexOffsets[2]; + UnsignedInt instanceOffsets[2]; + Vector4 expected; +} MultiDrawInstancedIndexedData[] { + {"single draw", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + {2, 0}, + {2, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + {"multi draw", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + {1, 1}, + {2, 2}, + {0, 4}, + {0, 0}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + #ifndef MAGNUM_TARGET_GLES2 + {"multi draw, vertex offset", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 0}, + {1, 1}, + {2, 2}, + {0, 0}, + {0, 1}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + {"multi draw, vertex offset, vertex ID", true, + {{0.25f, 0.75f, 0.0f}, + {0.0f, 0.5f, 1.0f}}, + /* Same as in the non-indexed case, gl_VertexID includes the baseVertex + as well */ + {0, 0}, + {1, 1}, + {2, 2}, + {0, 0}, + {0, 1}, + {0, 0}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + {"multi draw, instance offset", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + {2, 2}, + {1, 1}, + {0, 0}, + {0, 0}, + {0, 1}, + {0.25f, 0.5f, 0.75f, 1.0f}}, + #endif +}; +#endif + using namespace Math::Literals; MeshGLTest::MeshGLTest() { @@ -519,6 +701,39 @@ MeshGLTest::MeshGLTest() { &MeshGLTest::multiDrawViewsDifferentMeshes }); + #ifdef MAGNUM_TARGET_GLES + addInstancedTests({&MeshGLTest::multiDrawInstanced, + &MeshGLTest::multiDrawInstancedSparseArrays}, + Containers::arraySize(MultiDrawInstancedData)); + + addInstancedTests({ + &MeshGLTest::multiDrawInstancedIndexed, + #ifndef CORRADE_TARGET_32BIT + &MeshGLTest::multiDrawInstancedIndexed, + #endif + &MeshGLTest::multiDrawInstancedIndexedSparseArrays, + #ifndef CORRADE_TARGET_32BIT + &MeshGLTest::multiDrawInstancedIndexedSparseArrays + #endif + }, Containers::arraySize(MultiDrawInstancedIndexedData)); + + addTests({ + &MeshGLTest::multiDrawInstancedWrongInstanceCountSize, + &MeshGLTest::multiDrawInstancedWrongVertexOffsetSize, + #ifndef MAGNUM_TARGET_GLES2 + &MeshGLTest::multiDrawInstancedWrongInstanceOffsetSize, + #endif + &MeshGLTest::multiDrawInstancedIndexedWrongInstanceCountSize, + &MeshGLTest::multiDrawInstancedIndexedWrongVertexOffsetSize, + &MeshGLTest::multiDrawInstancedIndexedWrongIndexOffsetSize, + #ifndef MAGNUM_TARGET_GLES2 + &MeshGLTest::multiDrawInstancedIndexedWrongInstanceOffsetSize, + &MeshGLTest::multiDrawInstancedBaseVertexNoExtensionAvailable, + &MeshGLTest::multiDrawInstancedBaseInstanceNoExtensionAvailable + #endif + }); + #endif + /* Reset clear color to something trivial first */ Renderer::setClearColor(0x000000_rgbf); } @@ -4060,6 +4275,7 @@ void MeshGLTest::multiDrawIndexedWrongVertexOffsetSize() { std::ostringstream out; Error redirectError{&out}; shader.draw(mesh, counts, vertexOffsets, indexOffsets); + /* Omitting vertex offsets altogether is okay */ CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): expected 3 vertex offset items but got 2\n"); } @@ -4198,6 +4414,682 @@ void MeshGLTest::multiDrawViewsDifferentMeshes() { CORRADE_COMPARE(out.str(), Utility::formatString("GL::AbstractShaderProgram::draw(): all meshes must be views of the same original mesh, expected 0x{:x} but got 0x{:x} at index 1\n", reinterpret_cast(&a), reinterpret_cast(&b))); } +#ifdef MAGNUM_TARGET_GLES +struct MultiDrawInstancedShader: AbstractShaderProgram { + typedef Attribute<0, Float> PositionX; + typedef Attribute<1, Float> PositionY; + typedef Attribute<2, Vector3> Value; + #ifdef MAGNUM_TARGET_GLES2 + /* ES2 has no integer attributes either */ + typedef Attribute<3, Float> InstanceId; + #endif + + explicit MultiDrawInstancedShader(bool vertexId = false, bool drawId = false + #ifndef MAGNUM_TARGET_GLES2 + , bool instanceOffset = false + #endif + ); +}; + +MultiDrawInstancedShader::MultiDrawInstancedShader(bool vertexId, bool drawId + #ifndef MAGNUM_TARGET_GLES2 + , bool instanceOffset + #endif +) { + /* Pick GLSL 3.0 / ESSL 3.0 for gl_VertexID, if available */ + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + const Version version = Context::current().supportedVersion({Version::GL300, Version::GL210}); + #else + const Version version = Version::GL310; + #endif + #else + const Version version = Context::current().supportedVersion({Version::GLES300, Version::GLES200}); + #endif + Shader vert{version, Shader::Type::Vertex}; + Shader frag{version, Shader::Type::Fragment}; + + if(drawId) vert.addSource( + "#extension GL_ANGLE_multi_draw: require\n" + "#define vertexOrDrawIdOrInstanceOffset gl_DrawID\n"); + else if(vertexId) vert.addSource( + "#define vertexOrDrawIdOrInstanceOffset gl_VertexID\n"); + #ifndef MAGNUM_TARGET_GLES2 + else if(instanceOffset) vert.addSource( + "#extension GL_ANGLE_base_vertex_base_instance: require\n" + "#define vertexOrDrawIdOrInstanceOffset gl_BaseInstance\n"); + #endif + else vert.addSource( + "#define vertexOrDrawIdOrInstanceOffset 0\n"); + vert.addSource( + "#if defined(GL_ES) && __VERSION__ == 100\n" + "#define in attribute\n" + "#define out varying\n" + "#endif\n" + "in mediump float positionX;\n" + "in mediump float positionY;\n" + "in mediump vec3 value;\n" + #ifdef MAGNUM_TARGET_GLES2 + "in mediump float instanceId;\n" + #endif + "out mediump float valueInterpolated;\n" + "void main() {\n" + #ifndef MAGNUM_TARGET_GLES2 + " valueInterpolated = value[vertexOrDrawIdOrInstanceOffset + gl_InstanceID];\n" + #else + " valueInterpolated = value[vertexOrDrawIdOrInstanceOffset + int(instanceId)];\n" + #endif + " gl_Position = vec4(positionX, positionY, 0.0, 1.0);\n" + "}\n"); + frag.addSource( + "#if defined(GL_ES) && __VERSION__ == 100\n" + "#define in varying\n" + "#define result gl_FragColor\n" + "#endif\n" + "in mediump float valueInterpolated;\n" + "#if defined(GL_ES) && __VERSION__ >= 300\n" + "out mediump vec4 result;\n" + "#endif\n" + "void main() { result.r = valueInterpolated; }\n"); + + CORRADE_INTERNAL_ASSERT_OUTPUT(Shader::compile({vert, frag})); + + attachShaders({vert, frag}); + + bindAttributeLocation(PositionX::Location, "positionX"); + bindAttributeLocation(PositionY::Location, "positionY"); + bindAttributeLocation(Value::Location, "value"); + #ifdef MAGNUM_TARGET_GLES2 + bindAttributeLocation(InstanceId::Location, "instanceId"); + #endif + + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); +} + +void MeshGLTest::multiDrawInstanced() { + auto&& data = MultiDrawInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef 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 + + #ifndef MAGNUM_TARGET_GLES2 + if(data.vertexId && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("gl_VertexID not supported"); + #endif + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_WEBGL + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::base_vertex_base_instance::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() << "is not supported."); + #endif + #else + CORRADE_FAIL_IF(false, "Can't do base instance here."); + #endif + } + + const struct { + Float positionX; + Vector3 value; + } vertexData[] { + {}, /* initial offset */ + {-1.0f/3.0f, data.values[0]}, + { 1.0f/3.0f, data.values[1]}, + }; + const Float instanceData[]{ + {}, /* initial offset */ + -1.0f/3.0f, + 1.0f/3.0f + }; + + Mesh mesh{MeshPrimitive::Points}; + mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawInstancedShader::PositionX{}, MultiDrawInstancedShader::Value{}) + .addVertexBufferInstanced(Buffer{instanceData}, 1, sizeof(instanceData[0]), MultiDrawInstancedShader::PositionY{}); + + #ifdef MAGNUM_TARGET_GLES2 + /* Because ANGLE_instanced_arrays on ES2 / WebGL 1 doesn't even provide + gl_InstanceID ... and there are no integer attributes either */ + const Float instanceId[]{0.0f, 1.0f}; + mesh.addVertexBufferInstanced(Buffer{instanceId}, 1, 0, MultiDrawInstancedShader::InstanceId{}); + #endif + + MAGNUM_VERIFY_NO_GL_ERROR(); + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, data.drawId + #ifndef MAGNUM_TARGET_GLES2 + , hasBaseInstance + #endif + }.draw(mesh, data.counts, data.instanceCounts, data.vertexOffsets, nullptr + #ifndef MAGNUM_TARGET_GLES2 + , hasBaseInstance ? Containers::arrayView(data.instanceOffsets) : nullptr + #endif + ); + 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::multiDrawInstancedSparseArrays() { + auto&& data = MultiDrawInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef 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 + + #ifndef MAGNUM_TARGET_GLES2 + if(data.vertexId && !GL::Context::current().isExtensionSupported()) + CORRADE_SKIP("gl_VertexID not supported"); + #endif + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_WEBGL + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ANGLE::base_vertex_base_instance::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() << "is not supported."); + #endif + #else + CORRADE_FAIL_IF(false, "Can't do base instance here."); + #endif + } + + const struct { + Float positionX; + Vector3 value; + } vertexData[] { + {}, /* initial offset */ + {-1.0f/3.0f, data.values[0]}, + { 1.0f/3.0f, data.values[1]}, + }; + const Float instanceData[]{ + {}, /* initial offset */ + -1.0f/3.0f, + 1.0f/3.0f + }; + + Mesh mesh{MeshPrimitive::Points}; + mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawInstancedShader::PositionX{}, MultiDrawInstancedShader::Value{}) + .addVertexBufferInstanced(Buffer{instanceData}, 1, sizeof(instanceData[0]), MultiDrawInstancedShader::PositionY{}); + + #ifdef MAGNUM_TARGET_GLES2 + /* Because ANGLE_instanced_arrays on ES2 / WebGL 1 doesn't even provide + gl_InstanceID ... and there are no integer attributes either */ + const Float instanceId[]{0.0f, 1.0f}; + mesh.addVertexBufferInstanced(Buffer{instanceId}, 1, 0, MultiDrawInstancedShader::InstanceId{}); + #endif + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* The signature accepted by glMultiDrawArraysIndirect() */ + struct Command { + UnsignedInt count; + UnsignedInt instanceCount; + UnsignedInt first; + UnsignedInt baseInstance; + } commands[] { + {data.counts[0], data.instanceCounts[0], data.vertexOffsets[0], data.instanceOffsets[0]}, + {data.counts[1], data.instanceCounts[1], data.vertexOffsets[1], data.instanceOffsets[1]} + }; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, data.drawId + #ifndef MAGNUM_TARGET_GLES2 + , hasBaseInstance + #endif + }.draw(mesh, + Containers::stridedArrayView(commands).slice(&Command::count), + Containers::stridedArrayView(commands).slice(&Command::instanceCount), + Containers::stridedArrayView(commands).slice(&Command::first), + nullptr + #ifndef MAGNUM_TARGET_GLES2 + , + hasBaseInstance ? Containers::stridedArrayView(commands).slice(&Command::baseInstance) : nullptr + #endif + ); + 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 +} + +template void MeshGLTest::multiDrawInstancedIndexed() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + auto&& data = MultiDrawInstancedIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef 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 + + #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]; + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseVertex || hasBaseInstance) { + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() << "is not supported."); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() << "is not supported."); + #endif + #else + CORRADE_FAIL_IF(false, "Can't do base vertex or base instance here."); + #endif + } + + const struct { + Float positionX; + Vector3 value; + } vertexData[] { + {}, /* initial offset */ + {-1.0f/3.0f, data.values[0]}, + { 1.0f/3.0f, data.values[1]}, + }; + const Float instanceData[]{ + {}, /* initial offset */ + -1.0f/3.0f, + 1.0f/3.0f + }; + + Mesh mesh{MeshPrimitive::Points}; + mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawInstancedShader::PositionX{}, MultiDrawInstancedShader::Value{}) + .addVertexBufferInstanced(Buffer{instanceData}, 1, sizeof(instanceData[0]), MultiDrawInstancedShader::PositionY{}) + .setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, data.indices}, 0, MeshIndexType::UnsignedInt); + + #ifdef MAGNUM_TARGET_GLES2 + /* Because ANGLE_instanced_arrays on ES2 / WebGL 1 doesn't even provide + gl_InstanceID ... and there are no integer attributes either */ + const Float instanceId[]{0.0f, 1.0f}; + mesh.addVertexBufferInstanced(Buffer{instanceId}, 1, 0, MultiDrawInstancedShader::InstanceId{}); + #endif + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* Converted to either a 32bit or 64bit type */ + const T indexOffsetsInBytes[]{ + data.indexOffsetsInBytes[0], + data.indexOffsetsInBytes[1] + }; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, false + #ifndef MAGNUM_TARGET_GLES2 + , hasBaseInstance + #endif + }.draw(mesh, data.counts, data.instanceCounts, hasBaseVertex ? Containers::arrayView(data.vertexOffsets) : nullptr, indexOffsetsInBytes + #ifndef MAGNUM_TARGET_GLES2 + , hasBaseInstance ? Containers::arrayView(data.instanceOffsets) : nullptr + #endif + ); + 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 +} + +template void MeshGLTest::multiDrawInstancedIndexedSparseArrays() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + auto&& data = MultiDrawInstancedIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef 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 + + #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]; + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseVertex || hasBaseInstance) { + #ifndef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() << "is not supported."); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() << "is not supported."); + #endif + #else + CORRADE_FAIL_IF(false, "Can't do base vertex or base instance here."); + #endif + } + + const struct { + Float positionX; + Vector3 value; + } vertexData[] { + {}, /* initial offset */ + {-1.0f/3.0f, data.values[0]}, + { 1.0f/3.0f, data.values[1]}, + }; + const Float instanceData[]{ + {}, /* initial offset */ + -1.0f/3.0f, + 1.0f/3.0f + }; + + Mesh mesh{MeshPrimitive::Points}; + mesh.addVertexBuffer(Buffer{vertexData}, sizeof(vertexData[0]), MultiDrawInstancedShader::PositionX{}, MultiDrawInstancedShader::Value{}) + .addVertexBufferInstanced(Buffer{instanceData}, 1, sizeof(instanceData[0]), MultiDrawInstancedShader::PositionY{}) + .setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, data.indices}, 0, MeshIndexType::UnsignedInt); + + #ifdef MAGNUM_TARGET_GLES2 + /* Because ANGLE_instanced_arrays on ES2 / WebGL 1 doesn't even provide + gl_InstanceID ... and there are no integer attributes either */ + const Float instanceId[]{0.0f, 1.0f}; + mesh.addVertexBufferInstanced(Buffer{instanceId}, 1, 0, MultiDrawInstancedShader::InstanceId{}); + #endif + + 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; + T firstIndexInBytes; /* !! */ + UnsignedInt baseVertex; + UnsignedInt baseInstance; + } commands[] { + {data.counts[0], data.instanceCounts[0], data.indexOffsetsInBytes[0], data.vertexOffsets[0], data.instanceOffsets[0]}, + {data.counts[1], data.instanceCounts[1], data.indexOffsetsInBytes[1], data.vertexOffsets[1], data.instanceOffsets[1]} + }; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, false + #ifndef MAGNUM_TARGET_GLES2 + , hasBaseInstance + #endif + }.draw(mesh, + Containers::stridedArrayView(commands).slice(&Command::count), + Containers::stridedArrayView(commands).slice(&Command::instanceCount), + hasBaseVertex ? Containers::stridedArrayView(commands).slice(&Command::baseVertex) : nullptr, + Containers::stridedArrayView(commands).slice(&Command::firstIndexInBytes) + #ifndef MAGNUM_TARGET_GLES2 + , + hasBaseInstance ? Containers::stridedArrayView(commands).slice(&Command::baseInstance) : nullptr + #endif + ); + 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::multiDrawInstancedWrongInstanceCountSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh mesh; + MultiDrawInstancedShader shader; + UnsignedInt counts[3]{}; + UnsignedInt instanceCounts[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, instanceCounts, nullptr, nullptr); + shader.draw(mesh, counts, nullptr, nullptr, nullptr); + CORRADE_COMPARE(out.str(), + "GL::AbstractShaderProgram::draw(): expected 3 instance count items but got 2\n" + "GL::AbstractShaderProgram::draw(): expected 3 instance count items but got 0\n"); +} + +void MeshGLTest::multiDrawInstancedWrongVertexOffsetSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh mesh; + MultiDrawInstancedShader shader; + UnsignedInt counts[3]{}; + UnsignedInt instanceCounts[3]{}; + UnsignedInt vertexOffsets[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, instanceCounts, vertexOffsets, nullptr); + shader.draw(mesh, counts, instanceCounts, 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"); +} + +#ifndef MAGNUM_TARGET_GLES2 +void MeshGLTest::multiDrawInstancedWrongInstanceOffsetSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Mesh mesh; + MultiDrawInstancedShader shader; + UnsignedInt counts[3]{}; + UnsignedInt instanceCounts[3]{}; + UnsignedInt vertexOffsets[3]{}; + UnsignedInt instanceOffsets[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, instanceCounts, vertexOffsets, nullptr, instanceOffsets); + /* Omitting vertex offsets altogether is okay */ + CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): expected 3 instance offset items but got 2\n"); +} +#endif + +void MeshGLTest::multiDrawInstancedIndexedWrongInstanceCountSize() { + #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); + MultiDrawInstancedShader shader; + UnsignedInt counts[3]{}; + UnsignedInt instanceCounts[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, instanceCounts, nullptr, nullptr); + shader.draw(mesh, counts, nullptr, nullptr, nullptr); + CORRADE_COMPARE(out.str(), + "GL::AbstractShaderProgram::draw(): expected 3 instance count items but got 2\n" + "GL::AbstractShaderProgram::draw(): expected 3 instance count items but got 0\n"); +} + +void MeshGLTest::multiDrawInstancedIndexedWrongVertexOffsetSize() { + #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); + MultiDrawInstancedShader shader; + UnsignedInt counts[3]{}; + UnsignedInt instanceCounts[3]{}; + UnsignedInt vertexOffsets[2]{}; + UnsignedInt indexOffsets[3]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, instanceCounts, vertexOffsets, indexOffsets); + /* Omitting vertex offsets altogether is okay */ + CORRADE_COMPARE(out.str(), + "GL::AbstractShaderProgram::draw(): expected 3 vertex offset items but got 2\n"); +} + +void MeshGLTest::multiDrawInstancedIndexedWrongIndexOffsetSize() { + #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); + MultiDrawInstancedShader shader; + UnsignedInt counts[3]{}; + UnsignedInt instanceCounts[3]{}; + UnsignedInt indexOffsets[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, instanceCounts, nullptr, indexOffsets); + shader.draw(mesh, counts, instanceCounts, 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"); +} + +#ifndef MAGNUM_TARGET_GLES2 +void MeshGLTest::multiDrawInstancedIndexedWrongInstanceOffsetSize() { + #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); + MultiDrawInstancedShader shader; + UnsignedInt counts[3]{}; + UnsignedInt instanceCounts[3]{}; + UnsignedInt indexOffsets[3]{}; + UnsignedInt instanceOffsets[2]{}; + + std::ostringstream out; + Error redirectError{&out}; + shader.draw(mesh, counts, instanceCounts, nullptr, indexOffsets, instanceOffsets); + /* Omitting instance offsets altogether is okay */ + CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): expected 3 instance offset items but got 2\n"); +} + +void MeshGLTest::multiDrawInstancedBaseVertexNoExtensionAvailable() { + /* The top-level multidraw extension isn't guarded (the user is expected to + do so), but the base vertex is as the conditions are more complex */ + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ANGLE::multi_draw::string() << "is not supported."); + if(Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() << "is supported."); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw::string() << "is not supported."); + if(Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() << "is supported."); + #endif + + Mesh mesh; + mesh.setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, {2, 1, 0}}, 0, MeshIndexType::UnsignedInt); + + UnsignedInt counts[]{3}; + UnsignedInt instanceCounts[]{3}; + UnsignedInt vertexOffsets[]{0}; + UnsignedInt indexOffsets[]{0}; + + std::ostringstream out; + Error redirectError{&out}; + MultiDrawInstancedShader{}.draw(mesh, counts, instanceCounts, vertexOffsets, indexOffsets); + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh multi-draw with base vertex and base instance specification\n"); + #else + CORRADE_COMPARE(out.str(), "GL::AbstractShaderProgram::draw(): instanced indexed mesh multi-draw with base vertex specification possible only since OpenGL ES 3.0 and WebGL 2.0\n"); + #endif +} + +void MeshGLTest::multiDrawInstancedBaseInstanceNoExtensionAvailable() { + /* The top-level multidraw extension isn't guarded (the user is expected to + do so), but the base vertex is as the conditions are more complex */ + #ifndef MAGNUM_TARGET_WEBGL + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ANGLE::multi_draw::string() << "is not supported."); + if(Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ANGLE::base_vertex_base_instance::string() << "is supported."); + #else + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw::string() << "is not supported."); + if(Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::WEBGL::multi_draw_instanced_base_vertex_base_instance::string() << "is supported."); + #endif + + Mesh nonIndexed; + Mesh indexed; + indexed.setIndexBuffer(Buffer{Buffer::TargetHint::ElementArray, {2, 1, 0}}, 0, MeshIndexType::UnsignedInt); + + UnsignedInt counts[]{3}; + UnsignedInt instanceCounts[]{3}; + UnsignedInt vertexOffsets[]{0}; + UnsignedInt indexOffsets[]{0}; + UnsignedInt instanceOffsets[]{0}; + + std::ostringstream out; + Error redirectError{&out}; + MultiDrawInstancedShader{}.draw(nonIndexed, counts, instanceCounts, vertexOffsets, nullptr, instanceOffsets); + MultiDrawInstancedShader{}.draw(indexed, counts, instanceCounts, nullptr, indexOffsets, instanceOffsets); + CORRADE_COMPARE(out.str(), + "GL::AbstractShaderProgram::draw(): no extension available for instanced mesh multi-draw with base instance specification\n" + "GL::AbstractShaderProgram::draw(): no extension available for instanced indexed mesh multi-draw with base vertex and base instance specification\n"); +} +#endif +#endif + }}}} CORRADE_TEST_MAIN(Magnum::GL::Test::MeshGLTest)