From 6f355c564d20f245dbdbf61f0478857690097316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 26 Oct 2014 18:59:25 +0100 Subject: [PATCH] Initial transform feedback implementation. Full support for EXT_transform_feedback, transform feedback objects from ARB_transform_feedback2 and equivalent OpenGL ES 3.0 functionality. Example usage is in src/Magnum/Test/TransformFeedbackGLTest.cpp, I'll add some example later. --- doc/opengl-mapping.dox | 20 +- doc/opengl-support.dox | 6 +- src/Magnum/AbstractShaderProgram.cpp | 11 + src/Magnum/AbstractShaderProgram.h | 114 +++++ src/Magnum/Buffer.cpp | 8 +- src/Magnum/Buffer.h | 13 +- src/Magnum/CMakeLists.txt | 12 +- src/Magnum/Context.cpp | 5 + src/Magnum/Context.h | 7 +- src/Magnum/Implementation/State.cpp | 6 + src/Magnum/Implementation/State.h | 6 + .../Implementation/TransformFeedbackState.cpp | 45 ++ .../Implementation/TransformFeedbackState.h | 55 +++ src/Magnum/Magnum.h | 1 + src/Magnum/PrimitiveQuery.h | 3 +- src/Magnum/Test/CMakeLists.txt | 1 + src/Magnum/Test/TransformFeedbackGLTest.cpp | 406 ++++++++++++++++++ src/Magnum/TransformFeedback.cpp | 258 +++++++++++ src/Magnum/TransformFeedback.h | 375 ++++++++++++++++ 19 files changed, 1328 insertions(+), 24 deletions(-) create mode 100644 src/Magnum/Implementation/TransformFeedbackState.cpp create mode 100644 src/Magnum/Implementation/TransformFeedbackState.h create mode 100644 src/Magnum/Test/TransformFeedbackGLTest.cpp create mode 100644 src/Magnum/TransformFeedback.cpp create mode 100644 src/Magnum/TransformFeedback.h diff --git a/doc/opengl-mapping.dox b/doc/opengl-mapping.dox index 7d7c55992..b54e93c45 100644 --- a/doc/opengl-mapping.dox +++ b/doc/opengl-mapping.dox @@ -53,10 +53,10 @@ OpenGL function | Matching API @fn_gl{BeginConditionalRender}, `glEndConditionalRender()` | @ref SampleQuery::beginConditionalRender(), \n @ref SampleQuery::endConditionalRender() @fn_gl{BeginQuery}, `glEndQuery()` | @ref PrimitiveQuery::begin(), \n @ref SampleQuery::begin(), \n @ref TimeQuery::begin(), \n @ref AbstractQuery::end() @fn_gl{BeginQueryIndexed}, `glEndQueryIndexed()` | | -@fn_gl{BeginTransformFeedback}, `glEndTransformFeedback()` | | +@fn_gl{BeginTransformFeedback}, `glEndTransformFeedback()` | @ref TransformFeedback::begin(), @ref TransformFeedback::end() @fn_gl{BindAttribLocation} | @ref AbstractShaderProgram::bindAttributeLocation() @fn_gl{BindBuffer} | not needed, handled internally in @ref Buffer and elsewhere -@fn_gl{BindBufferBase}, \n @fn_gl{BindBuffersBase}, \n @fn_gl{BindBufferRange}, \n @fn_gl{BindBuffersRange} | @ref Buffer::bind(), \n @ref Buffer::unbind() +@fn_gl{BindBufferBase}, \n @fn_gl{BindBuffersBase}, \n @fn_gl{BindBufferRange}, \n @fn_gl{BindBuffersRange} | @ref Buffer::bind(), \n @ref Buffer::unbind(), \n @ref TransformFeedback::attachBuffer(), \n @ref TransformFeedback::attachBuffers() @fn_gl{BindFragDataLocation} | @ref AbstractShaderProgram::bindFragmentDataLocation() @fn_gl{BindFragDataLocationIndexed} | @ref AbstractShaderProgram::bindFragmentDataLocationIndexed() @fn_gl{BindFramebuffer} | @ref Framebuffer::bind() @@ -65,7 +65,7 @@ OpenGL function | Matching API @fn_gl{BindRenderbuffer} | not needed, handled internally in @ref Renderbuffer @fn_gl{BindSampler}, \n @fn_gl{BindSamplers} | | @fn_gl{BindTexture}, \n @fn_gl{BindTextureUnit}, \n @fn_gl{BindTextures}, \n @fn_gl_extension{BindMultiTexture,EXT,direct_state_access} | @ref AbstractTexture::bind() -@fn_gl{BindTransformFeedback} | | +@fn_gl{BindTransformFeedback} | not needed, handled internally in @ref TransformFeedback @fn_gl{BindVertexArray} | not needed, handled internally in @ref Mesh @fn_gl{BindVertexBuffer}, \n `glVertexArrayVertexBuffer()`, \n @fn_gl_extension{VertexArrayBindVertexBuffer,EXT,direct_state_access} \n @fn_gl{BindVertexBuffers}, \n `glVertexArrayVertexBuffers()` | | @fn_gl{BlendColor} | @ref Renderer::setBlendColor() @@ -210,8 +210,8 @@ OpenGL function | Matching API @fn_gl{GetTexLevelParameter}, \n `glGetTextureLevelParameter()`, \n @fn_gl_extension{GetTextureLevelParameter,EXT,direct_state_access} | @ref Texture::imageSize(), \n @ref TextureArray::imageSize(), \n @ref CubeMapTexture::imageSize(), \n @ref CubeMapTextureArray::imageSize(), \n @ref RectangleTexture::imageSize() @fn_gl{GetTexParameter}, \n `glGetTextureParameter()`, \n @fn_gl_extension{GetTextureParameter,EXT,direct_state_access} | | @fn_gl{GetTextureSubImage} | | -@fn_gl{GetTransformFeedback} | | -@fn_gl{GetTransformFeedbackVarying} | | +@fn_gl{GetTransformFeedback} | not queryable, @ref TransformFeedback::attachBuffer() and @ref TransformFeedback::attachBuffers() setters only +@fn_gl{GetTransformFeedbackVarying} | not queryable, @ref AbstractShaderProgram::setTransformFeedbackOutputs() setter only @fn_gl{GetUniform}, \n `glGetnUniform()`, \n @fn_gl_extension{GetnUniform,ARB,robustness} | not queryable, @ref AbstractShaderProgram::setUniform() setter only @fn_gl{GetUniformBlockIndex} | | @fn_gl{GetUniformIndices} | | @@ -267,7 +267,7 @@ OpenGL function | Matching API OpenGL function | Matching API --------------------------------------- | ------------ @fn_gl{PatchParameter} | | -@fn_gl{PauseTransformFeedback}, @fn_gl{ResumeTransformFeedback} | | +@fn_gl{PauseTransformFeedback}, @fn_gl{ResumeTransformFeedback} | @ref TransformFeedback::pause(), @ref TransformFeedback::resume() @fn_gl{PixelStore} | | @fn_gl{PointParameter} | | @fn_gl{PointSize} | @ref Renderer::setPointSize() @@ -325,8 +325,8 @@ OpenGL function | Matching API @fn_gl{TexSubImage1D}, \n `glTextureSubImage1D()`, \n @fn_gl_extension{TextureSubImage1D,EXT,direct_state_access}, \n @fn_gl{TexSubImage2D}, \n `glTextureSubImage2D()`, \n @fn_gl_extension{TextureSubImage2D,EXT,direct_state_access}, \n @fn_gl{TexSubImage3D}, \n `glTextureSubImage3D()`, \n @fn_gl_extension{TextureSubImage3D,EXT,direct_state_access} | @ref Texture::setSubImage(), \n @ref TextureArray::setSubImage(), \n @ref CubeMapTexture::setSubImage(), \n @ref CubeMapTextureArray::setSubImage(), \n @ref RectangleTexture::setSubImage() @fn_gl{TextureBarrier} | | @fn_gl{TextureView} | | -@fn_gl{TransformFeedbackBufferBase}, \n @fn_gl{TransformFeedbackBufferRange} | | -@fn_gl{TransformFeedbackVaryings} | | +@fn_gl{TransformFeedbackBufferBase}, \n @fn_gl{TransformFeedbackBufferRange} | @ref TransformFeedback::attachBuffer(), \n @ref TransformFeedback::attachBuffers() +@fn_gl{TransformFeedbackVaryings} | @ref AbstractShaderProgram::setTransformFeedbackOutputs() @subsection opengl-mapping-functions-u U @@ -438,6 +438,10 @@ OpenGL function | Matching API @def_gl{MAX_TEXTURE_BUFFER_SIZE} | @ref BufferTexture::maxSize() @def_gl_extension{MAX_TEXTURE_MAX_ANISOTROPY,EXT,texture_filter_anisotropic} | @ref Sampler::maxMaxAnisotropy() @def_gl{MAX_TEXTURE_LOD_BIAS} | @ref AbstractTexture::maxLodBias() +@def_gl{MAX_TRANSFORM_FEEDBACK_BUFFERS} | @ref TransformFeedback::maxBuffers() +@def_gl{MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS} | @ref TransformFeedback::maxInterleavedComponents() +@def_gl{MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS} | @ref TransformFeedback::maxSeparateAttributes() +@def_gl{MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS} | @ref TransformFeedback::maxSeparateComponents() @def_gl{MAX_UNIFORM_BLOCK_SIZE} | @ref AbstractShaderProgram::maxUniformBlockSize() @def_gl{MAX_UNIFORM_BUFFER_BINDINGS} | @ref Buffer::maxUniformBindings() @def_gl{MAX_UNIFORM_LOCATIONS} | @ref AbstractShaderProgram::maxUniformLocations() diff --git a/doc/opengl-support.dox b/doc/opengl-support.dox index bcd54b836..cc51f839b 100644 --- a/doc/opengl-support.dox +++ b/doc/opengl-support.dox @@ -70,7 +70,7 @@ GLSL 1.30 | done @extension{EXT,texture_shared_exponent} | done @extension{EXT,draw_buffers2} | | @extension{EXT,texture_integer} | done (GL 3.0 subset) -@extension{EXT,transform_feedback} | | +@extension{EXT,transform_feedback} | done @extension{NV,depth_buffer_float} | | @extension{NV,conditional_render} | done @@ -134,8 +134,8 @@ GLSL 4.00 | done @extension{ARB,shader_subroutine} | | @extension{ARB,tessellation_shader} | missing some limit queries and patch parameter specification function @extension{ARB,texture_buffer_object_rgb32} | done -@extension{ARB,transform_feedback2} | | -@extension{ARB,transform_feedback3} | | +@extension{ARB,transform_feedback2} | missing transform feedback draw +@extension{ARB,transform_feedback3} | only advanced interleaving @subsection opengl-support-41 OpenGL 4.1 diff --git a/src/Magnum/AbstractShaderProgram.cpp b/src/Magnum/AbstractShaderProgram.cpp index 9dbdd586b..20446c068 100644 --- a/src/Magnum/AbstractShaderProgram.cpp +++ b/src/Magnum/AbstractShaderProgram.cpp @@ -311,6 +311,17 @@ void AbstractShaderProgram::bindFragmentDataLocationIndexedInternal(const Unsign } #endif +#ifndef MAGNUM_TARGET_GLES2 +void AbstractShaderProgram::setTransformFeedbackOutputs(const std::initializer_list outputs, const TransformFeedbackBufferMode bufferMode) { + Containers::Array names{outputs.size()}; + + Int i = 0; + for(const std::string& output: outputs) names[i++] = output.data(); + + glTransformFeedbackVaryings(_id, outputs.size(), names, GLenum(bufferMode)); +} +#endif + bool AbstractShaderProgram::link(std::initializer_list> shaders) { bool allSuccess = true; diff --git a/src/Magnum/AbstractShaderProgram.h b/src/Magnum/AbstractShaderProgram.h index 134addeb4..a7f28a611 100644 --- a/src/Magnum/AbstractShaderProgram.h +++ b/src/Magnum/AbstractShaderProgram.h @@ -112,6 +112,22 @@ MyShader& setSpecularTexture(Texture2D& texture) { return *this; } @endcode +- **Transform feedback setup function**, if needed, in which you bind buffers + to particular indices using @ref TransformFeedback::attachBuffer() and + similar, possibly with overloads based on desired use cases, e.g.: +@code +MyShader& setTransformFeedback(TransformFeedback& feedback, Buffer& positions, Buffer& data) { + feedback.attachBuffers(0, {positions, data}); + return *this; +} +MyShader& setTransformFeedback(TransformFeedback& feedback, Int totalCount, Buffer& positions, GLintptr positionsOffset, Buffer& data, GLintptr dataOffset) { + feedback.attachBuffers(0, { + std::make_tuple(positions, positionsOffset, totalCount*sizeof(Vector3)), + std::make_tuple(data, dataOffset, totalCount*sizeof(Vector2ui)) + }); + return *this; +} +@endcode @anchor AbstractShaderProgram-attribute-location ### Binding attribute location @@ -251,6 +267,53 @@ setUniform(uniformLocation("specularTexture"), 1); 3.0 and older. Use @ref Magnum::AbstractShaderProgram::setUniform(Int, const T&) "setUniform(Int, Int)" instead. +@anchor AbstractShaderProgram-transform-feedback +### Specifying transform feedback binding points + +The preferred workflow is to specify output binding points directly in the +shader code, e.g.: +@code +// GLSL 4.40, or +#extension GL_ARB_enhanced_layouts: enable +layout(xfb_buffer = 0, xfb_stride = 32) out block { + layout(xfb_offset = 0) vec3 position; + layout(xfb_offset = 16) vec3 normal; +}; +layout(xfb_buffer = 1) out vec3 velocity; +@endcode + +If you don't have the required extension, declare the uniforms without the +`xfb_*` qualifier and set the binding points using @ref setTransformFeedbackOutputs(). +Equivalent setup for the previous code would be the following: +@code +out block { + vec3 position; + vec3 normal; +}; +out vec3 velocity; +@endcode +@code +setTransformFeedbackOutputs({ + // Buffer 0 + "position", "gl_SkipComponents1", "normal", "gl_SkipComponents1", + // Buffer 1 + "gl_NextBuffer", "velocity" + }, TransformFeedbackBufferMode::InterleavedAttributes); +@endcode + +@see @ref TransformFeedback::maxInterleavedComponents(), + @ref TransformFeedback::maxSeparateAttributes(), + @ref TransformFeedback::maxSeparateComponents() +@requires_gl40 %Extension @extension{ARB,transform_feedback3} for using + `gl_NextBuffer` or `gl_SkipComponents#` names in + @ref Magnum::AbstractShaderProgram::setTransformFeedbackOutputs() "setTransformFeedbackOutputs()" + function +@requires_gl44 %Extension @extension{ARB,enhanced_layouts} for explicit + transform feedback output specification instead of using + @ref Magnum::AbstractShaderProgram::setTransformFeedbackOutputs() "setTransformFeedbackOutputs()" +@requires_gl Explicit transform feedback output specification is not available + in OpenGL ES. + @anchor AbstractShaderProgram-rendering-workflow ## Rendering workflow @@ -334,6 +397,24 @@ class MAGNUM_EXPORT AbstractShaderProgram: public AbstractObject { friend struct Implementation::ShaderProgramState; public: + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief %Buffer mode for transform feedback + * + * @see @ref setTransformFeedbackOutputs() + * @requires_gl30 %Extension @extension{EXT,transform_feedback} + * @requires_gles30 Transform feedback is not available in OpenGL ES + * 2.0 + */ + enum class TransformFeedbackBufferMode: GLenum { + /** Attributes will be interleaved at one buffer binding point */ + InterleavedAttributes = GL_INTERLEAVED_ATTRIBS, + + /** Each attribute will be put into separate buffer binding point */ + SeparateAttributes = GL_SEPARATE_ATTRIBS + }; + #endif + /** * @brief Max supported vertex attribute count * @@ -700,6 +781,39 @@ class MAGNUM_EXPORT AbstractShaderProgram: public AbstractObject { } #endif + #ifndef MAGNUM_TARGET_GLES2 + /** + * @brief Specify shader outputs to be recorded in transform feedback + * @param outputs Names of output variables + * @param bufferMode %Buffer mode + * + * Binds given output variables from vertex, geometry or tessellation + * shader to transform feedback buffer binding points. If + * @ref TransformFeedbackBufferMode::SeparateAttributes is used, each + * output is bound to separate binding point. If + * @ref TransformFeedbackBufferMode::InterleavedAttributes is used, the + * outputs are interleaved into single buffer binding point. In this + * case, special output name `gl_NextBuffer` causes the following + * output to be recorded into next buffer binding point and + * `gl_SkipComponents#` causes the transform feedback to offset the + * following output variable by `#` components. + * @see @fn_gl{TransformFeedbackVaryings} + * @deprecated_gl Preferred usage is to specify transform feedback + * outputs explicitly in the shader instead of using this + * function. See @ref AbstractShaderProgram-transform-feedback "class documentation" + * for more information. + * @requires_gl30 %Extension @extension{EXT,transform_feedback} + * @requires_gl40 %Extension @extension{ARB,transform_feedback3} for + * using `gl_NextBuffer` or `gl_SkipComponents#` names in + * @p outputs array + * @requires_gles30 Transform feedback is not available in OpenGL ES + * 2.0 + * @requires_gl Special output names `gl_NextBuffer` and + * `gl_SkipComponents#` are not available in OpenGL ES + */ + void setTransformFeedbackOutputs(std::initializer_list outputs, TransformFeedbackBufferMode bufferMode); + #endif + /** * @brief Link the shader * diff --git a/src/Magnum/Buffer.cpp b/src/Magnum/Buffer.cpp index 7656613ff..4847540f2 100644 --- a/src/Magnum/Buffer.cpp +++ b/src/Magnum/Buffer.cpp @@ -144,7 +144,7 @@ void Buffer::unbind(const Target target, const UnsignedInt firstIndex, const std /** @todoc const std::initializer_list makes Doxygen grumpy */ void Buffer::bind(const Target target, const UnsignedInt firstIndex, std::initializer_list> buffers) { #ifdef MAGNUM_BUILD_DEPRECATED - CORRADE_INTERNAL_ASSERT(target == Target::AtomicCounter || target == Target::ShaderStorage || target == Target::Uniform); + CORRADE_INTERNAL_ASSERT(target == Target::AtomicCounter || target == Target::ShaderStorage || target == Target::Uniform || GLenum(target) == GL_TRANSFORM_FEEDBACK_BUFFER); #endif Context::current()->state().buffer->bindRangesImplementation(target, firstIndex, {buffers.begin(), buffers.size()}); } @@ -152,7 +152,7 @@ void Buffer::bind(const Target target, const UnsignedInt firstIndex, std::initia /** @todoc const std::initializer_list makes Doxygen grumpy */ void Buffer::bind(const Target target, const UnsignedInt firstIndex, std::initializer_list buffers) { #ifdef MAGNUM_BUILD_DEPRECATED - CORRADE_INTERNAL_ASSERT(target == Target::AtomicCounter || target == Target::ShaderStorage || target == Target::Uniform); + CORRADE_INTERNAL_ASSERT(target == Target::AtomicCounter || target == Target::ShaderStorage || target == Target::Uniform || GLenum(target) == GL_TRANSFORM_FEEDBACK_BUFFER); #endif Context::current()->state().buffer->bindBasesImplementation(target, firstIndex, {buffers.begin(), buffers.size()}); } @@ -262,7 +262,7 @@ auto Buffer::bindSomewhereInternal(const TargetHint hint) -> TargetHint { #ifndef MAGNUM_TARGET_GLES2 Buffer& Buffer::bind(const Target target, const UnsignedInt index, const GLintptr offset, const GLsizeiptr size) { #ifdef MAGNUM_BUILD_DEPRECATED - CORRADE_INTERNAL_ASSERT(target == Target::AtomicCounter || target == Target::ShaderStorage || target == Target::Uniform); + CORRADE_INTERNAL_ASSERT(target == Target::AtomicCounter || target == Target::ShaderStorage || target == Target::Uniform || GLenum(target) == GL_TRANSFORM_FEEDBACK_BUFFER); #endif glBindBufferRange(GLenum(target), index, _id, offset, size); return *this; @@ -270,7 +270,7 @@ Buffer& Buffer::bind(const Target target, const UnsignedInt index, const GLintpt Buffer& Buffer::bind(const Target target, const UnsignedInt index) { #ifdef MAGNUM_BUILD_DEPRECATED - CORRADE_INTERNAL_ASSERT(target == Target::AtomicCounter || target == Target::ShaderStorage || target == Target::Uniform); + CORRADE_INTERNAL_ASSERT(target == Target::AtomicCounter || target == Target::ShaderStorage || target == Target::Uniform || GLenum(target) == GL_TRANSFORM_FEEDBACK_BUFFER); #endif glBindBufferBase(GLenum(target), index, _id); return *this; diff --git a/src/Magnum/Buffer.h b/src/Magnum/Buffer.h index d30d991a3..2a857e7f1 100644 --- a/src/Magnum/Buffer.h +++ b/src/Magnum/Buffer.h @@ -701,8 +701,8 @@ class MAGNUM_EXPORT Buffer: public AbstractObject { * @see @ref bind(Target, UnsignedInt, GLintptr, GLsizeiptr), * @ref maxAtomicCounterBindings(), @ref maxShaderStorageBindings(), * @ref maxUniformBindings(), @ref shaderStorageOffsetAlignment(), - * @ref uniformOffsetAlignment(), @fn_gl{BindBuffersRange} or - * @fn_gl{BindBufferRange} + * @ref uniformOffsetAlignment(), @ref TransformFeedback::attachBuffers(), + * @fn_gl{BindBuffersRange} or @fn_gl{BindBufferRange} * @requires_gl30 No form of indexed buffer binding is available in * OpenGL 2.1, see particular @ref Magnum::Buffer::Target "Target" * values for version requirements. @@ -727,7 +727,8 @@ class MAGNUM_EXPORT Buffer: public AbstractObject { * for more information. * @see @ref bind(Target, UnsignedInt), @ref maxAtomicCounterBindings(), * @ref maxShaderStorageBindings(), @ref maxUniformBindings(), - * @fn_gl{BindBuffersBase} or @fn_gl{BindBufferBase} + * @ref TransformFeedback::attachBuffers(), @fn_gl{BindBuffersBase} + * or @fn_gl{BindBufferBase} * @requires_gl30 No form of indexed buffer binding is available in * OpenGL 2.1, see particular @ref Magnum::Buffer::Target "Target" * values for version requirements. @@ -882,7 +883,8 @@ class MAGNUM_EXPORT Buffer: public AbstractObject { * @see @ref bind(Target, UnsignedInt, std::initializer_list>), * @ref maxAtomicCounterBindings(), @ref maxShaderStorageBindings(), * @ref maxUniformBindings(), @ref shaderStorageOffsetAlignment(), - * @ref uniformOffsetAlignment(), @fn_gl{BindBufferRange} + * @ref uniformOffsetAlignment(), @ref TransformFeedback::attachBuffer(), + * @fn_gl{BindBufferRange} * @requires_gl30 No form of indexed buffer binding is available in * OpenGL 2.1, see particular @ref Magnum::Buffer::Target "Target" * values for version requirements. @@ -903,7 +905,8 @@ class MAGNUM_EXPORT Buffer: public AbstractObject { * for more information. * @see @ref bind(Target, UnsignedInt, std::initializer_list), * @ref maxAtomicCounterBindings(), @ref maxShaderStorageBindings(), - * @ref maxUniformBindings(), @fn_gl{BindBufferBase} + * @ref maxUniformBindings(), @ref TransformFeedback::attachBuffer(), + * @fn_gl{BindBufferBase} * @requires_gl30 No form of indexed buffer binding is available in * OpenGL 2.1, see particular @ref Magnum::Buffer::Target "Target" * values for version requirements. diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index bee8b9878..4c8bb6455 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -163,11 +163,19 @@ if(NOT TARGET_GLES2) BufferImage.h MultisampleTexture.h PrimitiveQuery.h - TextureArray.h) + TextureArray.h + TransformFeedback.h) + + list(APPEND Magnum_PRIVATE_HEADES + Implementation/TransformFeedbackState.h) + set(Magnum_SRCS ${Magnum_SRCS} BufferImage.cpp MultisampleTexture.cpp - TextureArray.cpp) + TextureArray.cpp + TransformFeedback.cpp + + Implementation/TransformFeedbackState.cpp) endif() # Link in GL function pointer variables on platforms that support it diff --git a/src/Magnum/Context.cpp b/src/Magnum/Context.cpp index 3828d07ad..1f95bd782 100644 --- a/src/Magnum/Context.cpp +++ b/src/Magnum/Context.cpp @@ -50,6 +50,7 @@ #include "Implementation/MeshState.h" #include "Implementation/ShaderProgramState.h" #include "Implementation/TextureState.h" +#include "Implementation/TransformFeedbackState.h" namespace Magnum { @@ -578,6 +579,10 @@ void Context::resetState(const States states) { if(states & State::Textures) _state->texture->reset(); + #ifndef MAGNUM_TARGET_GLES2 + if(states & State::TransformFeedback) + _state->transformFeedback->reset(); + #endif } #ifndef DOXYGEN_GENERATING_OUTPUT diff --git a/src/Magnum/Context.h b/src/Magnum/Context.h index fdb13deb8..ffdd4aa53 100644 --- a/src/Magnum/Context.h +++ b/src/Magnum/Context.h @@ -163,7 +163,12 @@ class MAGNUM_EXPORT Context { Shaders = 1 << 4, /** Reset tracked texture-related bindings and state */ - Textures = 1 << 5 + Textures = 1 << 5, + + #ifndef MAGNUM_TARGET_GLES2 + /** Reset tracked transform feedback-related bindings */ + TransformFeedback = 1 << 6 + #endif }; /** diff --git a/src/Magnum/Implementation/State.cpp b/src/Magnum/Implementation/State.cpp index 490989a1a..935f5cd22 100644 --- a/src/Magnum/Implementation/State.cpp +++ b/src/Magnum/Implementation/State.cpp @@ -39,6 +39,9 @@ #include "ShaderState.h" #include "ShaderProgramState.h" #include "TextureState.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "TransformFeedbackState.h" +#endif namespace Magnum { namespace Implementation { @@ -61,6 +64,9 @@ State::State(Context& context) { shader = new ShaderState; shaderProgram = new ShaderProgramState(context, extensions); texture = new TextureState(context, extensions); + #ifndef MAGNUM_TARGET_GLES2 + transformFeedback = new TransformFeedbackState(context, extensions); + #endif /* Sort the features and remove duplicates */ std::sort(extensions.begin(), extensions.end()); diff --git a/src/Magnum/Implementation/State.h b/src/Magnum/Implementation/State.h index 481e6ad73..d1e7e1dcf 100644 --- a/src/Magnum/Implementation/State.h +++ b/src/Magnum/Implementation/State.h @@ -39,6 +39,9 @@ struct RendererState; struct ShaderState; struct ShaderProgramState; struct TextureState; +#ifndef MAGNUM_TARGET_GLES2 +struct TransformFeedbackState; +#endif struct State { /* Initializes context-based functionality */ @@ -57,6 +60,9 @@ struct State { ShaderState* shader; ShaderProgramState* shaderProgram; TextureState* texture; + #ifndef MAGNUM_TARGET_GLES2 + TransformFeedbackState* transformFeedback; + #endif }; }} diff --git a/src/Magnum/Implementation/TransformFeedbackState.cpp b/src/Magnum/Implementation/TransformFeedbackState.cpp new file mode 100644 index 000000000..dd90c63f4 --- /dev/null +++ b/src/Magnum/Implementation/TransformFeedbackState.cpp @@ -0,0 +1,45 @@ +#include "TransformFeedbackState.h" + +#include "Magnum/Extensions.h" +#include "Magnum/TransformFeedback.h" + +#include "State.h" + +namespace Magnum { namespace Implementation { + +TransformFeedbackState::TransformFeedbackState(Context& context, std::vector& extensions): maxInterleavedComponents{0}, maxSeparateAttributes{0}, maxSeparateComponents{0} + #ifndef MAGNUM_TARGET_GLES + , maxBuffers{0} + #endif +{ + #ifndef MAGNUM_TARGET_GLES + if(context.isExtensionSupported()) { + extensions.push_back(Extensions::GL::ARB::direct_state_access::string()); + + createImplementation = &TransformFeedback::createImplementationDSA; + attachRangeImplementation = &TransformFeedback::attachImplementationDSA; + attachBaseImplementation = &TransformFeedback::attachImplementationDSA; + attachRangesImplementation = &TransformFeedback::attachImplementationDSA; + attachBasesImplementation = &TransformFeedback::attachImplementationDSA; + + } else + #endif + { + createImplementation = &TransformFeedback::createImplementationDefault; + attachRangeImplementation = &TransformFeedback::attachImplementationFallback; + attachBaseImplementation = &TransformFeedback::attachImplementationFallback; + attachRangesImplementation = &TransformFeedback::attachImplementationFallback; + attachBasesImplementation = &TransformFeedback::attachImplementationFallback; + } + + #ifdef MAGNUM_TARGET_GLES + static_cast(context); + static_cast(extensions); + #endif +} + +void TransformFeedbackState::reset() { + binding = State::DisengagedBinding; +} + +}} diff --git a/src/Magnum/Implementation/TransformFeedbackState.h b/src/Magnum/Implementation/TransformFeedbackState.h new file mode 100644 index 000000000..24bbd134f --- /dev/null +++ b/src/Magnum/Implementation/TransformFeedbackState.h @@ -0,0 +1,55 @@ +#ifndef Magnum_Implementation_TransformFeedbackState_h +#define Magnum_Implementation_TransformFeedbackState_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Magnum/Context.h" + +namespace Magnum { namespace Implementation { + +struct TransformFeedbackState { + explicit TransformFeedbackState(Context& context, std::vector& extensions); + + void reset(); + + GLint maxInterleavedComponents, + maxSeparateAttributes, + maxSeparateComponents; + #ifndef MAGNUM_TARGET_GLES + GLint maxBuffers; + #endif + + GLuint binding; + + void(TransformFeedback::*createImplementation)(); + void(TransformFeedback::*attachRangeImplementation)(GLuint, Buffer&, GLintptr, GLsizeiptr); + void(TransformFeedback::*attachBaseImplementation)(GLuint, Buffer&); + void(TransformFeedback::*attachRangesImplementation)(GLuint, std::initializer_list>); + void(TransformFeedback::*attachBasesImplementation)(GLuint, std::initializer_list); +}; + +}} + +#endif diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index bd7cbf2bb..e231452fc 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -556,6 +556,7 @@ typedef TextureArray<2> Texture2DArray; enum class TextureFormat: GLenum; +class TransformFeedback; class Timeline; enum class Version: Int; diff --git a/src/Magnum/PrimitiveQuery.h b/src/Magnum/PrimitiveQuery.h index ad7649f19..b86fc632c 100644 --- a/src/Magnum/PrimitiveQuery.h +++ b/src/Magnum/PrimitiveQuery.h @@ -58,8 +58,9 @@ UnsignedInt primitiveCount = q.result(); @requires_gl30 %Extension @extension{EXT,transform_feedback} @requires_gles30 Only sample queries are available on OpenGL ES 2.0. -@see @ref SampleQuery, @ref TimeQuery +@see @ref SampleQuery, @ref TimeQuery, @ref TransformFeedback @todo glBeginQueryIndexed +@todo @extension{ARB,transform_feedback_overflow_query} */ class PrimitiveQuery: public AbstractQuery { public: diff --git a/src/Magnum/Test/CMakeLists.txt b/src/Magnum/Test/CMakeLists.txt index bdc108a93..4de89e8b6 100644 --- a/src/Magnum/Test/CMakeLists.txt +++ b/src/Magnum/Test/CMakeLists.txt @@ -71,6 +71,7 @@ if(BUILD_GL_TESTS) corrade_add_test(MultisampleTextureGLTest MultisampleTextureGLTest.cpp LIBRARIES ${GL_TEST_LIBRARIES}) corrade_add_test(PrimitiveQueryGLTest PrimitiveQueryGLTest.cpp LIBRARIES ${GL_TEST_LIBRARIES}) corrade_add_test(TextureArrayGLTest TextureArrayGLTest.cpp LIBRARIES ${GL_TEST_LIBRARIES}) + corrade_add_test(TransformFeedbackGLTest TransformFeedbackGLTest.cpp LIBRARIES ${GL_TEST_LIBRARIES}) endif() if(NOT MAGNUM_TARGET_GLES) diff --git a/src/Magnum/Test/TransformFeedbackGLTest.cpp b/src/Magnum/Test/TransformFeedbackGLTest.cpp new file mode 100644 index 000000000..c2a8eb0cf --- /dev/null +++ b/src/Magnum/Test/TransformFeedbackGLTest.cpp @@ -0,0 +1,406 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Magnum/AbstractShaderProgram.h" +#include "Magnum/Buffer.h" +#include "Magnum/Mesh.h" +#include "Magnum/Shader.h" +#include "Magnum/TransformFeedback.h" +#include "Magnum/Math/Vector2.h" +#include "Magnum/Test/AbstractOpenGLTester.h" + +namespace Magnum { namespace Test { + +class TransformFeedbackGLTest: public AbstractOpenGLTester { + public: + explicit TransformFeedbackGLTest(); + + void construct(); + void constructCopy(); + void constructMove(); + + void label(); + + void attachBase(); + void attachRange(); + void attachBases(); + void attachRanges(); + + void interleaved(); +}; + +TransformFeedbackGLTest::TransformFeedbackGLTest() { + addTests({&TransformFeedbackGLTest::construct, + &TransformFeedbackGLTest::constructCopy, + &TransformFeedbackGLTest::constructMove, + + &TransformFeedbackGLTest::label, + + &TransformFeedbackGLTest::attachBase, + &TransformFeedbackGLTest::attachRange, + &TransformFeedbackGLTest::attachBases, + &TransformFeedbackGLTest::attachRanges, + + &TransformFeedbackGLTest::interleaved}); +} + +void TransformFeedbackGLTest::construct() { + { + TransformFeedback feedback; + + MAGNUM_VERIFY_NO_ERROR(); + CORRADE_VERIFY(feedback.id() > 0); + } + + MAGNUM_VERIFY_NO_ERROR(); +} + +void TransformFeedbackGLTest::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +void TransformFeedbackGLTest::constructMove() { + TransformFeedback a; + const Int id = a.id(); + + MAGNUM_VERIFY_NO_ERROR(); + CORRADE_VERIFY(id > 0); + + TransformFeedback b{std::move(a)}; + + CORRADE_COMPARE(a.id(), 0); + CORRADE_COMPARE(b.id(), id); + + TransformFeedback c; + const Int cId = c.id(); + c = std::move(b); + + MAGNUM_VERIFY_NO_ERROR(); + CORRADE_VERIFY(cId > 0); + CORRADE_COMPARE(b.id(), cId); + CORRADE_COMPARE(c.id(), id); +} + +void TransformFeedbackGLTest::label() { + /* No-Op version is tested in AbstractObjectGLTest */ + if(!Context::current()->isExtensionSupported() && + !Context::current()->isExtensionSupported()) + CORRADE_SKIP("Required extension is not available"); + + TransformFeedback feedback; + + CORRADE_COMPARE(feedback.label(), ""); + MAGNUM_VERIFY_NO_ERROR(); + + feedback.setLabel("MyXfb"); + MAGNUM_VERIFY_NO_ERROR(); + + CORRADE_COMPARE(feedback.label(), "MyXfb"); +} + +namespace { + +constexpr const Vector2 inputData[] = { + {0.0f, 0.0f}, + {-1.0f, 1.0f} +}; + +struct XfbShader: AbstractShaderProgram { + typedef Attribute<0, Vector2> Input; + + explicit XfbShader(); +}; + +XfbShader::XfbShader() { + #ifndef MAGNUM_TARGET_GLES + Shader vert(Version::GL300, Shader::Type::Vertex); + #else + Shader vert(Version::GLES300, Shader::Type::Vertex); + Shader frag(Version::GLES300, Shader::Type::Fragment); + #endif + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.addSource( + "in mediump vec2 inputData;\n" + "out mediump vec2 outputData;\n" + "void main() {\n" + " outputData = inputData + vec2(1.0, -1.0);\n" + "}\n").compile()); + #ifndef MAGNUM_TARGET_GLES + attachShader(vert); + #else + /* ES for some reason needs both vertex and fragment shader */ + CORRADE_INTERNAL_ASSERT_OUTPUT(frag.addSource("void main() {}\n").compile()); + attachShaders({vert, frag}); + #endif + bindAttributeLocation(Input::Location, "inputData"); + setTransformFeedbackOutputs({"outputData"}, TransformFeedbackBufferMode::SeparateAttributes); + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); +} + +} + +void TransformFeedbackGLTest::attachBase() { + XfbShader shader; + + Buffer input; + input.setData(inputData, BufferUsage::StaticDraw); + Buffer output; + output.setData({nullptr, 2*sizeof(Vector2)}, BufferUsage::StaticRead); + + Mesh mesh; + mesh.setPrimitive(MeshPrimitive::Points) + .addVertexBuffer(input, 0, XfbShader::Input{}) + .setCount(2); + + TransformFeedback feedback; + feedback.attachBuffer(0, output); + + MAGNUM_VERIFY_NO_ERROR(); + + Renderer::enable(Renderer::Feature::RasterizerDiscard); + feedback.begin(shader, TransformFeedback::PrimitiveMode::Points); + mesh.draw(shader); + feedback.end(); + + MAGNUM_VERIFY_NO_ERROR(); + + Vector2* data = reinterpret_cast(output.map(0, 2*sizeof(Vector2), Buffer::MapFlag::Read)); + CORRADE_COMPARE(data[0], Vector2(1.0f, -1.0f)); + CORRADE_COMPARE(data[1], Vector2(0.0f, 0.0f)); + output.unmap(); +} + +void TransformFeedbackGLTest::attachRange() { + XfbShader shader; + + Buffer input; + input.setData(inputData, BufferUsage::StaticDraw); + Buffer output; + output.setData({nullptr, 512 + 2*sizeof(Vector2)}, BufferUsage::StaticRead); + + Mesh mesh; + mesh.setPrimitive(MeshPrimitive::Points) + .addVertexBuffer(input, 0, XfbShader::Input{}) + .setCount(2); + + TransformFeedback feedback; + feedback.attachBuffer(0, output, 256, 2*sizeof(Vector2)); + + MAGNUM_VERIFY_NO_ERROR(); + + shader.use(); + + Renderer::enable(Renderer::Feature::RasterizerDiscard); + feedback.begin(shader, TransformFeedback::PrimitiveMode::Points); + mesh.draw(shader); + feedback.end(); + + MAGNUM_VERIFY_NO_ERROR(); + + Vector2* data = reinterpret_cast(output.map(256, 2*sizeof(Vector2), Buffer::MapFlag::Read)); + CORRADE_COMPARE(data[0], Vector2(1.0f, -1.0f)); + CORRADE_COMPARE(data[1], Vector2(0.0f, 0.0f)); + output.unmap(); +} + +namespace { + +struct XfbMultiShader: AbstractShaderProgram { + typedef Attribute<0, Vector2> Input; + + explicit XfbMultiShader(); +}; + +XfbMultiShader::XfbMultiShader() { + #ifndef MAGNUM_TARGET_GLES + Shader vert(Version::GL300, Shader::Type::Vertex); + #else + Shader vert(Version::GLES300, Shader::Type::Vertex); + Shader frag(Version::GLES300, Shader::Type::Fragment); + #endif + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.addSource( + "in mediump vec2 inputData;\n" + "out mediump vec2 output1;\n" + "out mediump float output2;\n" + "void main() {\n" + " output1 = inputData + vec2(1.0, -1.0);\n" + " output2 = inputData.x - inputData.y;\n" + "}\n").compile()); + #ifndef MAGNUM_TARGET_GLES + attachShader(vert); + #else + /* ES for some reason needs both vertex and fragment shader */ + CORRADE_INTERNAL_ASSERT_OUTPUT(frag.addSource("void main() {}\n").compile()); + attachShaders({vert, frag}); + #endif + bindAttributeLocation(Input::Location, "inputData"); + setTransformFeedbackOutputs({"output1", "output2"}, TransformFeedbackBufferMode::SeparateAttributes); + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); +} + +} + +void TransformFeedbackGLTest::attachBases() { + XfbMultiShader shader; + + Buffer input; + input.setData(inputData, BufferUsage::StaticDraw); + Buffer output1, output2; + output1.setData({nullptr, 2*sizeof(Vector2)}, BufferUsage::StaticRead); + output2.setData({nullptr, 2*sizeof(Float)}, BufferUsage::StaticRead); + + Mesh mesh; + mesh.setPrimitive(MeshPrimitive::Points) + .addVertexBuffer(input, 0, XfbMultiShader::Input{}) + .setCount(2); + + TransformFeedback feedback; + feedback.attachBuffers(0, {&output1, &output2}); + + MAGNUM_VERIFY_NO_ERROR(); + + Renderer::enable(Renderer::Feature::RasterizerDiscard); + feedback.begin(shader, TransformFeedback::PrimitiveMode::Points); + mesh.draw(shader); + feedback.end(); + + MAGNUM_VERIFY_NO_ERROR(); + + Vector2* data1 = reinterpret_cast(output1.map(0, 2*sizeof(Vector2), Buffer::MapFlag::Read)); + CORRADE_COMPARE(data1[0], Vector2(1.0f, -1.0f)); + CORRADE_COMPARE(data1[1], Vector2(0.0f, 0.0f)); + output1.unmap(); + + Float* data2 = reinterpret_cast(output2.map(0, 2*sizeof(Float), Buffer::MapFlag::Read)); + CORRADE_COMPARE(data2[0], 0.0f); + CORRADE_COMPARE(data2[1], -2.0f); + output2.unmap(); +} + +void TransformFeedbackGLTest::attachRanges() { + Buffer input; + input.setData(inputData, BufferUsage::StaticDraw); + Buffer output1, output2; + output1.setData({nullptr, 512 + 2*sizeof(Vector2)}, BufferUsage::StaticRead); + output2.setData({nullptr, 768 + 2*sizeof(Float)}, BufferUsage::StaticRead); + + XfbMultiShader shader; + + Mesh mesh; + mesh.setPrimitive(MeshPrimitive::Points) + .addVertexBuffer(input, 0, XfbMultiShader::Input{}) + .setCount(2); + + TransformFeedback feedback; + feedback.attachBuffers(0, { + std::make_tuple(&output1, 256, 2*sizeof(Vector2)), + std::make_tuple(&output2, 512, 2*sizeof(Float)) + }); + + MAGNUM_VERIFY_NO_ERROR(); + + Renderer::enable(Renderer::Feature::RasterizerDiscard); + feedback.begin(shader, TransformFeedback::PrimitiveMode::Points); + mesh.draw(shader); + feedback.end(); + + MAGNUM_VERIFY_NO_ERROR(); + + Vector2* data1 = reinterpret_cast(output1.map(256, 2*sizeof(Vector2), Buffer::MapFlag::Read)); + CORRADE_COMPARE(data1[0], Vector2(1.0f, -1.0f)); + CORRADE_COMPARE(data1[1], Vector2(0.0f, 0.0f)); + output1.unmap(); + + Float* data2 = reinterpret_cast(output2.map(512, 2*sizeof(Float), Buffer::MapFlag::Read)); + CORRADE_COMPARE(data2[0], 0.0f); + CORRADE_COMPARE(data2[1], -2.0f); + output2.unmap(); +} + +void TransformFeedbackGLTest::interleaved() { + struct XfbInterleavedShader: AbstractShaderProgram { + typedef Attribute<0, Vector2> Input; + + explicit XfbInterleavedShader() { + #ifndef MAGNUM_TARGET_GLES + Shader vert(Version::GL300, Shader::Type::Vertex); + #else + Shader vert(Version::GLES300, Shader::Type::Vertex); + Shader frag(Version::GLES300, Shader::Type::Fragment); + #endif + CORRADE_INTERNAL_ASSERT_OUTPUT(vert.addSource( + "in mediump vec2 inputData;\n" + "out mediump vec2 output1;\n" + "out mediump float output2;\n" + "void main() {\n" + " output1 = inputData + vec2(1.0, -1.0);\n" + " output2 = inputData.x - inputData.y + 5.0;\n" + "}\n").compile()); + #ifndef MAGNUM_TARGET_GLES + attachShader(vert); + #else + /* ES for some reason needs both vertex and fragment shader */ + CORRADE_INTERNAL_ASSERT_OUTPUT(frag.addSource("void main() {}\n").compile()); + attachShaders({vert, frag}); + #endif + bindAttributeLocation(Input::Location, "inputData"); + setTransformFeedbackOutputs({"output1", "gl_SkipComponents1", "output2"}, TransformFeedbackBufferMode::InterleavedAttributes); + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); + } + } shader; + + Buffer input; + input.setData(inputData, BufferUsage::StaticDraw); + Buffer output; + output.setData({nullptr, 4*sizeof(Vector2)}, BufferUsage::StaticRead); + + Mesh mesh; + mesh.setPrimitive(MeshPrimitive::Points) + .addVertexBuffer(input, 0, XfbInterleavedShader::Input{}) + .setCount(2); + + TransformFeedback feedback; + feedback.attachBuffer(0, output); + + MAGNUM_VERIFY_NO_ERROR(); + + Renderer::enable(Renderer::Feature::RasterizerDiscard); + feedback.begin(shader, TransformFeedback::PrimitiveMode::Points); + mesh.draw(shader); + feedback.end(); + + MAGNUM_VERIFY_NO_ERROR(); + + Vector2* data = reinterpret_cast(output.map(0, 4*sizeof(Vector2), Buffer::MapFlag::Read)); + CORRADE_COMPARE(data[0], Vector2(1.0f, -1.0f)); + CORRADE_COMPARE(data[1].y(), 5.0f); + CORRADE_COMPARE(data[2], Vector2(0.0f, 0.0f)); + CORRADE_COMPARE(data[3].y(), 3.0f); + output.unmap(); +} + +}} + +CORRADE_TEST_MAIN(Magnum::Test::TransformFeedbackGLTest) diff --git a/src/Magnum/TransformFeedback.cpp b/src/Magnum/TransformFeedback.cpp new file mode 100644 index 000000000..27b5a5822 --- /dev/null +++ b/src/Magnum/TransformFeedback.cpp @@ -0,0 +1,258 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "TransformFeedback.h" + +#ifndef MAGNUM_TARGET_GLES2 +#include + +#include "Magnum/AbstractShaderProgram.h" +#include "Magnum/Buffer.h" +#include "Magnum/Context.h" +#include "Magnum/Extensions.h" +#include "Magnum/Implementation/DebugState.h" +#include "Magnum/Implementation/State.h" +#include "Magnum/Implementation/TransformFeedbackState.h" + +namespace Magnum { + +Int TransformFeedback::maxInterleavedComponents() { + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isExtensionSupported()) + return 0; + #endif + + GLint& value = Context::current()->state().transformFeedback->maxInterleavedComponents; + + if(value == 0) + glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, &value); + + return value; +} + +Int TransformFeedback::maxSeparateAttributes() { + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isExtensionSupported()) + return 0; + #endif + + GLint& value = Context::current()->state().transformFeedback->maxSeparateAttributes; + + if(value == 0) + glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &value); + + return value; +} + +Int TransformFeedback::maxSeparateComponents() { + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isExtensionSupported()) + return 0; + #endif + + GLint& value = Context::current()->state().transformFeedback->maxSeparateComponents; + + if(value == 0) + glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS, &value); + + return value; +} + +#ifndef MAGNUM_TARGET_GLES +Int TransformFeedback::maxBuffers() { + if(!Context::current()->isExtensionSupported()) + return maxSeparateAttributes(); + + GLint& value = Context::current()->state().transformFeedback->maxBuffers; + + if(value == 0) + glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_BUFFERS, &value); + + return value; +} +#endif + +TransformFeedback::TransformFeedback() { + (this->*Context::current()->state().transformFeedback->createImplementation)(); + CORRADE_INTERNAL_ASSERT(_id != Implementation::State::DisengagedBinding); +} + +void TransformFeedback::createImplementationDefault() { + glGenTransformFeedbacks(1, &_id); + _created = false; +} + +#ifndef MAGNUM_TARGET_GLES +void TransformFeedback::createImplementationDSA() { + glCreateTransformFeedbacks(1, &_id); + _created = true; +} +#endif + +TransformFeedback::~TransformFeedback() { + if(!_id) return; + + /* If bound, remove itself from state */ + GLuint& binding = Context::current()->state().transformFeedback->binding; + if(binding == _id) binding = 0; + + glDeleteTransformFeedbacks(1, &_id); +} + +void TransformFeedback::bindInternal() { + GLuint& bound = Context::current()->state().transformFeedback->binding; + + /* Already bound, nothing to do */ + if(bound == _id) return; + + /* Bind the transform feedback otherwise, which will also finally create it */ + bound = _id; + _created = true; + glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, _id); +} + +inline void TransformFeedback::createIfNotAlready() { + if(_created) return; + + /* glGen*() does not create the object, just reserves the name. Some + commands (such as glObjectLabel()) operate with IDs directly and they + require the object to be created. Binding the transform feedback finally + creates it. Also all EXT DSA functions implicitly create it. */ + bindInternal(); + CORRADE_INTERNAL_ASSERT(_created); +} + +std::string TransformFeedback::label() { + createIfNotAlready(); + return Context::current()->state().debug->getLabelImplementation(GL_TRANSFORM_FEEDBACK, _id); +} + +TransformFeedback& TransformFeedback::setLabelInternal(const Containers::ArrayReference label) { + createIfNotAlready(); + Context::current()->state().debug->labelImplementation(GL_TRANSFORM_FEEDBACK, _id, label); + return *this; +} + +TransformFeedback& TransformFeedback::attachBuffer(const UnsignedInt index, Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + (this->*Context::current()->state().transformFeedback->attachRangeImplementation)(index, buffer, offset, size); + return *this; +} + +TransformFeedback& TransformFeedback::attachBuffer(const UnsignedInt index, Buffer& buffer) { + (this->*Context::current()->state().transformFeedback->attachBaseImplementation)(index, buffer); + return *this; +} + +void TransformFeedback::attachImplementationFallback(const GLuint index, Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + bindInternal(); + buffer.bind(Buffer::Target(GL_TRANSFORM_FEEDBACK_BUFFER), index, offset, size); +} + +#ifndef MAGNUM_TARGET_GLES +void TransformFeedback::attachImplementationDSA(const GLuint index, Buffer& buffer, const GLintptr offset, const GLsizeiptr size) { + glTransformFeedbackBufferRange(_id, index, buffer.id(), offset, size); +} +#endif + +void TransformFeedback::attachImplementationFallback(const GLuint index, Buffer& buffer) { + bindInternal(); + buffer.bind(Buffer::Target(GL_TRANSFORM_FEEDBACK_BUFFER), index); +} + +#ifndef MAGNUM_TARGET_GLES +void TransformFeedback::attachImplementationDSA(const GLuint index, Buffer& buffer) { + glTransformFeedbackBufferBase(_id, index, buffer.id()); +} +#endif + +/** @todoc const std::initializer_list makes Doxygen grumpy */ +TransformFeedback& TransformFeedback::attachBuffers(const UnsignedInt firstIndex, std::initializer_list> buffers) { + (this->*Context::current()->state().transformFeedback->attachRangesImplementation)(firstIndex, buffers); + return *this; +} + +/** @todoc const std::initializer_list makes Doxygen grumpy */ +TransformFeedback& TransformFeedback::attachBuffers(const UnsignedInt firstIndex, std::initializer_list buffers) { + (this->*Context::current()->state().transformFeedback->attachBasesImplementation)(firstIndex, buffers); + return *this; +} + +/** @todoc const std::initializer_list makes Doxygen grumpy */ +void TransformFeedback::attachImplementationFallback(const GLuint firstIndex, std::initializer_list> buffers) { + bindInternal(); + Buffer::bind(Buffer::Target(GL_TRANSFORM_FEEDBACK_BUFFER), firstIndex, buffers); +} + +#ifndef MAGNUM_TARGET_GLES +/** @todoc const Containers::ArrayReference makes Doxygen grumpy */ +void TransformFeedback::attachImplementationDSA(const GLuint firstIndex, std::initializer_list> buffers) { + for(std::size_t i = 0; i != buffers.size(); ++i) { + Buffer* buffer; + GLintptr offset; + GLsizeiptr size; + std::tie(buffer, offset, size) = *(buffers.begin() + i); + + glTransformFeedbackBufferRange(_id, firstIndex + i, buffer ? buffer->id() : 0, offset, size); + } +} +#endif + +/** @todoc const Containers::ArrayReference makes Doxygen grumpy */ +void TransformFeedback::attachImplementationFallback(const GLuint firstIndex, std::initializer_list buffers) { + bindInternal(); + Buffer::bind(Buffer::Target(GL_TRANSFORM_FEEDBACK_BUFFER), firstIndex, buffers); +} + +#ifndef MAGNUM_TARGET_GLES +/** @todoc const Containers::ArrayReference makes Doxygen grumpy */ +void TransformFeedback::attachImplementationDSA(const GLuint firstIndex, std::initializer_list buffers) { + for(std::size_t i = 0; i != buffers.size(); ++i) + glTransformFeedbackBufferBase(_id, firstIndex + i, *(buffers.begin() + i) ? (*(buffers.begin() + i))->id() : 0); +} +#endif + +void TransformFeedback::begin(AbstractShaderProgram& shader, const PrimitiveMode mode) { + shader.use(); + bindInternal(); + glBeginTransformFeedback(GLenum(mode)); +} + +void TransformFeedback::pause() { + bindInternal(); + glPauseTransformFeedback(); +} + +void TransformFeedback::resume() { + bindInternal(); + glResumeTransformFeedback(); +} + +void TransformFeedback::end() { + bindInternal(); + glEndTransformFeedback(); +} + +} +#endif diff --git a/src/Magnum/TransformFeedback.h b/src/Magnum/TransformFeedback.h new file mode 100644 index 000000000..429f84b27 --- /dev/null +++ b/src/Magnum/TransformFeedback.h @@ -0,0 +1,375 @@ +#ifndef Magnum_TransformFeedback_h +#define Magnum_TransformFeedback_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include "Magnum/AbstractObject.h" + +#ifndef MAGNUM_TARGET_GLES2 +/** @file + * @brief Class @ref Magnum::TransformFeedback + */ +#endif + +#ifndef MAGNUM_TARGET_GLES2 +namespace Magnum { + +namespace Implementation { struct TransformFeedbackState; } + +/** +@brief Transform feedback + +@see @ref PrimitiveQuery +@requires_gl40 %Extension @extension{ARB,transform_feedback2} +@requires_gles30 Transform feedback is not available in OpenGL ES 2.0 +@todo @extension{AMD,transform_feedback3_lines_triangles}? +*/ +class MAGNUM_EXPORT TransformFeedback: public AbstractObject { + friend struct Implementation::TransformFeedbackState; + + public: + /** + * @brief Transform feedback primitive mode + * + * @see @ref begin() + */ + enum class PrimitiveMode: GLenum { + /** + * Points. If no geometry shader is present, allowed only in + * combination with @ref MeshPrimitive::Points mesh primitive type. + * If geometry shader is present, allowed only in combination with + * `points` output primitive type. + */ + Points = GL_POINTS, + + /** + * Lines. If no geometry shader is present, allowed only in + * combination with @ref MeshPrimitive::LineStrip, + * @ref MeshPrimitive::LineLoop, @ref MeshPrimitive::Lines, + * @ref MeshPrimitive::LineStripAdjacency and + * @ref MeshPrimitive::LinesAdjacency mesh primitive type. If + * geometry shader is present, allowed only in combination with + * `line_strip` output primitive type. + */ + Lines = GL_LINES, + + /** + * Triangles. If no geometry shader is present, allowed only in + * combination with @ref MeshPrimitive::TriangleStrip, + * @ref MeshPrimitive::TriangleFan, @ref MeshPrimitive::Triangles, + * @ref MeshPrimitive::TriangleStripAdjacency and + * @ref MeshPrimitive::TrianglesAdjacency mesh primitive type. If + * geometry shader is present, allowed only in commbination with + * `triangle_strip` output primitive type. + */ + Triangles = GL_TRIANGLES + }; + + /** + * @brief Max supported interleaved component count + * + * The result is cached, repeated queries don't result in repeated + * OpenGL calls. If extension @extension{EXT,transform_feedback} + * (part of OpenGL 3.0) is not available, returns `0`. + * @see @fn_gl{Get} with @def_gl{MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS} + */ + static Int maxInterleavedComponents(); + + /** + * @brief Max supported separate attribute count + * + * The result is cached, repeated queries don't result in repeated + * OpenGL calls. If extension @extension{EXT,transform_feedback} + * (part of OpenGL 3.0) is not available, returns `0`. + * @see @fn_gl{Get} with @def_gl{MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS} + */ + static Int maxSeparateAttributes(); + + /** + * @brief Max supported separate component count + * + * The result is cached, repeated queries don't result in repeated + * OpenGL calls. If extension @extension{EXT,transform_feedback} + * (part of OpenGL 3.0) is not available, returns `0`. + * @see @fn_gl{Get} with @def_gl{MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS} + */ + static Int maxSeparateComponents(); + + #ifndef MAGNUM_TARGET_GLES + /** + * @brief Max supported buffer count + * + * The result is cached, repeated queries don't result in repeated + * OpenGL calls. If extension @extension{ARB,transform_feedback3} + * (part of OpenGL 4.0) is not available, returns the same value as + * @ref maxSeparateAttributes(). + * @see @fn_gl{Get} with @def_gl{MAX_TRANSFORM_FEEDBACK_BUFFERS} + * @requires_gl Use @ref Magnum::TransformFeedback::maxSeparateAttributes() "maxSeparateAttributes()" + * in OpenGL ES. + */ + static Int maxBuffers(); + #endif + + /** + * @brief Constructor + * + * Creates new OpenGL transform feedback object. If + * @extension{ARB,direct_state_access} (part of OpenGL 4.5) is not + * supported, the transform feedback object is created on first use. + * @see @fn_gl{CreateTransformFeedbacks}, eventually + * @fn_gl{GenTransformFeedbacks} + */ + explicit TransformFeedback(); + + /** @brief Copying is not allowed */ + TransformFeedback(const TransformFeedback&) = delete; + + /** @brief Move constructor */ + TransformFeedback(TransformFeedback&& other) noexcept; + + /** + * @brief Destructor + * + * Deletes associated OpenGL transform feedback object. + * @see @fn_gl{DeleteTransformFeedbacks} + */ + ~TransformFeedback(); + + /** @brief Copying is not allowed */ + TransformFeedback& operator=(const TransformFeedback&) = delete; + + /** @brief Move assignment */ + TransformFeedback& operator=(TransformFeedback&& other) noexcept; + + /** @brief OpenGL transform feedback ID */ + GLuint id() const { return _id; } + + /** + * @brief %Buffer label + * + * The result is *not* cached, repeated queries will result in repeated + * OpenGL calls. If OpenGL 4.3 is not supported and neither + * @extension{KHR,debug} nor @extension2{EXT,debug_label} desktop or ES + * extension is available, this function returns empty string. + * @see @fn_gl{GetObjectLabel} or @fn_gl_extension2{GetObjectLabel,EXT,debug_label} + * with @def_gl{BUFFER_OBJECT_EXT} + */ + std::string label(); + + /** + * @brief Set buffer label + * @return Reference to self (for method chaining) + * + * Default is empty string. If OpenGL 4.3 is not supported and neither + * @extension{KHR,debug} nor @extension2{EXT,debug_label} desktop or ES + * extension is available, this function does nothing. + * @see @ref maxLabelLength(), @fn_gl{ObjectLabel} or + * @fn_gl_extension2{LabelObject,EXT,debug_label} with + * @def_gl{TRANSFORM_FEEDBACK} + */ + TransformFeedback& setLabel(const std::string& label) { + return setLabelInternal({label.data(), label.size()}); + } + + /** @overload */ + template TransformFeedback& setLabel(const char(&label)[size]) { + return setLabelInternal(label); + } + + /** + * @brief Attach range of buffer + * @return Reference to self (for method chaining) + * + * The @p offset parameter must be aligned to 4 bytes. If on OpenGL ES + * or @extension{ARB,direct_state_access} (part of OpenGL 4.5) is not + * available, the transform feedback object is bound (if not already) + * and the operation is then done equivalently to + * @ref Buffer::bind(Buffer::Target, UnsignedInt, GLintptr, GLsizeiptr). + * @note This function is meant to be used only internally from + * @ref AbstractShaderProgram subclasses. See its documentation + * for more information. + * @see @ref attachBuffers(), @ref maxBuffers()/@ref maxSeparateAttributes(), + * @fn_gl{TransformFeedbackBufferRange}, eventually + * @fn_gl{BindTransformFeedback} and @fn_gl{BindBuffersRange} or + * @fn_gl{BindBufferRange} + */ + TransformFeedback& attachBuffer(UnsignedInt index, Buffer& buffer, GLintptr offset, GLsizeiptr size); + + /** + * @brief Attach buffer + * @return Reference to self (for method chaining) + * + * If on OpenGL ES or @extension{ARB,direct_state_access} (part of + * OpenGL 4.5) is not available, the transform feedback object is bound + * (if not already) and the operation is then done equivalently to + * @ref Buffer::bind(Buffer::Target, UnsignedInt). + * @note This function is meant to be used only internally from + * @ref AbstractShaderProgram subclasses. See its documentation + * for more information. + * @see @ref attachBuffers(), @ref maxBuffers()/@ref maxSeparateAttributes(), + * @fn_gl{TransformFeedbackBufferRange}, eventually + * @fn_gl{BindTransformFeedback} and @fn_gl{BindBuffersRange} or + * @fn_gl{BindBufferRange} + */ + TransformFeedback& attachBuffer(UnsignedInt index, Buffer& buffer); + + /** + * @brief Attach ranges of buffers + * @return Reference to self (for method chaining) + * + * Attches first buffer in the list to @p firstIndex, second to + * `firstIndex + 1` etc. Second parameter is offset, third is size. If + * any buffer is `nullptr`, given attachment point is detached. The + * range of indices must respect @ref maxBuffers() (@ref maxSeparateComponents() + * in OpenGL ES or if @extension{ARB,transform_feedback3} (part of + * OpenGL 4.0) is not available). The offsets must be aligned to 4 + * bytes. All the buffers must have allocated data store. If on OpenGL + * ES or @extension{ARB,direct_state_access} (part of OpenGL 4.5) is + * not available, the transform feedback object is bound (if not + * already) and the operation is then done equivalently to + * @ref Buffer::bind(Buffer::Target, UnsignedInt, std::initializer_list>). + * @note This function is meant to be used only internally from + * @ref AbstractShaderProgram subclasses. See its documentation + * for more information. + * @see @ref attachBuffer(), @fn_gl{TransformFeedbackBufferRange}, + * eventually @fn_gl{BindTransformFeedback} and + * @fn_gl{BindBuffersRange} or @fn_gl{BindBufferRange} + */ + TransformFeedback& attachBuffers(UnsignedInt firstIndex, std::initializer_list> buffers); + + /** + * @brief Attach buffers + * @return Reference to self (for method chaining) + * + * Attches first buffer in the list to @p firstIndex, second to + * `firstIndex + 1` etc. If any buffer is `nullptr`, given index is + * detached. The range of indices must respect @ref maxBuffers() + * (@ref maxSeparateComponents() in OpenGL ES or if + * @extension{ARB,transform_feedback3} (part of OpenGL 4.0) is not + * available). All the buffers must have allocated data store. If on + * OpenGL ES or @extension{ARB,direct_state_access} (part of OpenGL + * 4.5) is not available, the transform feedback object is bound (if + * not already) and the operation then is done equivalently to + * @ref Buffer::bind(Buffer::Target, UnsignedInt, std::initializer_list). + * @note This function is meant to be used only internally from + * @ref AbstractShaderProgram subclasses. See its documentation + * for more information. + * @see @ref attachBuffer(), @fn_gl{TransformFeedbackBufferBase}, + * eventually @fn_gl{BindTransformFeedback} and + * @fn_gl{BindBuffersBase} or @fn_gl{BindBufferBase} + */ + TransformFeedback& attachBuffers(UnsignedInt firstIndex, std::initializer_list buffers); + + /** + * @brief Begin transform feedback + * @param shader Shader from which to capture data + * @param mode Primitive mode + * + * When transform feedback is active, only shader given in @p shader + * and meshes with primitive type (or geometry shaders with output + * primitive type) compatible with @p mode can be used. Only one + * transform feedback object can be active at a time. + * @see @ref pause(), @ref end(), @fn_gl{BindTransformFeedback} and + * @fn_gl{BeginTransformFeedback} + */ + void begin(AbstractShaderProgram& shader, PrimitiveMode mode); + + /** + * @brief Pause transform feedback + * + * Pausing transform feedback makes it inactive, allowing to use + * different shader, or starting another transform feedback. + * @see @ref resume(), @ref end(), @fn_gl{BindTransformFeedback} and + * @fn_gl{PauseTransformFeedback} + */ + void pause(); + + /** + * @brief Resume transform feedback + * + * Resumes transform feedback so the next captured data are appended to + * already captured ones. The restrictions specified for @ref begin() + * still apply after resuming. Only one transform feedback object can + * be active at a time. + * @see @ref pause(), @ref end(), @fn_gl{BindTransformFeedback} and + * @fn_gl{ResumeTransformFeedback} + */ + void resume(); + + /** + * @brief End transform feedback + * + * Ends transform feedback so the captured data can be used. + * @see @ref begin(), @fn_gl{BindTransformFeedback} and + * @fn_gl{EndTransformFeedback} + */ + void end(); + + private: + void bindInternal(); + + void MAGNUM_LOCAL createIfNotAlready(); + + void MAGNUM_LOCAL createImplementationDefault(); + #ifndef MAGNUM_TARGET_GLES + void MAGNUM_LOCAL createImplementationDSA(); + #endif + + void MAGNUM_LOCAL attachImplementationFallback(GLuint index, Buffer& buffer, GLintptr offset, GLsizeiptr size); + void MAGNUM_LOCAL attachImplementationFallback(GLuint index, Buffer& buffer); + #ifndef MAGNUM_TARGET_GLES + void MAGNUM_LOCAL attachImplementationDSA(GLuint index, Buffer& buffer, GLintptr offset, GLsizeiptr size); + void MAGNUM_LOCAL attachImplementationDSA(GLuint index, Buffer& buffer); + #endif + + void MAGNUM_LOCAL attachImplementationFallback(GLuint firstIndex, std::initializer_list> buffers); + void MAGNUM_LOCAL attachImplementationFallback(GLuint firstIndex, std::initializer_list buffers); + #ifndef MAGNUM_TARGET_GLES + void MAGNUM_LOCAL attachImplementationDSA(GLuint firstIndex, std::initializer_list> buffers); + void MAGNUM_LOCAL attachImplementationDSA(GLuint firstIndex, std::initializer_list buffers); + #endif + + TransformFeedback& setLabelInternal(Containers::ArrayReference label); + + GLuint _id; + bool _created; /* see createIfNotAlready() for details */ +}; + +inline TransformFeedback::TransformFeedback(TransformFeedback&& other) noexcept: _id{other._id}, _created{other._created} { + other._id = 0; +} + +inline TransformFeedback& TransformFeedback::operator=(TransformFeedback&& other) noexcept { + std::swap(_id, other._id); + std::swap(_created, other._created); + return *this; +} + +} +#endif + +#endif