From 1c1d3e5ee845d35eacabf3edf19b885f2c53eb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 20 Feb 2026 00:26:05 +0100 Subject: [PATCH] GL: implement indirect drawing and compute dispatch. --- doc/changelog.dox | 6 +- doc/opengl-mapping.dox | 6 +- doc/opengl-support.dox | 6 +- src/Magnum/GL/AbstractShaderProgram.cpp | 39 + src/Magnum/GL/AbstractShaderProgram.h | 264 ++++- src/Magnum/GL/Buffer.cpp | 3 + src/Magnum/GL/Buffer.h | 16 +- src/Magnum/GL/Extensions.h | 4 +- src/Magnum/GL/Implementation/BufferState.cpp | 8 +- src/Magnum/GL/Implementation/BufferState.h | 6 +- src/Magnum/GL/Mesh.cpp | 66 ++ src/Magnum/GL/Mesh.h | 8 + .../GL/Test/AbstractShaderProgramGLTest.cpp | 216 +++- src/Magnum/GL/Test/MeshGLTest.cpp | 969 +++++++++++++++++- 14 files changed, 1584 insertions(+), 33 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 8f395216b..914cd1d0b 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -197,7 +197,11 @@ See also: - @webgl_extension{WEBGL,multi_draw} - @webgl_extension{WEBGL,draw_instanced_base_vertex_base_instance} - @webgl_extension{WEBGL,multi_draw_instanced_base_vertex_base_instance} -- Recognizing the @gl_extension{EXT,multi_draw_indirect} OpenGL ES extension +- Implemented @ref GL::AbstractShaderProgram::drawIndirect() and + @ref GL::AbstractShaderProgram::dispatchComputeIndirect() along with + recognizing the @gl_extension{EXT,multi_draw_indirect} and + @gl_extension{EXT,base_instance} OpenGL ES extensions (see + [mosra/magnum#687](https://github.com/mosra/magnum/discussions/687)) - Recognizing the @gl_extension2{EXT,memory_object,EXT_external_objects}, @gl_extension2{EXT,semaphore,EXT_external_objects}, @gl_extension2{EXT,memory_object_fd,EXT_external_objects_fd}, diff --git a/doc/opengl-mapping.dox b/doc/opengl-mapping.dox index f04f5a1ac..c79ef5083 100644 --- a/doc/opengl-mapping.dox +++ b/doc/opengl-mapping.dox @@ -140,9 +140,10 @@ OpenGL function | Matching API @fn_gl{DetachShader} | | @fn_gl{DispatchCompute} | @ref GL::AbstractShaderProgram::dispatchCompute() @fn_gl_extension{DispatchComputeGroupSize,ARB,compute_variable_group_size} | | -@fn_gl{DispatchComputeIndirect} | | +@fn_gl{DispatchComputeIndirect} | @ref GL::AbstractShaderProgram::dispatchComputeIndirect() @fn_gl{DrawArrays}, \n @fn_gl{DrawArraysInstanced}, \n @fn_gl{DrawArraysInstancedBaseInstance}, \n @fn_gl{DrawElements}, \n @fn_gl{DrawRangeElements}, \n @fn_gl{DrawElementsBaseVertex}, \n @fn_gl{DrawRangeElementsBaseVertex}, \n @fn_gl{DrawElementsInstanced}, \n @fn_gl{DrawElementsInstancedBaseInstance}, \n @fn_gl{DrawElementsInstancedBaseVertex}, \n @fn_gl{DrawElementsInstancedBaseVertexBaseInstance} | @ref GL::AbstractShaderProgram::drawTransformFeedback() -@fn_gl{DrawArraysIndirect}, \n @fn_gl{DrawElementsIndirect}, \n @fn_gl{MultiDrawArraysIndirect}, \n @fn_gl{MultiDrawElementsIndirect} | | +@fn_gl{DrawArraysIndirect}, \n @fn_gl{DrawElementsIndirect}, \n @fn_gl{MultiDrawArraysIndirect}, \n @fn_gl{MultiDrawElementsIndirect}, \n +@fn_gl{MultiDrawArraysIndirectCount}, \n @fn_gl{MultiDrawElementsIndirectCount} | @ref GL::AbstractShaderProgram::drawIndirect() @fn_gl{DrawBuffer}, \n `glNamedFramebufferDrawBuffer()`, \n @fn_gl{DrawBuffers}, \n `glNamedFramebufferDrawBuffers()` | @ref GL::DefaultFramebuffer::mapForDraw(), \n @ref GL::Framebuffer::mapForDraw() @fn_gl{DrawTransformFeedback}, \n @fn_gl{DrawTransformFeedbackInstanced}, \n @fn_gl{DrawTransformFeedbackStream}, \n @fn_gl{DrawTransformFeedbackStreamInstanced} | @ref GL::AbstractShaderProgram::drawTransformFeedback() @@ -308,7 +309,6 @@ OpenGL function | Matching API @fn_gl_extension{MemoryObjectParameteriv,EXT,external_objects} | | @fn_gl{MinSampleShading} | @ref GL::Renderer::setMinSampleShading() @fn_gl{MultiDrawArrays}, \n @fn_gl{MultiDrawElements}, \n @fn_gl{MultiDrawElementsBaseVertex} | @ref GL::AbstractShaderProgram::draw(const Containers::Iterable&) -@fn_gl{MultiDrawArraysIndirectCount}, \n @fn_gl{MultiDrawElementsIndirectCount} | | @subsection opengl-mapping-functions-o O diff --git a/doc/opengl-support.dox b/doc/opengl-support.dox index 77c34dc9a..9c8289d49 100644 --- a/doc/opengl-support.dox +++ b/doc/opengl-support.dox @@ -143,7 +143,7 @@ GLSL 4.00 | done @gl_extension{ARB,texture_cube_map_array} | done @gl_extension{ARB,texture_gather} | missing limit queries @gl_extension{ARB,texture_query_lod} | done (shading language only) -@gl_extension{ARB,draw_indirect} | | +@gl_extension{ARB,draw_indirect} | done @gl_extension{ARB,gpu_shader5} | missing limit queries @gl_extension{ARB,gpu_shader_fp64} | done @gl_extension{ARB,shader_subroutine} | | @@ -204,7 +204,7 @@ GLSL 4.30 | done @gl_extension{ARB,framebuffer_no_attachments} | done @gl_extension{ARB,internalformat_query2} | only compressed texture block queries @gl_extension{ARB,invalidate_subdata} | done -@gl_extension{ARB,multi_draw_indirect} | | +@gl_extension{ARB,multi_draw_indirect} | done @gl_extension{ARB,program_interface_query} | | @gl_extension{ARB,robust_buffer_access_behavior} | done (nothing to do) @gl_extension{ARB,shader_image_size} | done (shading language only) @@ -262,7 +262,7 @@ GLSL 4.50 | done Extension | Status ------------------------------------------- | ------ GLSL 4.60 | done -@gl_extension{ARB,indirect_parameters} | | +@gl_extension{ARB,indirect_parameters} | done @gl_extension{ARB,shader_draw_parameters} | done (shading language only) @gl_extension{ARB,shader_group_vote} | done (shading language only) @gl_extension{ARB,pipeline_statistics_query} | done diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index 481844eb5..967b71cab 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -511,7 +511,38 @@ AbstractShaderProgram& AbstractShaderProgram::draw(const Containers::Iterable& meshes); + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + /** + * @brief Draw a mesh view indirectly + * @param mesh Mesh to draw + * @param indirectBuffer Buffer to take the indirect command + * from + * @param indirectBufferOffset Four-byte-aligned offset of the + * indirect command in the buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * The @p indirectBuffer is assumed to have a @ref DrawArraysIndirect + * structure for a non-indexed @p mesh or a @ref DrawElementsIndirect + * structure for an indexed @p mesh at @p indirectBufferOffset. + * @see @fn_gl{UseProgram}, @fn_gl_keyword{EnableVertexAttribArray}, + * @fn_gl{BindBuffer}, @fn_gl_keyword{VertexAttribPointer}, + * @fn_gl_keyword{DisableVertexAttribArray} or + * @fn_gl{BindVertexArray}, @fn_gl_keyword{DrawArraysIndirect} or + * @fn_gl_keyword{DrawElementsIndirect} + * @requires_gl40 Extension @gl_extension{ARB,draw_indirect} + * @requires_gl42 Extension @gl_extension{ARB,base_instance} if + * @ref DrawArraysIndirect::instanceOffset or + * @ref DrawElementsIndirect::instanceOffset is not `0` + * @requires_gles31 Indirect rendering isn't available in OpenGL ES + * 3.0 and older. + * @requires_es_extension Extension @gl_extension{EXT,base_instance} if + * @ref DrawArraysIndirect::instanceOffset or + * @ref DrawElementsIndirect::instanceOffset is not `0` + * @requires_gles Indirect rendering isn't availabe in WebGL. + */ + AbstractShaderProgram& drawIndirect(Mesh& mesh, Buffer& indirectBuffer, GLintptr indirectBufferOffset); + + /** + * @brief Draw multiple mesh views indirectly + * @param mesh Mesh to draw + * @param indirectBuffer Buffer to take the indirect command + * from + * @param indirectBufferOffset Four-byte-aligned offset of the + * indirect command in the buffer + * @param count Draw count + * @param stride Four-byte-aligned stride between + * commands or @cpp 0 @ce if it's a tightly-packed sequence of + * @ref DrawArraysIndirect structures for a non-indexed @p mesh or + * @ref DrawElementsIndirect structures for an indexed @p mesh. + * @return Reference to self (for method chaining) + * @m_since_latest + * + * The @p indirectBuffer is assumed to have a list of + * @ref DrawArraysIndirect structures for a non-indexed @p mesh or + * @ref DrawElementsIndirect structures for an indexed @p mesh at + * @p indirectBufferOffset with @p count items and a @p stride. + * @see @fn_gl{UseProgram}, @fn_gl_keyword{EnableVertexAttribArray}, + * @fn_gl{BindBuffer}, @fn_gl_keyword{VertexAttribPointer}, + * @fn_gl_keyword{DisableVertexAttribArray} or + * @fn_gl{BindVertexArray}, @fn_gl_keyword{MultiDrawArraysIndirect} or + * @fn_gl_keyword{MultiDrawElementsIndirect} + * @requires_gl43 Extension @gl_extension{ARB,multi_draw_indirect} + * @requires_gl42 Extension @gl_extension{ARB,base_instance} if + * @ref DrawArraysIndirect::instanceOffset or + * @ref DrawElementsIndirect::instanceOffset is not `0` + * @requires_gles31 OpenGL ES 3.1 and @gl_extension{EXT,multi_draw_indirect} + * @requires_es_extension Extension @gl_extension{EXT,base_instance} if + * @ref DrawArraysIndirect::instanceOffset or + * @ref DrawElementsIndirect::instanceOffset is not `0` + * @requires_gles Indirect rendering isn't availabe in WebGL. + */ + AbstractShaderProgram& drawIndirect(Mesh& mesh, Buffer& indirectBuffer, GLintptr indirectBufferOffset, GLsizei count, GLsizei stride); + #endif + #ifndef MAGNUM_TARGET_GLES + /** + * @brief Draw multiple mesh views indirectly with indirect draw count + * @param mesh Mesh to draw + * @param indirectBuffer Buffer to take the indirect command + * from + * @param indirectBufferOffset Four-byte-aligned offset of the + * indirect command in the buffer + * @param countBuffer Buffer to take the draw count from + * @param countBufferOffset Four-byte-aligned offset of a 32-bit + * draw count value in the buffer + * @param maxCount Max draw count + * @param stride Four-byte-aligned stride between + * commands or @cpp 0 @ce if it's a tightly-packed sequence of + * @ref DrawArraysIndirect structures for a non-indexed @p mesh or + * @ref DrawElementsIndirect structures for an indexed @p mesh. + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Compared to @ref drawIndirect(Mesh&, Buffer&, GLintptr, GLsizei, GLsizei), + * not only the draw commands themselves are taken from a buffer but + * their count as well. The @p indirectBuffer is assumed to have a list + * of @ref DrawArraysIndirect structures for a non-indexed @p mesh or + * @ref DrawElementsIndirect structures for an indexed @p mesh at + * @p indirectBufferOffset with a @p stride. The @p countBuffer is + * assumed to have a 32-bit value describing draw count at + * @p countBufferOffset. If the value in the buffer is larger than + * @p maxCount, only @p maxCount draws is performed. + * @see @ref Buffer::TargetHint::Parameter, @fn_gl{UseProgram}, + * @fn_gl_keyword{EnableVertexAttribArray}, @fn_gl{BindBuffer}, + * @fn_gl_keyword{VertexAttribPointer}, + * @fn_gl_keyword{DisableVertexAttribArray} or + * @fn_gl{BindVertexArray}, + * @fn_gl_keyword{MultiDrawArraysIndirectCount} or + * @fn_gl_keyword{MultiDrawElementsIndirectCount} + * @requires_gl46 Extension @gl_extension{ARB,indirect_parameters} + * @requires_gl42 Extension @gl_extension{ARB,base_instance} if + * @ref DrawArraysIndirect::instanceOffset or + * @ref DrawElementsIndirect::instanceOffset is not `0` + * @requires_gl Indirect multidraw with indirect draw count is not + * available in OpenGL ES or WebGL. + */ + AbstractShaderProgram& drawIndirect(Mesh& mesh, Buffer& indirectBuffer, GLintptr indirectBufferOffset, Buffer& countBuffer, GLintptr countBufferOffset, GLsizei maxCount, GLsizei stride); + /** * @brief Draw a mesh with vertices coming out of transform feedback * @param mesh Mesh to draw @@ -1348,6 +1565,25 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { * @requires_gles Compute shaders are not available in WebGL. */ AbstractShaderProgram& dispatchCompute(const Vector3ui& workgroupCount); + + /** + * @brief Dispatch compute indirectly + * @param indirectBuffer Buffer to take the workgroup count from + * @param indirectBufferOffset Four-byte-aligned offset of the + * workgroup count in the buffer + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Valid only on programs with compute shader attached. The + * @p indirectBuffer is assumed to have three 32-bit values describing + * X, Y and Z workgroup count at @p indirectBufferOffset. + * @see @fn_gl{DispatchComputeIndirect} + * @requires_gl43 Extension @gl_extension{ARB,compute_shader} + * @requires_gles31 Compute shaders are not available in OpenGL ES 3.0 + * and older. + * @requires_gles Compute shaders are not available in WebGL. + */ + AbstractShaderProgram& dispatchComputeIndirect(Buffer& indirectBuffer, GLintptr indirectBufferOffset); #endif /** @@ -2051,6 +2287,9 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { #else #define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_GLES(...) #define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_NOT_GLES(...) \ + __VA_ARGS__& drawIndirect(Magnum::GL::Mesh& mesh, Magnum::GL::Buffer& indirectBuffer, GLintptr indirectBufferOffset, Magnum::GL::Buffer& countBuffer, GLintptr countBufferOffset, GLsizei maxCount, GLsizei stride) { \ + return static_cast<__VA_ARGS__&>(Magnum::GL::AbstractShaderProgram::drawIndirect(mesh, indirectBuffer, indirectBufferOffset, countBuffer, countBufferOffset, maxCount, stride)); \ + } \ __VA_ARGS__& drawTransformFeedback(Magnum::GL::Mesh& mesh, Magnum::GL::TransformFeedback& xfb, Magnum::UnsignedInt stream = 0) { \ return static_cast<__VA_ARGS__&>(Magnum::GL::AbstractShaderProgram::drawTransformFeedback(mesh, xfb, stream)); \ } \ @@ -2065,9 +2304,24 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { #define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_HIDE_XFB #endif #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +/* The drawIndirect() variant with indirect count is already in + _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_NOT_GLES */ +#define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_INDIRECT_IMPLEMENTATION(...) \ + __VA_ARGS__& drawIndirect(Magnum::GL::Mesh& mesh, Magnum::GL::Buffer& indirectBuffer, GLintptr indirectBufferOffset) { \ + return static_cast<__VA_ARGS__&>(Magnum::GL::AbstractShaderProgram::drawIndirect(mesh, indirectBuffer, indirectBufferOffset)); \ + } \ + __VA_ARGS__& drawIndirect(Magnum::GL::Mesh& mesh, Magnum::GL::Buffer& indirectBuffer, GLintptr indirectBufferOffset, GLsizei count, GLsizei stride) { \ + return static_cast<__VA_ARGS__&>(Magnum::GL::AbstractShaderProgram::drawIndirect(mesh, indirectBuffer, indirectBufferOffset, count, stride)); \ + } +#define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_HIDE_DRAW \ + using Magnum::GL::AbstractShaderProgram::draw; \ + using Magnum::GL::AbstractShaderProgram::drawIndirect; #define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_HIDE_COMPUTE \ using Magnum::GL::AbstractShaderProgram::dispatchCompute; #else +#define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_INDIRECT_IMPLEMENTATION(...) +#define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_HIDE_DRAW \ + using Magnum::GL::AbstractShaderProgram::draw; #define _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_HIDE_COMPUTE #endif #endif @@ -2108,7 +2362,8 @@ accidental calls to __VA_ARGS__& draw(const Corrade::Containers::Iterable& meshes) { \ return static_cast<__VA_ARGS__&>(Magnum::GL::AbstractShaderProgram::draw(meshes)); \ } \ - _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_NOT_GLES(__VA_ARGS__) + _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_NOT_GLES(__VA_ARGS__) \ + _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_INDIRECT_IMPLEMENTATION(__VA_ARGS__) #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) /** @@ -2126,11 +2381,14 @@ accidental calls to @relativeref{Magnum::GL,AbstractShaderProgram::draw()} and */ #define MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DISPATCH_IMPLEMENTATION(...) \ private: \ - using Magnum::GL::AbstractShaderProgram::draw; \ + _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_HIDE_DRAW \ _MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION_HIDE_XFB \ public: \ __VA_ARGS__& dispatchCompute(const Magnum::Vector3ui& workgroupCount) { \ return static_cast<__VA_ARGS__&>(Magnum::GL::AbstractShaderProgram::dispatchCompute(workgroupCount)); \ + } \ + __VA_ARGS__& dispatchComputeIndirect(Magnum::GL::Buffer& indirectBuffer, GLintptr indirectBufferOffset) { \ + return static_cast<__VA_ARGS__&>(Magnum::GL::AbstractShaderProgram::dispatchComputeIndirect(indirectBuffer, indirectBufferOffset)); \ } #endif diff --git a/src/Magnum/GL/Buffer.cpp b/src/Magnum/GL/Buffer.cpp index 426ce4337..0f2641c0e 100644 --- a/src/Magnum/GL/Buffer.cpp +++ b/src/Magnum/GL/Buffer.cpp @@ -725,6 +725,9 @@ Debug& operator<<(Debug& debug, const Buffer::TargetHint value) { #endif #endif _c(ElementArray) + #ifndef MAGNUM_TARGET_GLES + _c(Parameter) + #endif #ifndef MAGNUM_TARGET_GLES2 _c(PixelPack) _c(PixelUnpack) diff --git a/src/Magnum/GL/Buffer.h b/src/Magnum/GL/Buffer.h index 9ee08b2d7..4607309d4 100644 --- a/src/Magnum/GL/Buffer.h +++ b/src/Magnum/GL/Buffer.h @@ -297,7 +297,8 @@ class MAGNUM_GL_EXPORT Buffer: public AbstractObject { #ifndef MAGNUM_TARGET_WEBGL /** - * Indirect compute dispatch commands. + * Indirect compute dispatch commands for + * @ref AbstractShaderProgram::dispatchComputeIndirect() * @requires_gl43 Extension @gl_extension{ARB,compute_shader} * @requires_gles31 Compute shaders are not available in OpenGL ES * 3.0 and older. @@ -306,7 +307,7 @@ class MAGNUM_GL_EXPORT Buffer: public AbstractObject { DispatchIndirect = GL_DISPATCH_INDIRECT_BUFFER, /** - * Used for supplying arguments for indirect drawing. + * Indirect draw commands for @ref AbstractShaderProgram::drawIndirect() * @requires_gl40 Extension @gl_extension{ARB,draw_indirect} * @requires_gles31 Indirect drawing is not available in OpenGL ES * 3.0 and older. @@ -319,6 +320,17 @@ class MAGNUM_GL_EXPORT Buffer: public AbstractObject { /** Used for storing vertex indices. */ ElementArray = GL_ELEMENT_ARRAY_BUFFER, + #ifndef MAGNUM_TARGET_GLES + /** + * Indirect draw count parameter for @ref AbstractShaderProgram::drawIndirect(Mesh&, Buffer&, GLintptr, Buffer&, GLintptr, GLsizei, GLsizei) + * @m_since_latest + * @requires_gl46 Extension @gl_extension{ARB,indirect_parameters} + * @requires_gl Indirect multidraw with indirect draw count is not + * available in OpenGL ES or WebGL. + */ + Parameter = GL_PARAMETER_BUFFER, + #endif + #ifndef MAGNUM_TARGET_GLES2 /** * Target for pixel pack operations. diff --git a/src/Magnum/GL/Extensions.h b/src/Magnum/GL/Extensions.h index 5d0712e47..544d42544 100644 --- a/src/Magnum/GL/Extensions.h +++ b/src/Magnum/GL/Extensions.h @@ -547,8 +547,8 @@ namespace ANDROID { _extension( 85,EXT,semaphore, GLES300, None) // #280 /* These two pairs appear to be exclusive so they share the same indices */ #ifndef CORRADE_TARGET_WINDOWS - _extension( 84,EXT,memory_object_fd, GLES300, None) // #281 - _extension( 85,EXT,semaphore_fd, GLES300, None) // #281 + _extension( 86,EXT,memory_object_fd, GLES300, None) // #281 + _extension( 87,EXT,semaphore_fd, GLES300, None) // #281 #else _extension( 86,EXT,memory_object_win32, GLES300, None) // #282 _extension( 87,EXT,semaphore_win32, GLES300, None) // #282 diff --git a/src/Magnum/GL/Implementation/BufferState.cpp b/src/Magnum/GL/Implementation/BufferState.cpp index 32b50ba6b..23608c63e 100644 --- a/src/Magnum/GL/Implementation/BufferState.cpp +++ b/src/Magnum/GL/Implementation/BufferState.cpp @@ -53,9 +53,12 @@ const Buffer::TargetHint BufferState::targetForIndex[] = { Buffer::TargetHint::DispatchIndirect, Buffer::TargetHint::DrawIndirect, Buffer::TargetHint::ShaderStorage, - Buffer::TargetHint::Texture + Buffer::TargetHint::Texture, #endif #endif + #ifndef MAGNUM_TARGET_GLES + Buffer::TargetHint::Parameter + #endif }; std::size_t BufferState::indexForTarget(Buffer::TargetHint target) { @@ -77,6 +80,9 @@ std::size_t BufferState::indexForTarget(Buffer::TargetHint target) { case Buffer::TargetHint::Texture: return 13; #endif #endif + #ifndef MAGNUM_TARGET_GLES + case Buffer::TargetHint::Parameter: return 14; + #endif } CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ diff --git a/src/Magnum/GL/Implementation/BufferState.h b/src/Magnum/GL/Implementation/BufferState.h index e43f29a14..4b6b5af49 100644 --- a/src/Magnum/GL/Implementation/BufferState.h +++ b/src/Magnum/GL/Implementation/BufferState.h @@ -47,8 +47,10 @@ struct BufferState { TargetCount = 2+1, #elif defined(MAGNUM_TARGET_WEBGL) /* WebGL 2 */ TargetCount = 8+1, - #else /* ES3 and desktop */ - TargetCount = 13+1 + #elif defined(MAGNUM_TARGET_GLES) /* ES3 */ + TargetCount = 13+1, + #else /* desktop */ + TargetCount = 14+1, #endif }; diff --git a/src/Magnum/GL/Mesh.cpp b/src/Magnum/GL/Mesh.cpp index d5b0471ba..44634383e 100644 --- a/src/Magnum/GL/Mesh.cpp +++ b/src/Magnum/GL/Mesh.cpp @@ -1028,6 +1028,72 @@ void Mesh::drawInternal(Int count, Int baseVertex, Int instanceCount, GLintptr i state.unbindImplementation(*this); } +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +void Mesh::drawInternal(Buffer& indirectBuffer, const GLintptr indirectBufferOffset) { + const Implementation::MeshState& state = Context::current().state().mesh; + + state.bindImplementation(*this); + indirectBuffer.bindInternal(Buffer::TargetHint::DrawIndirect); + + /* Non-indexed mesh */ + if(!_indexBuffer.id()) + glDrawArraysIndirect(GLenum(_primitive), reinterpret_cast(indirectBufferOffset)); + + /* Indexed mesh */ + else + glDrawElementsIndirect(GLenum(_primitive), GLenum(_indexType), reinterpret_cast(indirectBufferOffset)); + + state.unbindImplementation(*this); +} + +void Mesh::drawInternal(Buffer& indirectBuffer, const GLintptr indirectBufferOffset, GLsizei count, GLsizei stride) { + const Implementation::MeshState& state = Context::current().state().mesh; + + state.bindImplementation(*this); + indirectBuffer.bindInternal(Buffer::TargetHint::DrawIndirect); + + /* Non-indexed mesh */ + if(!_indexBuffer.id()) + #ifndef MAGNUM_TARGET_GLES + glMultiDrawArraysIndirect + #else + glMultiDrawArraysIndirectEXT + #endif + (GLenum(_primitive), reinterpret_cast(indirectBufferOffset), count, stride); + + /* Indexed mesh */ + else + #ifndef MAGNUM_TARGET_GLES + glMultiDrawElementsIndirect + #else + glMultiDrawElementsIndirectEXT + #endif + (GLenum(_primitive), GLenum(_indexType), reinterpret_cast(indirectBufferOffset), count, stride); + + state.unbindImplementation(*this); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +void Mesh::drawInternal(Buffer& indirectBuffer, const GLintptr indirectBufferOffset, Buffer& countBuffer, const GLintptr countBufferOffset, const GLsizei maxCount, const GLsizei stride) { + const Implementation::MeshState& state = Context::current().state().mesh; + + state.bindImplementation(*this); + indirectBuffer.bindInternal(Buffer::TargetHint::DrawIndirect); + countBuffer.bindInternal(Buffer::TargetHint::Parameter); + + /* Non-indexed mesh */ + if(!_indexBuffer.id()) + glMultiDrawArraysIndirectCount(GLenum(_primitive), reinterpret_cast(indirectBufferOffset), countBufferOffset, maxCount, stride); + + /* Indexed mesh */ + else + glMultiDrawElementsIndirectCount(GLenum(_primitive), GLenum(_indexType), reinterpret_cast(indirectBufferOffset), countBufferOffset, maxCount, stride); + + state.unbindImplementation(*this); +} +#endif + #ifndef MAGNUM_TARGET_GLES void Mesh::drawInternal(TransformFeedback& xfb, const UnsignedInt stream, const Int instanceCount) { const Implementation::MeshState& state = Context::current().state().mesh; diff --git a/src/Magnum/GL/Mesh.h b/src/Magnum/GL/Mesh.h index 51345c91b..99f14d7bb 100644 --- a/src/Magnum/GL/Mesh.h +++ b/src/Magnum/GL/Mesh.h @@ -1325,6 +1325,14 @@ class MAGNUM_GL_EXPORT Mesh: public AbstractObject { #endif #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + MAGNUM_GL_LOCAL void drawInternal(Buffer& indirectBuffer, GLintptr indirectBufferOffset); + MAGNUM_GL_LOCAL void drawInternal(Buffer& indirectBuffer, GLintptr indirectBufferOffset, GLsizei count, GLsizei stride); + #endif + #ifndef MAGNUM_TARGET_GLES + MAGNUM_GL_LOCAL void drawInternal(Buffer& indirectBuffer, GLintptr indirectBufferOffset, Buffer& countBuffer, GLintptr countBufferOffset, GLsizei maxCount, GLsizei stride); + #endif + #ifndef MAGNUM_TARGET_GLES MAGNUM_GL_LOCAL void drawInternal(TransformFeedback& xfb, UnsignedInt stream, Int instanceCount); #endif diff --git a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp index 9691d481c..6c32cb882 100644 --- a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp +++ b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp @@ -40,6 +40,7 @@ #include "Magnum/GL/AbstractShaderProgram.h" #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" +#include "Magnum/GL/Framebuffer.h" #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) #include "Magnum/GL/ImageFormat.h" #endif @@ -47,6 +48,8 @@ #include "Magnum/GL/MeshView.h" #include "Magnum/GL/OpenGLTester.h" #include "Magnum/GL/PixelFormat.h" +#include "Magnum/GL/Renderbuffer.h" +#include "Magnum/GL/RenderbufferFormat.h" #include "Magnum/GL/Shader.h" #include "Magnum/GL/Texture.h" #include "Magnum/GL/TextureFormat.h" @@ -106,12 +109,15 @@ struct AbstractShaderProgramGLTest: OpenGLTester { #ifndef MAGNUM_TARGET_WEBGL void compute(); + void computeIndirect(); #endif #endif void subclassDraw(); #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + void subclassDrawIndirect(); void subclassDispatch(); + void subclassDispatchIndirect(); #endif }; @@ -214,12 +220,15 @@ AbstractShaderProgramGLTest::AbstractShaderProgramGLTest() { #ifndef MAGNUM_TARGET_WEBGL &AbstractShaderProgramGLTest::compute, + &AbstractShaderProgramGLTest::computeIndirect, #endif #endif &AbstractShaderProgramGLTest::subclassDraw, #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) - &AbstractShaderProgramGLTest::subclassDispatch + &AbstractShaderProgramGLTest::subclassDrawIndirect, + &AbstractShaderProgramGLTest::subclassDispatch, + &AbstractShaderProgramGLTest::subclassDispatchIndirect, #endif }); } @@ -1311,6 +1320,90 @@ void AbstractShaderProgramGLTest::compute() { TestSuite::Compare::Container); #endif } + +void AbstractShaderProgramGLTest::computeIndirect() { + /* Like compute(), just with the workgroup count taken from a buffer */ + + #ifndef MAGNUM_TARGET_GLES + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ARB::compute_shader::string() << "is not supported."); + #else + if(!Context::current().isVersionSupported(Version::GLES310)) + CORRADE_SKIP("OpenGL ES 3.1 is not supported."); + #endif + + struct ComputeShader: AbstractShaderProgram { + explicit ComputeShader() { + Utility::Resource rs("AbstractShaderProgramGLTest"); + + Shader compute( + #ifndef MAGNUM_TARGET_GLES + Version::GL430, + #else + Version::GLES310, + #endif + Shader::Type::Compute); + compute.addSource(rs.getString("ComputeShader.comp")); + CORRADE_INTERNAL_ASSERT_OUTPUT(compute.compile()); + + attachShader(compute); + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + } + + ComputeShader& setImages(Texture2D& input, Texture2D& output) { + input.bindImage(0, 0, ImageAccess::ReadOnly, ImageFormat::RGBA8UI); + output.bindImage(1, 0, ImageAccess::WriteOnly, ImageFormat::RGBA8UI); + return *this; + } + } shader; + + MAGNUM_VERIFY_NO_GL_ERROR(); + + const Color4ub inData[] = { + { 10, 20, 30, 40}, + { 50, 60, 70, 80}, + { 90, 100, 110, 120}, + {130, 140, 150, 160} + }; + + #ifndef MAGNUM_TARGET_GLES + const Color4ub outData[] = { + { 15, 30, 45, 60}, + { 75, 90, 105, 120}, + {135, 150, 165, 180}, + {195, 210, 225, 240} + }; + #endif + + Texture2D in; + in.setStorage(1, TextureFormat::RGBA8UI, {2, 2}) + .setSubImage(0, {}, ImageView2D{PixelFormat::RGBAInteger, PixelType::UnsignedByte, {2, 2}, inData}); + + Texture2D out; + out.setStorage(1, TextureFormat::RGBA8UI, {2, 2}); + + /* The first element is skipped to verify the offset argument */ + UnsignedInt workgroupCount[]{45, 1, 1, 1}; + Buffer indirectBuffer{workgroupCount}; + + MAGNUM_VERIFY_NO_GL_ERROR(); + + shader.setImages(in, out) + .dispatchComputeIndirect(indirectBuffer, sizeof(UnsignedInt)); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /** @todo Test on ES */ + #ifndef MAGNUM_TARGET_GLES + const auto data = out.image(0, {PixelFormat::RGBAInteger, PixelType::UnsignedByte}).release(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_AS(Containers::arrayCast(data), + Containers::arrayView(outData), + TestSuite::Compare::Container); + #endif +} #endif #endif @@ -1320,10 +1413,47 @@ void AbstractShaderProgramGLTest::compute() { names */ namespace { struct ShaderSubclassDraw: Magnum::GL::AbstractShaderProgram { + explicit ShaderSubclassDraw() { + using namespace Magnum::GL; /* Here I allow myself to be lazy, yes */ + #ifndef MAGNUM_TARGET_GLES + const Version version = Context::current().supportedVersion({Version::GL310, Version::GL300, Version::GL210}); + #else + const Version version = Context::current().supportedVersion({Version::GLES300, Version::GLES200}); + #endif + + Shader vert{version, Shader::Type::Vertex}; + vert.addSource("void main() {}"); + Shader frag{version, Shader::Type::Fragment}; + frag.addSource("void main() {}"); + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile() && frag.compile()); + + attachShaders({vert, frag}); + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + } + MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DRAW_IMPLEMENTATION(ShaderSubclassDraw) }; #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) struct ShaderSubclassDispatch: Magnum::GL::AbstractShaderProgram { + explicit ShaderSubclassDispatch() { + using namespace Magnum::GL; /* Here I allow myself to be lazy, yes */ + + Shader compute( + #ifndef MAGNUM_TARGET_GLES + Version::GL430, + #else + Version::GLES310, + #endif + Shader::Type::Compute); + compute.addSource( + "layout(local_size_x = 1) in;\n" + "void main() {}"); + CORRADE_INTERNAL_ASSERT_OUTPUT(compute.compile()); + + attachShader(compute); + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + } + MAGNUM_GL_ABSTRACTSHADERPROGRAM_SUBCLASS_DISPATCH_IMPLEMENTATION(ShaderSubclassDispatch) }; #endif @@ -1403,17 +1533,95 @@ void AbstractShaderProgramGLTest::subclassDraw() { } #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +void AbstractShaderProgramGLTest::subclassDrawIndirect() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::draw_indirect::string() << "is not supported."); + #else + if(!GL::Context::current().isVersionSupported(Version::GLES310)) + CORRADE_SKIP(Version::GLES310 << "is not supported."); + #endif + + ShaderSubclassDraw shader; + Mesh mesh; + + /* Zero counts, instance counts etc */ + DrawArraysIndirect indirect[1]{}; + Buffer indirectBuffer{indirect}; + UnsignedInt count[1]{}; + Buffer countBuffer{count}; + + /* Need to create a framebuffer because even an empty draw goes through all + GL validation */ + Renderbuffer renderbuffer; + renderbuffer.setStorage(RenderbufferFormat::RGBA8, Vector2i{1}); + + Framebuffer framebuffer{{{}, Vector2i{1}}}; + framebuffer + .attachRenderbuffer(Framebuffer::ColorAttachment{0}, renderbuffer) + .bind(); + + /* These should all be a no-op because we either specify all zeros in the + buffer or a zero multidraw count, and the shader itself does nothing. + And if everything is alright, the returned type should still be + ShaderSubclassDraw& even after all these. */ + ShaderSubclassDraw& out = shader + .drawIndirect(mesh, indirectBuffer, 0) + /* These two require the multi_draw_indirect / indirect_parameters + extensions, but as the count / maxCount is 0 they exit early without + calling into GL at all, it's fine */ + .drawIndirect(mesh, indirectBuffer, 0, 0, 0) + #ifndef MAGNUM_TARGET_GLES + .drawIndirect(mesh, indirectBuffer, 0, countBuffer, 0, 0, 0) + #endif + ; + + CORRADE_VERIFY(out.id()); + MAGNUM_VERIFY_NO_GL_ERROR(); +} + void AbstractShaderProgramGLTest::subclassDispatch() { + #ifndef MAGNUM_TARGET_GLES + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ARB::compute_shader::string() << "is not supported."); + #else + if(!Context::current().isVersionSupported(Version::GLES310)) + CORRADE_SKIP("OpenGL ES 3.1 is not supported."); + #endif + ShaderSubclassDispatch shader; - /* These should all be a no-op because the count is empty. And if - everything is alright, the returned type should still be - ShaderSubclassDispatch& again. */ + /* This one should be a no-op because the count is empty. And if everything + is alright, the returned type should still be ShaderSubclassDispatch& + again. */ ShaderSubclassDispatch& out = shader.dispatchCompute({}); CORRADE_VERIFY(out.id()); MAGNUM_VERIFY_NO_GL_ERROR(); } + +void AbstractShaderProgramGLTest::subclassDispatchIndirect() { + #ifndef MAGNUM_TARGET_GLES + if(!Context::current().isExtensionSupported()) + CORRADE_SKIP(Extensions::ARB::compute_shader::string() << "is not supported."); + #else + if(!Context::current().isVersionSupported(Version::GLES310)) + CORRADE_SKIP("OpenGL ES 3.1 is not supported."); + #endif + + ShaderSubclassDispatch shader; + + UnsignedInt indirect[3]{}; + Buffer indirectBuffer{indirect}; + + /* This one should be a no-op because there are all zeros in the buffer, + and the shader itself does nothing. And if everything is alright, the + returned type should still be ShaderSubclassDispatch& again. */ + ShaderSubclassDispatch& out = shader.dispatchComputeIndirect(indirectBuffer, 0); + + CORRADE_VERIFY(out.id()); + MAGNUM_VERIFY_NO_GL_ERROR(); +} #endif }}}} diff --git a/src/Magnum/GL/Test/MeshGLTest.cpp b/src/Magnum/GL/Test/MeshGLTest.cpp index a42fceb99..882fba1e1 100644 --- a/src/Magnum/GL/Test/MeshGLTest.cpp +++ b/src/Magnum/GL/Test/MeshGLTest.cpp @@ -236,6 +236,21 @@ struct MeshGLTest: OpenGLTester { void multiDrawInstancedBaseInstanceNoExtensionAvailable(); #endif #endif + + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + void drawIndirect(); + void drawIndirectIndexed(); + void multiDrawIndirect(); + void multiDrawIndirectSparseBuffer(); + void multiDrawIndirectIndexed(); + void multiDrawIndirectIndexedSparseBuffer(); + #endif + #ifndef MAGNUM_TARGET_GLES + void multiDrawIndirectCount(); + void multiDrawIndirectCountSparseBuffer(); + void multiDrawIndirectCountIndexed(); + void multiDrawIndirectCountIndexedSparseBuffer(); + #endif }; const struct { @@ -397,7 +412,6 @@ const struct { {0.25f, 0.5f, 0.0f, 1.0f}} }; -#ifdef MAGNUM_TARGET_GLES const struct { const char* name; bool vertexId; @@ -489,6 +503,74 @@ const struct { #endif }; +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +/* Copy of MultiDrawInstancedData, taking just the single draw variants and no + draw ID, and adding variants with vertex / instance offset */ +const struct { + const char* name; + bool vertexId; + Vector3 values[2]; + UnsignedInt count; + UnsignedInt instanceCount; + UnsignedInt vertexOffset; + UnsignedInt instanceOffset; + Vector4 expected; +} DrawIndirectData[] { + {"zero vertex count", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + 0, + 1, + 0, + 0, + {0.0f, 0.0f, 0.0f, 0.0f}}, + {"zero instance count", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + 1, + 0, + 0, + 0, + {0.0f, 0.0f, 0.0f, 0.0f}}, + {"", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + 2, + 2, + 0, + 0, + {0.25f, 0.5f, 0.75f, 1.0f}}, + {"vertex offset", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + 1, + 2, + 1, + 0, + /* Drawing just one vertex per instance, which fills every second + pixel */ + {0.0f, 0.5f, 0.0f, 1.0f}}, + {"vertex offset, vertex ID", true, + {{0.25f, 0.75f, 0.0f}, + {0.0f, 0.5f, 1.0f}}, + 1, + 2, + 1, + 0, + /* Same as above but with the input values shifted */ + {0.0f, 0.5f, 0.0f, 1.0f}}, + {"instance offset", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + 2, + 1, + 0, + 1, + /* Drawing just one instance, which fills the last two pixels */ + {0.0f, 0.0f, 0.75f, 1.0f}}, +}; +#endif + const struct { const char* name; bool vertexId; @@ -556,6 +638,99 @@ const struct { {0.25f, 0.5f, 0.75f, 1.0f}}, #endif }; + +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +/* Copy of MultiDrawInstancedIndexedData, taking just the single draw variants + and no draw ID, and adding variants with index / vertex / instance offset */ +const struct { + const char* name; + bool vertexId; + Vector3 values[2]; + UnsignedInt indices[2]; + UnsignedInt count; + UnsignedInt instanceCount; + UnsignedInt indexOffset; + UnsignedInt vertexOffset; + UnsignedInt instanceOffset; + Vector4 expected; +} DrawIndirectIndexedData[] { + {"zero vertex count", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + 2, + 2, + 0, + 0, + 0, + {0.25f, 0.5f, 0.75f, 1.0f}}, + {"zero instance count", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + 2, + 2, + 0, + 0, + 0, + {0.25f, 0.5f, 0.75f, 1.0f}}, + {"", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + 2, + 2, + 0, + 0, + 0, + {0.25f, 0.5f, 0.75f, 1.0f}}, + {"index offset", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + 1, + 2, + 1, + 0, + 0, + /* Drawing just one vertex per instance, which fills every second + pixel */ + {0.0f, 0.5f, 0.0f, 1.0f}}, + {"vertex offset", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + 1, + 2, + 0, + 1, + 0, + /* Drawing just one vertex per instance, which fills every second + pixel */ + {0.0f, 0.5f, 0.0f, 1.0f}}, + {"vertex offset, vertex ID", true, + {{0.25f, 0.75f, 0.0f}, + {0.0f, 0.5f, 1.0f}}, + {0, 1}, + 1, + 2, + 0, + 1, + 0, + /* Same as above but with the input values shifted */ + {0.0f, 0.5f, 0.0f, 1.0f}}, + {"instance offset", false, + {{0.25f, 0.75f, 0.0f}, + {0.5f, 1.0f, 0.0f}}, + {0, 1}, + 2, + 1, + 0, + 0, + 1, + /* Drawing just one instance, which fills the last two pixels */ + {0.0f, 0.0f, 0.75f, 1.0f}}, +}; #endif using namespace Math::Literals; @@ -770,6 +945,32 @@ MeshGLTest::MeshGLTest() { }); #endif + #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + addInstancedTests({&MeshGLTest::drawIndirect}, + Containers::arraySize(DrawIndirectData)); + + addInstancedTests({&MeshGLTest::drawIndirectIndexed}, + Containers::arraySize(DrawIndirectIndexedData)); + + addInstancedTests({&MeshGLTest::multiDrawIndirect, + &MeshGLTest::multiDrawIndirectSparseBuffer}, + Containers::arraySize(MultiDrawInstancedData)); + + addInstancedTests({&MeshGLTest::multiDrawIndirectIndexed, + &MeshGLTest::multiDrawIndirectIndexedSparseBuffer}, + Containers::arraySize(MultiDrawInstancedIndexedData)); + #endif + + #ifndef MAGNUM_TARGET_GLES + addInstancedTests({&MeshGLTest::multiDrawIndirectCount, + &MeshGLTest::multiDrawIndirectCountSparseBuffer}, + Containers::arraySize(MultiDrawInstancedData)); + + addInstancedTests({&MeshGLTest::multiDrawIndirectCountIndexed, + &MeshGLTest::multiDrawIndirectCountIndexedSparseBuffer}, + Containers::arraySize(MultiDrawInstancedIndexedData)); + #endif + /* Reset clear color to something trivial first */ Renderer::setClearColor(0x000000_rgbf); } @@ -4774,7 +4975,6 @@ void MeshGLTest::multiDrawViewsDifferentMeshes() { CORRADE_COMPARE(out, Utility::format("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; @@ -4796,13 +4996,10 @@ MultiDrawInstancedShader::MultiDrawInstancedShader(bool vertexId, bool drawId , bool instanceOffset #endif ) { - /* Pick GLSL 3.0 / ESSL 3.0 for gl_VertexID, if available */ + /* Pick GLSL 3.1 for gl_InstanceID, 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 + const Version version = Context::current().supportedVersion({Version::GL310, Version::GL300, Version::GL210}); #else const Version version = Context::current().supportedVersion({Version::GLES300, Version::GLES200}); #endif @@ -4810,14 +5007,26 @@ MultiDrawInstancedShader::MultiDrawInstancedShader(bool vertexId, bool drawId Shader frag{version, Shader::Type::Fragment}; if(drawId) vert.addSource( + #ifndef MAGNUM_TARGET_GLES + "#extension GL_ARB_shader_draw_parameters: require\n" + "#define vertexOrDrawIdOrInstanceOffset gl_DrawIDARB\n" + #else "#extension GL_ANGLE_multi_draw: require\n" - "#define vertexOrDrawIdOrInstanceOffset gl_DrawID\n"); + "#define vertexOrDrawIdOrInstanceOffset gl_DrawID\n" + #endif + ); else if(vertexId) vert.addSource( "#define vertexOrDrawIdOrInstanceOffset gl_VertexID\n"); #ifndef MAGNUM_TARGET_GLES2 else if(instanceOffset) vert.addSource( + #ifndef MAGNUM_TARGET_GLES + "#extension GL_ARB_shader_draw_parameters: require\n" + "#define vertexOrDrawIdOrInstanceOffset gl_BaseInstanceARB\n" + #else "#extension GL_ANGLE_base_vertex_base_instance: require\n" - "#define vertexOrDrawIdOrInstanceOffset gl_BaseInstance\n"); + "#define vertexOrDrawIdOrInstanceOffset gl_BaseInstance\n" + #endif + ); #endif else vert.addSource( "#define vertexOrDrawIdOrInstanceOffset 0\n"); @@ -4850,12 +5059,12 @@ MultiDrawInstancedShader::MultiDrawInstancedShader(bool vertexId, bool drawId " gl_Position = vec4(positionX, positionY, 0.0, 1.0);\n" "}\n"); frag.addSource( - "#if defined(GL_ES) && __VERSION__ == 100\n" + "#if (!defined(GL_ES) && __VERSION__ < 130) || (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" + "#if (!defined(GL_ES) && __VERSION__ >= 130) || (defined(GL_ES) && __VERSION__ >= 300)\n" "out mediump vec4 result;\n" "#endif\n" "void main() { result.r = valueInterpolated; }\n"); @@ -4874,6 +5083,7 @@ MultiDrawInstancedShader::MultiDrawInstancedShader(bool vertexId, bool drawId CORRADE_INTERNAL_ASSERT_OUTPUT(link()); } +#ifdef MAGNUM_TARGET_GLES void MeshGLTest::multiDrawInstanced() { auto&& data = MultiDrawInstancedData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -5452,6 +5662,741 @@ void MeshGLTest::multiDrawInstancedBaseInstanceNoExtensionAvailable() { #endif #endif +#if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +void MeshGLTest::drawIndirect() { + auto&& data = DrawIndirectData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Derived from multiDrawIndirect() below, limiting to just one draw */ + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::draw_indirect::string() << "is not supported."); + #else + if(!GL::Context::current().isVersionSupported(Version::GLES310)) + CORRADE_SKIP(Version::GLES310 << "is not supported."); + #endif + + if(data.instanceOffset) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + #else + /** @todo GL_ANGLE_base_vertex_base_instance supports gl_BaseInstance, + but ANGLE on it's own likely doesn't support + EXT_multi_draw_indirect, and implementations that support EXT_multi_draw_indirect don't support the ANGLE extension so + there's likely no point in even trying */ + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "is not supported."); + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "doesn't have the gl_BaseInstance builtin, cannot test."); + #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[]{ + 0, /* 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{}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element is unused to test that the offset is passed properly */ + DrawArraysIndirect indirect[2]; + indirect[1].count = data.count; + indirect[1].instanceCount = data.instanceCount; + indirect[1].vertexOffset = data.vertexOffset; + indirect[1].instanceOffset = data.instanceOffset; + Buffer indirectBuffer{indirect}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, false, !!data.instanceOffset} + .drawIndirect(mesh, indirectBuffer, sizeof(DrawArraysIndirect)); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} + +void MeshGLTest::drawIndirectIndexed() { + auto&& data = DrawIndirectIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Derived from multiDrawIndirectIndexed() below, limiting to just one + draw */ + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::multi_draw_indirect::string() << "is not supported."); + #endif + + if(data.instanceOffset) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + #else + /** @todo GL_ANGLE_base_vertex_base_instance supports gl_BaseInstance, + but ANGLE on it's own likely doesn't support + EXT_multi_draw_indirect, and implementations that support EXT_multi_draw_indirect don't support the ANGLE extension so + there's likely no point in even trying */ + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "is not supported."); + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "doesn't have the gl_BaseInstance builtin, cannot test."); + #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[]{ + 0, /* 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); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element is unused to test that the offset is passed properly */ + DrawElementsIndirect indirect[2]; + indirect[1].count = data.count; + indirect[1].instanceCount = data.instanceCount; + indirect[1].indexOffset = data.indexOffset; + indirect[1].vertexOffset = data.vertexOffset; + indirect[1].instanceOffset = data.instanceOffset; + Buffer indirectBuffer{indirect}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, false, !!data.instanceOffset} + .drawIndirect(mesh, indirectBuffer, sizeof(DrawElementsIndirect)); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} + +void MeshGLTest::multiDrawIndirect() { + auto&& data = MultiDrawInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Derived from multiDrawInstanced() */ + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::multi_draw_indirect::string() << "is not supported."); + #endif + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + #else + /** @todo GL_ANGLE_base_vertex_base_instance supports gl_BaseInstance, + but ANGLE on it's own likely doesn't support + EXT_multi_draw_indirect, and implementations that support EXT_multi_draw_indirect don't support the ANGLE extension so + there's likely no point in even trying */ + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "is not supported."); + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "doesn't have the gl_BaseInstance builtin, cannot test."); + #endif + } + + #ifdef MAGNUM_TARGET_GLES + /** @todo GL_ANGLE_multi_draw supports gl_DrawID, but ANGLE on it's own + likely doesn't support EXT_multi_draw_indirect, and implementations + that support EXT_multi_draw_indirect don't support the ANGLE extension + so there's likely no point in even trying */ + if(data.drawId) + CORRADE_SKIP("There is no GLES platform supporting both indirect draw and gl_DrawID, cannot test."); + #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[]{ + 0, /* 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{}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element is unused to test that the offset is passed properly */ + DrawArraysIndirect indirect[3]; + for(Int i: {0, 1}) { + indirect[i + 1].count = data.counts[i]; + indirect[i + 1].instanceCount = data.instanceCounts[i]; + indirect[i + 1].vertexOffset = data.vertexOffsets[i]; + indirect[i + 1].instanceOffset = data.instanceOffsets[i]; + } + Buffer indirectBuffer{indirect}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, data.drawId, hasBaseInstance} + .drawIndirect(mesh, indirectBuffer, sizeof(DrawArraysIndirect), data.counts[1] ? 2 : 1, 0); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} + +void MeshGLTest::multiDrawIndirectSparseBuffer() { + auto&& data = MultiDrawInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Copy of multiDrawIndirect(), except for the indirect array having extra + space at the end which is being reflected in the stride */ + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::multi_draw_indirect::string() << "is not supported."); + #endif + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + #else + /** @todo GL_ANGLE_base_vertex_base_instance supports gl_BaseInstance, + but ANGLE on it's own likely doesn't support + EXT_multi_draw_indirect, and implementations that support EXT_multi_draw_indirect don't support the ANGLE extension so + there's likely no point in even trying */ + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "is not supported."); + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "doesn't have the gl_BaseInstance builtin, cannot test."); + #endif + } + + #ifdef MAGNUM_TARGET_GLES + /** @todo GL_ANGLE_multi_draw supports gl_DrawID, but ANGLE on it's own + likely doesn't support EXT_multi_draw_indirect, and implementations + that support EXT_multi_draw_indirect don't support the ANGLE extension + so there's likely no point in even trying */ + if(data.drawId) + CORRADE_SKIP("There is no GLES platform supporting both indirect draw and gl_DrawID, cannot test."); + #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[]{ + 0, /* 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{}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element is unused to test that the offset is passed properly */ + struct Indirect { + DrawArraysIndirect command; + UnsignedInt:32; + } indirect[3]; + for(Int i: {0, 1}) { + indirect[i + 1].command.count = data.counts[i]; + indirect[i + 1].command.instanceCount = data.instanceCounts[i]; + indirect[i + 1].command.vertexOffset = data.vertexOffsets[i]; + indirect[i + 1].command.instanceOffset = data.instanceOffsets[i]; + } + Buffer indirectBuffer{indirect}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, data.drawId, hasBaseInstance} + .drawIndirect(mesh, indirectBuffer, sizeof(Indirect), data.counts[1] ? 2 : 1, sizeof(Indirect)); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} + +void MeshGLTest::multiDrawIndirectIndexed() { + auto&& data = MultiDrawInstancedIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Derived from multiDrawInstancedIndexed() */ + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::multi_draw_indirect::string() << "is not supported."); + #endif + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + #else + /** @todo GL_ANGLE_base_vertex_base_instance supports gl_BaseInstance, + but ANGLE on it's own likely doesn't support + EXT_multi_draw_indirect, and implementations that support EXT_multi_draw_indirect don't support the ANGLE extension so + there's likely no point in even trying */ + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "is not supported."); + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "doesn't have the gl_BaseInstance builtin, cannot test."); + #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[]{ + 0, /* 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); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element is unused to test that the offset is passed properly */ + DrawElementsIndirect indirect[3]; + for(Int i: {0, 1}) { + indirect[i + 1].count = data.counts[i]; + indirect[i + 1].instanceCount = data.instanceCounts[i]; + /* The test data were made for the client-side multi-draw extensions + first, which accept index offsets in bytes, not elements */ + indirect[i + 1].indexOffset = data.indexOffsetsInBytes[i]/4; + indirect[i + 1].vertexOffset = data.vertexOffsets[i]; + indirect[i + 1].instanceOffset = data.instanceOffsets[i]; + } + Buffer indirectBuffer{indirect}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, false, hasBaseInstance} + .drawIndirect(mesh, indirectBuffer, sizeof(DrawElementsIndirect), data.counts[1] ? 2 : 1, 0); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} + +void MeshGLTest::multiDrawIndirectIndexedSparseBuffer() { + auto&& data = MultiDrawInstancedIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Copy of multiDrawIndirectIndexed(), except for the indirect array having + extra space at the end which is being reflected in the stride */ + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + #else + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::multi_draw_indirect::string() << "is not supported."); + #endif + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + #else + /** @todo GL_ANGLE_base_vertex_base_instance supports gl_BaseInstance, + but ANGLE on it's own likely doesn't support + EXT_multi_draw_indirect, and implementations that support EXT_multi_draw_indirect don't support the ANGLE extension so + there's likely no point in even trying */ + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "is not supported."); + CORRADE_SKIP(GL::Extensions::EXT::base_instance::string() << "doesn't have the gl_BaseInstance builtin, cannot test."); + #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[]{ + 0, /* 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); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element is unused to test that the offset is passed properly */ + struct Indirect { + DrawElementsIndirect command; + UnsignedInt:32; + } indirect[3]; + for(Int i: {0, 1}) { + indirect[i + 1].command.count = data.counts[i]; + indirect[i + 1].command.instanceCount = data.instanceCounts[i]; + /* The test data were made for the client-side multi-draw extensions + first, which accept index offsets in bytes, not elements */ + indirect[i + 1].command.indexOffset = data.indexOffsetsInBytes[i]/4; + indirect[i + 1].command.vertexOffset = data.vertexOffsets[i]; + indirect[i + 1].command.instanceOffset = data.instanceOffsets[i]; + } + Buffer indirectBuffer{indirect}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, false, hasBaseInstance} + .drawIndirect(mesh, indirectBuffer, sizeof(Indirect), data.counts[1] ? 2 : 1, sizeof(Indirect)); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +void MeshGLTest::multiDrawIndirectCount() { + auto&& data = MultiDrawInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Like multiDrawIndirect(), but with count supplied via a buffer as + well (and extension checks adjusted appropriately) */ + + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + } + + 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[]{ + 0, /* 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{}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element of each is unused to test that the offset is passed + properly */ + DrawArraysIndirect indirect[3]; + for(Int i: {0, 1}) { + indirect[i + 1].count = data.counts[i]; + indirect[i + 1].instanceCount = data.instanceCounts[i]; + indirect[i + 1].vertexOffset = data.vertexOffsets[i]; + indirect[i + 1].instanceOffset = data.instanceOffsets[i]; + } + Buffer indirectBuffer{indirect}; + UnsignedInt count[2]{ + 0, + data.counts[1] ? 2u : 1u + }; + Buffer countBuffer{count}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, data.drawId, hasBaseInstance} + .drawIndirect(mesh, indirectBuffer, sizeof(DrawArraysIndirect), countBuffer, sizeof(UnsignedInt), 2, 0); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} + +void MeshGLTest::multiDrawIndirectCountSparseBuffer() { + auto&& data = MultiDrawInstancedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Copy of multiDrawIndirectCount(), except for the indirect array having + extra space at the end which is being reflected in the stride */ + + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + } + + 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[]{ + 0, /* 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{}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element of each is unused to test that the offset is passed + properly */ + struct Indirect { + DrawArraysIndirect command; + UnsignedInt:32; + } indirect[3]; + for(Int i: {0, 1}) { + indirect[i + 1].command.count = data.counts[i]; + indirect[i + 1].command.instanceCount = data.instanceCounts[i]; + indirect[i + 1].command.vertexOffset = data.vertexOffsets[i]; + indirect[i + 1].command.instanceOffset = data.instanceOffsets[i]; + } + Buffer indirectBuffer{indirect}; + UnsignedInt count[2]{ + 0, + data.counts[1] ? 2u : 1u + }; + Buffer countBuffer{count}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, data.drawId, hasBaseInstance} + .drawIndirect(mesh, indirectBuffer, sizeof(Indirect), countBuffer, sizeof(UnsignedInt), 2, sizeof(Indirect)); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} + +void MeshGLTest::multiDrawIndirectCountIndexed() { + auto&& data = MultiDrawInstancedIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Like multiDrawIndirectIndexed(), but with count supplied via a buffer as + well (and extension checks adjusted appropriately) */ + + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + } + + 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[]{ + 0, /* 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); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element is unused to test that the offset is passed properly */ + DrawElementsIndirect indirect[3]; + for(Int i: {0, 1}) { + indirect[i + 1].count = data.counts[i]; + indirect[i + 1].instanceCount = data.instanceCounts[i]; + /* The test data were made for the client-side multi-draw extensions + first, which accept index offsets in bytes, not elements */ + indirect[i + 1].indexOffset = data.indexOffsetsInBytes[i]/4; + indirect[i + 1].vertexOffset = data.vertexOffsets[i]; + indirect[i + 1].instanceOffset = data.instanceOffsets[i]; + } + Buffer indirectBuffer{indirect}; + UnsignedInt count[2]{ + 0, + data.counts[1] ? 2u : 1u + }; + Buffer countBuffer{count}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, false, hasBaseInstance} + .drawIndirect(mesh, indirectBuffer, sizeof(DrawElementsIndirect), countBuffer, sizeof(UnsignedInt), 2, 0); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} + +void MeshGLTest::multiDrawIndirectCountIndexedSparseBuffer() { + auto&& data = MultiDrawInstancedIndexedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Copy of multiDrawIndirectCountIndexed(), except for the indirect array + having extra space at the end which is being reflected in the stride */ + + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::multi_draw_indirect::string() << "is not supported."); + + bool hasBaseInstance = data.instanceOffsets[0] || data.instanceOffsets[1]; + if(hasBaseInstance) { + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::base_instance::string() << "is not supported."); + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::ARB::shader_draw_parameters::string() << "for the gl_BaseInstance builtin is not supported."); + } + + 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[]{ + 0, /* 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); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* First element is unused to test that the offset is passed properly */ + struct Indirect { + DrawElementsIndirect command; + UnsignedInt:32; + } indirect[3]; + for(Int i: {0, 1}) { + indirect[i + 1].command.count = data.counts[i]; + indirect[i + 1].command.instanceCount = data.instanceCounts[i]; + /* The test data were made for the client-side multi-draw extensions + first, which accept index offsets in bytes, not elements */ + indirect[i + 1].command.indexOffset = data.indexOffsetsInBytes[i]/4; + indirect[i + 1].command.vertexOffset = data.vertexOffsets[i]; + indirect[i + 1].command.instanceOffset = data.instanceOffsets[i]; + } + Buffer indirectBuffer{indirect}; + UnsignedInt count[2]{ + 0, + data.counts[1] ? 2u : 1u + }; + Buffer countBuffer{count}; + + MultiDrawChecker checker; + MultiDrawInstancedShader{data.vertexId, false, hasBaseInstance} + .drawIndirect(mesh, indirectBuffer, sizeof(Indirect), countBuffer, sizeof(UnsignedInt), 2, sizeof(Indirect)); + Vector4 value = checker.get(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH(value, data.expected, + TestSuite::Compare::around(Vector4{1.0f/255.0f})); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::GL::Test::MeshGLTest)