From 5998f46e12d4ff55d1f22c9a5fefde9b5886e591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 18 May 2014 17:59:22 +0200 Subject: [PATCH] Added support for glMultiDrawArrays()/glMultiDrawElements(). In OpenGL ES this is implemented in EXT_multi_draw_arrays extension, if it is not available, the functionality is emulated using sequence of normal draw() calls. --- doc/opengl-mapping.dox | 3 +- doc/opengl-support.dox | 3 +- src/Magnum/Context.cpp | 1 + src/Magnum/Extensions.h | 1 + src/Magnum/Implementation/MeshState.cpp | 10 ++ src/Magnum/Implementation/MeshState.h | 4 + .../Implementation/setupDriverWorkarounds.cpp | 1 + src/Magnum/Mesh.h | 13 +- src/Magnum/MeshView.cpp | 105 +++++++++++++ src/Magnum/MeshView.h | 33 +++++ src/Magnum/Test/MeshGLTest.cpp | 139 +++++++++++++++++- 11 files changed, 304 insertions(+), 9 deletions(-) diff --git a/doc/opengl-mapping.dox b/doc/opengl-mapping.dox index 9b06599c0..41d0c5b16 100644 --- a/doc/opengl-mapping.dox +++ b/doc/opengl-mapping.dox @@ -196,8 +196,7 @@ OpenGL function | Matching API @fn_gl_extension{MapBufferSubData,CHROMIUM,map_sub}, @fn_gl_extension{UnmapBufferSubData,CHROMIUM,map_sub} | @ref Buffer::mapSub(), @ref Buffer::unmapSub() @fn_gl{MemoryBarrier} | | @fn_gl{MinSampleShading} | | -@fn_gl{MultiDrawArrays}, \n @fn_gl{MultiDrawElements} | | -@fn_gl{MultiDrawElementsBaseVertex} | | +@fn_gl{MultiDrawArrays}, \n @fn_gl{MultiDrawElements}, \n @fn_gl{MultiDrawElementsBaseVertex} | @ref MeshView::draw(AbstractShaderProgram&, std::initializer_list>) @fn_gl{ObjectLabel}, \n @fn_gl{ObjectPtrLabel}, \n @fn_gl_extension2{LabelObject,EXT,debug_label} | @ref AbstractShaderProgram::setLabel(), \n @ref AbstractQuery::setLabel(), \n @ref AbstractTexture::setLabel(), \n @ref Buffer::setLabel(), \n @ref Framebuffer::setLabel(), \n @ref Mesh::setLabel(), \n @ref Renderbuffer::setLabel(), \n @ref Shader::setLabel() @fn_gl{PatchParameter} | | @fn_gl{PauseTransformFeedback}, @fn_gl{ResumeTransformFeedback} | | diff --git a/doc/opengl-support.dox b/doc/opengl-support.dox index 3e71f098f..1acebb414 100644 --- a/doc/opengl-support.dox +++ b/doc/opengl-support.dox @@ -95,7 +95,7 @@ following: -------------------------------------------- | ------ @extension{ARB,geometry_shader4} | missing layered attachments @extension{ARB,depth_clamp} | done -@extension{ARB,draw_elements_base_vertex} | missing `Multi*` command +@extension{ARB,draw_elements_base_vertex} | done @extension{ARB,fragment_coord_conventions} | done (shading language only) @extension{ARB,provoking_vertex} | done @extension{ARB,seamless_cube_map} | done @@ -299,6 +299,7 @@ Only extensions not already listed in above tables are included here. @es_extension{CHROMIUM,map_sub} | only buffer mapping @es_extension{EXT,texture_format_BGRA8888} | done @es_extension{EXT,read_format_bgra} | done +@es_extension2{EXT,multi_draw_arrays,multi_draw_arrays} | done @es_extension{EXT,disjoint_timer_query} | only time elapsed query @es_extension{EXT,separate_shader_objects} | only direct uniform binding @es_extension{EXT,sRGB} | done diff --git a/src/Magnum/Context.cpp b/src/Magnum/Context.cpp index c8e077396..7a2eaf812 100644 --- a/src/Magnum/Context.cpp +++ b/src/Magnum/Context.cpp @@ -197,6 +197,7 @@ const std::vector& Extension::extensions(Version version) { _extension(GL,EXT,texture_filter_anisotropic), _extension(GL,EXT,texture_format_BGRA8888), _extension(GL,EXT,read_format_bgra), + _extension(GL,EXT,multi_draw_arrays), _extension(GL,EXT,debug_label), _extension(GL,EXT,debug_marker), _extension(GL,EXT,disjoint_timer_query), diff --git a/src/Magnum/Extensions.h b/src/Magnum/Extensions.h index d5257f838..55f02dfcc 100644 --- a/src/Magnum/Extensions.h +++ b/src/Magnum/Extensions.h @@ -239,6 +239,7 @@ namespace GL { _extension(GL,EXT,blend_minmax, GLES200, GLES300) // #65 #endif _extension(GL,EXT,read_format_bgra, GLES200, None) // #66 + _extension(GL,EXT,multi_draw_arrays, GLES200, None) // #67 #ifdef MAGNUM_TARGET_GLES2 _extension(GL,EXT,shader_texture_lod, GLES200, GLES300) // #77 #endif diff --git a/src/Magnum/Implementation/MeshState.cpp b/src/Magnum/Implementation/MeshState.cpp index 9af9a0d76..eed3a9763 100644 --- a/src/Magnum/Implementation/MeshState.cpp +++ b/src/Magnum/Implementation/MeshState.cpp @@ -27,6 +27,7 @@ #include "Magnum/Context.h" #include "Magnum/Extensions.h" +#include "Magnum/MeshView.h" #include "State.h" @@ -95,6 +96,15 @@ MeshState::MeshState(Context& context, std::vector& extensions): cu } #endif + #ifdef MAGNUM_TARGET_GLES + /* Multi draw implementation on ES */ + if(context.isExtensionSupported()) { + extensions.push_back(Extensions::GL::EXT::multi_draw_arrays::string()); + + multiDrawImplementation = &MeshView::multiDrawImplementationDefault; + } else multiDrawImplementation = &MeshView::multiDrawImplementationFallback; + #endif + #ifdef MAGNUM_TARGET_GLES2 /* Instanced draw ímplementation on ES2 */ if(context.isExtensionSupported()) { diff --git a/src/Magnum/Implementation/MeshState.h b/src/Magnum/Implementation/MeshState.h index c5ba22bef..0b0349cec 100644 --- a/src/Magnum/Implementation/MeshState.h +++ b/src/Magnum/Implementation/MeshState.h @@ -58,6 +58,10 @@ struct MeshState { void(Mesh::*drawElementsInstancedImplementation)(GLsizei, GLintptr, GLsizei); #endif + #ifdef MAGNUM_TARGET_GLES + void(*multiDrawImplementation)(std::initializer_list>); + #endif + GLuint currentVAO; #ifndef MAGNUM_TARGET_GLES2 GLint maxElementsIndices, maxElementsVertices; diff --git a/src/Magnum/Implementation/setupDriverWorkarounds.cpp b/src/Magnum/Implementation/setupDriverWorkarounds.cpp index e909bf48f..a29332253 100644 --- a/src/Magnum/Implementation/setupDriverWorkarounds.cpp +++ b/src/Magnum/Implementation/setupDriverWorkarounds.cpp @@ -56,6 +56,7 @@ void Context::setupDriverWorkarounds() { #ifndef CORRADE_TARGET_NACL _setRequiredVersion(GL::CHROMIUM::map_sub, None); #endif + _setRequiredVersion(GL::EXT::multi_draw_arrays, None); _setRequiredVersion(GL::EXT::debug_label, None); _setRequiredVersion(GL::EXT::debug_marker, None); _setRequiredVersion(GL::EXT::disjoint_timer_query, None); diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index ac6697013..d37831ae6 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -750,11 +750,14 @@ class MAGNUM_EXPORT Mesh: public AbstractObject { * commands are issued. See also * @ref AbstractShaderProgram-rendering-workflow "AbstractShaderProgram documentation" * for more information. - * @see @ref setCount(), @ref setInstanceCount(), @fn_gl{UseProgram}, - * @fn_gl{EnableVertexAttribArray}, @fn_gl{BindBuffer}, - * @fn_gl{VertexAttribPointer}, @fn_gl{DisableVertexAttribArray} - * or @fn_gl{BindVertexArray} (if @extension{APPLE,vertex_array_object} - * is available), @fn_gl{DrawArrays}/@fn_gl{DrawArraysInstanced}/ + * @see @ref setCount(), @ref setInstanceCount(), + * @ref MeshView::draw(AbstractShaderProgram&), + * @ref MeshView::draw(AbstractShaderProgram&, std::initializer_list>), + * @fn_gl{UseProgram}, @fn_gl{EnableVertexAttribArray}, + * @fn_gl{BindBuffer}, @fn_gl{VertexAttribPointer}, + * @fn_gl{DisableVertexAttribArray} or @fn_gl{BindVertexArray} (if + * @extension{APPLE,vertex_array_object} is available), + * @fn_gl{DrawArrays}/@fn_gl{DrawArraysInstanced}/ * @fn_gl{DrawArraysInstancedBaseInstance} or @fn_gl{DrawElements}/ * @fn_gl{DrawRangeElements}/@fn_gl{DrawElementsBaseVertex}/ * @fn_gl{DrawRangeElementsBaseVertex}/@fn_gl{DrawElementsInstanced}/ diff --git a/src/Magnum/MeshView.cpp b/src/Magnum/MeshView.cpp index 6db77e719..a4994d0cb 100644 --- a/src/Magnum/MeshView.cpp +++ b/src/Magnum/MeshView.cpp @@ -25,10 +25,115 @@ #include "MeshView.h" +#include +#include + +#include "Magnum/Context.h" #include "Magnum/Mesh.h" +#include "Implementation/State.h" +#include "Implementation/MeshState.h" + namespace Magnum { +void MeshView::draw(AbstractShaderProgram& shader, std::initializer_list> meshes) { + /* Why std::initializer_list doesn't have empty()? */ + if(!meshes.size()) return; + + shader.use(); + + #ifndef CORRADE_NO_ASSERT + const Mesh* original = meshes.begin()->get()._original; + for(MeshView& mesh: meshes) + CORRADE_ASSERT(mesh._original == original, "MeshView::draw(): all meshes must be views of the same original mesh", ); + #endif + + #ifndef MAGNUM_TARGET_GLES + multiDrawImplementationDefault(meshes); + #else + Context::current()->state().mesh->multiDrawImplementation(meshes); + #endif +} + +void MeshView::multiDrawImplementationDefault(std::initializer_list> meshes) { + CORRADE_INTERNAL_ASSERT(meshes.size()); + + const Implementation::MeshState& state = *Context::current()->state().mesh; + + Mesh& original = *meshes.begin()->get()._original; + Containers::Array count{meshes.size()}; + Containers::Array indices{meshes.size()}; + Containers::Array baseVertex{meshes.size()}; + + /* Gather the parameters */ + #ifndef MAGNUM_TARGET_GLES + bool hasBaseVertex = false; + #endif + std::size_t i = 0; + for(MeshView& mesh: meshes) { + CORRADE_ASSERT(mesh._instanceCount == 1, "MeshView::draw(): cannot draw multiple instanced meshes", ); + + count[i] = mesh._count; + indices[i] = reinterpret_cast(mesh._indexOffset); + baseVertex[i] = mesh._baseVertex; + + if(mesh._baseVertex) { + #ifndef MAGNUM_TARGET_GLES + hasBaseVertex = true; + #else + CORRADE_ASSERT(!original._indexBuffer, "MeshView::draw(): desktop OpenGL is required for base vertex specification in indexed meshes", ); + #endif + } + + ++i; + } + + (original.*state.bindImplementation)(); + + /* Non-indexed meshes */ + if(!original._indexBuffer) { + #ifndef MAGNUM_TARGET_GLES + glMultiDrawArrays(GLenum(original._primitive), baseVertex, count, meshes.size()); + #else + //glMultiDrawArraysEXT(GLenum(original._primitive), baseVertex, count, meshes.size()); + CORRADE_INTERNAL_ASSERT(false); + #endif + + /* Indexed meshes */ + } else { + /* Indexed meshes with base vertex */ + #ifndef MAGNUM_TARGET_GLES + if(hasBaseVertex) { + glMultiDrawElementsBaseVertex(GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size(), baseVertex); + + /* Indexed meshes */ + } else + #endif + { + #ifndef MAGNUM_TARGET_GLES + glMultiDrawElements(GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size()); + #else + //glMultiDrawElements(GLenum(original._primitive), count, GLenum(original._indexType), indices, meshes.size()); + CORRADE_INTERNAL_ASSERT(false); + #endif + } + } + + (original.*state.unbindImplementation)(); +} + +#ifdef MAGNUM_TARGET_GLES +void MeshView::multiDrawImplementationFallback(std::initializer_list> meshes) { + for(MeshView& mesh: meshes) { + #ifndef MAGNUM_TARGET_GLES2 + mesh._original->drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset, mesh._indexStart, mesh._indexEnd); + #else + mesh._original->drawInternal(mesh._count, mesh._baseVertex, mesh._instanceCount, mesh._indexOffset); + #endif + } +} +#endif + MeshView& MeshView::setIndexRange(Int first) { _indexOffset = _original->_indexOffset + first*_original->indexSize(); return *this; diff --git a/src/Magnum/MeshView.h b/src/Magnum/MeshView.h index 94d5ac7f2..b3446e575 100644 --- a/src/Magnum/MeshView.h +++ b/src/Magnum/MeshView.h @@ -29,12 +29,17 @@ * @brief Class @ref Magnum::MeshView */ +#include +#include + #include "Magnum/Magnum.h" #include "Magnum/OpenGL.h" #include "Magnum/visibility.h" namespace Magnum { +namespace Implementation { struct MeshState; } + /** @brief %Mesh view @@ -52,7 +57,31 @@ You must ensure that the original mesh remains available for whole view lifetime. */ class MAGNUM_EXPORT MeshView { + friend struct Implementation::MeshState; + public: + /** + * @brief Draw multiple meshes at once + * + * In OpenGL ES, if @es_extension2{EXT,multi_draw_arrays,multi_draw_arrays} + * is not present, the functionality is emulated using sequence of + * @ref draw(AbstractShaderProgram&) calls. + * @attention All meshes must be views of the same original mesh and + * must not be instanced. + * @see @ref draw(AbstractShaderProgram&), @fn_gl{UseProgram}, + * @fn_gl{EnableVertexAttribArray}, @fn_gl{BindBuffer}, + * @fn_gl{VertexAttribPointer}, @fn_gl{DisableVertexAttribArray} + * or @fn_gl{BindVertexArray} (if @extension{APPLE,vertex_array_object} + * is available), @fn_gl{MultiDrawArrays} or + * @fn_gl{MultiDrawElements}/@fn_gl{MultiDrawElementsBaseVertex} + */ + static void draw(AbstractShaderProgram& shader, std::initializer_list> meshes); + + /** @overload */ + static void draw(AbstractShaderProgram&& shader, std::initializer_list> meshes) { + draw(shader, meshes); + } + /** * @brief Constructor * @param original Original, already configured mesh @@ -212,6 +241,7 @@ class MAGNUM_EXPORT MeshView { * @brief Draw the mesh * * See @ref Mesh::draw() for more information. + * @see @ref draw(AbstractShaderProgram&, std::initializer_list>) */ void draw(AbstractShaderProgram& shader); void draw(AbstractShaderProgram&& shader) { draw(shader); } /**< @overload */ @@ -226,6 +256,9 @@ class MAGNUM_EXPORT MeshView { #endif private: + static MAGNUM_LOCAL void multiDrawImplementationDefault(std::initializer_list> meshes); + static MAGNUM_LOCAL void multiDrawImplementationFallback(std::initializer_list> meshes); + Mesh* _original; Int _count, _baseVertex, _instanceCount; diff --git a/src/Magnum/Test/MeshGLTest.cpp b/src/Magnum/Test/MeshGLTest.cpp index 2af08f897..5fbefcfaf 100644 --- a/src/Magnum/Test/MeshGLTest.cpp +++ b/src/Magnum/Test/MeshGLTest.cpp @@ -126,6 +126,12 @@ class MeshGLTest: public AbstractOpenGLTester { #ifndef MAGNUM_TARGET_GLES void addVertexBufferInstancedDouble(); #endif + + void multiDraw(); + void multiDrawIndexed(); + #ifndef MAGNUM_TARGET_GLES + void multiDrawBaseVertex(); + #endif }; MeshGLTest::MeshGLTest() { @@ -207,7 +213,13 @@ MeshGLTest::MeshGLTest() { &MeshGLTest::addVertexBufferInstancedInteger, #endif #ifndef MAGNUM_TARGET_GLES - &MeshGLTest::addVertexBufferInstancedDouble + &MeshGLTest::addVertexBufferInstancedDouble, + #endif + + &MeshGLTest::multiDraw, + &MeshGLTest::multiDrawIndexed, + #ifndef MAGNUM_TARGET_GLES + &MeshGLTest::multiDrawBaseVertex #endif }); } @@ -1662,6 +1674,131 @@ void MeshGLTest::addVertexBufferInstancedDouble() { } #endif +namespace { + struct MultiChecker { + MultiChecker(AbstractShaderProgram&& shader, Mesh& mesh); + + template T get(ColorFormat format, ColorType type); + + Renderbuffer renderbuffer; + Framebuffer framebuffer; + }; +} + +#ifndef DOXYGEN_GENERATING_OUTPUT +MultiChecker::MultiChecker(AbstractShaderProgram&& shader, Mesh& mesh): framebuffer({{}, Vector2i(1)}) { + renderbuffer.setStorage( + #ifndef MAGNUM_TARGET_GLES2 + RenderbufferFormat::RGBA8, + #else + RenderbufferFormat::RGBA4, + #endif + Vector2i(1)); + framebuffer.attachRenderbuffer(Framebuffer::ColorAttachment(0), renderbuffer); + + framebuffer.bind(FramebufferTarget::ReadDraw); + mesh.setPrimitive(MeshPrimitive::Points) + .setCount(2); + + /* Skip first vertex so we test also offsets */ + MeshView a(mesh); + a.setCount(1) + .setBaseVertex(mesh.baseVertex()); + + MeshView b(mesh); + b.setCount(1); + if(mesh.isIndexed()) { + b.setBaseVertex(mesh.baseVertex()) + .setIndexRange(1); + } else b.setBaseVertex(1); + + MeshView::draw(shader, {a, b}); +} + +template T MultiChecker::get(ColorFormat format, ColorType type) { + Image2D image(format, type); + framebuffer.read({}, Vector2i(1), image); + return image.data()[0]; +} +#endif + +void MeshGLTest::multiDraw() { + #ifdef MAGNUM_TARGET_GLES + if(!Context::current()->isExtensionSupported()) + Debug() << Extensions::GL::EXT::multi_draw_arrays::string() << "not supported, using fallback implementation"; + #endif + + typedef AbstractShaderProgram::Attribute<0, Float> Attribute; + + const Float data[] = { 0.0f, -0.7f, Math::normalize(96) }; + Buffer buffer; + buffer.setData(data, BufferUsage::StaticDraw); + + Mesh mesh; + mesh.addVertexBuffer(buffer, 4, Attribute()); + + MAGNUM_VERIFY_NO_ERROR(); + + const auto value = MultiChecker(FloatShader("float", "vec4(valueInterpolated, 0.0, 0.0, 0.0)"), + mesh).get(ColorFormat::RGBA, ColorType::UnsignedByte); + + MAGNUM_VERIFY_NO_ERROR(); + CORRADE_COMPARE(value, 96); +} + +void MeshGLTest::multiDrawIndexed() { + #ifdef MAGNUM_TARGET_GLES + if(!Context::current()->isExtensionSupported()) + Debug() << Extensions::GL::EXT::multi_draw_arrays::string() << "not supported, using fallback implementation"; + #endif + + Buffer vertices; + vertices.setData(indexedVertexData, BufferUsage::StaticDraw); + + constexpr UnsignedShort indexData[] = { 2, 1, 0 }; + Buffer indices(Buffer::Target::ElementArray); + indices.setData(indexData, BufferUsage::StaticDraw); + + Mesh mesh; + mesh.addVertexBuffer(vertices, 1*4, MultipleShader::Position(), + MultipleShader::Normal(), MultipleShader::TextureCoordinates()) + .setIndexBuffer(indices, 2, Mesh::IndexType::UnsignedShort); + + MAGNUM_VERIFY_NO_ERROR(); + + const auto value = MultiChecker(MultipleShader{}, mesh).get(ColorFormat::RGBA, ColorType::UnsignedByte); + + MAGNUM_VERIFY_NO_ERROR(); + CORRADE_COMPARE(value, indexedResult); +} + +#ifndef MAGNUM_TARGET_GLES +void MeshGLTest::multiDrawBaseVertex() { + if(!Context::current()->isExtensionSupported()) + CORRADE_SKIP(Extensions::GL::ARB::draw_elements_base_vertex::string() + std::string(" is not available.")); + + Buffer vertices; + vertices.setData(indexedVertexDataBaseVertex, BufferUsage::StaticDraw); + + constexpr UnsignedShort indexData[] = { 2, 1, 0 }; + Buffer indices(Buffer::Target::ElementArray); + indices.setData(indexData, BufferUsage::StaticDraw); + + Mesh mesh; + mesh.setBaseVertex(2) + .addVertexBuffer(vertices, 2*4, MultipleShader::Position(), + MultipleShader::Normal(), MultipleShader::TextureCoordinates()) + .setIndexBuffer(indices, 2, Mesh::IndexType::UnsignedShort); + + MAGNUM_VERIFY_NO_ERROR(); + + const auto value = MultiChecker(MultipleShader{}, mesh).get(ColorFormat::RGBA, ColorType::UnsignedByte); + + MAGNUM_VERIFY_NO_ERROR(); + CORRADE_COMPARE(value, indexedResult); +} +#endif + }} CORRADE_TEST_MAIN(Magnum::Test::MeshGLTest)