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)