diff --git a/src/Buffer.cpp b/src/Buffer.cpp index 777d0b06b..a812b6eb7 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -29,6 +29,10 @@ Buffer::CopyImplementation Buffer::copyImplementation = &Buffer::copyImplementat #endif Buffer::SetDataImplementation Buffer::setDataImplementation = &Buffer::setDataImplementationDefault; Buffer::SetSubDataImplementation Buffer::setSubDataImplementation = &Buffer::setSubDataImplementationDefault; +Buffer::MapImplementation Buffer::mapImplementation = &Buffer::mapImplementationDefault; +Buffer::MapRangeImplementation Buffer::mapRangeImplementation = &Buffer::mapRangeImplementationDefault; +Buffer::FlushMappedRangeImplementation Buffer::flushMappedRangeImplementation = &Buffer::flushMappedRangeImplementationDefault; +Buffer::UnmapImplementation Buffer::unmapImplementation = &Buffer::unmapImplementationDefault; void Buffer::initializeContextBasedFunctionality(Context* context) { #ifndef MAGNUM_TARGET_GLES @@ -38,6 +42,10 @@ void Buffer::initializeContextBasedFunctionality(Context* context) { copyImplementation = &Buffer::copyImplementationDSA; setDataImplementation = &Buffer::setDataImplementationDSA; setSubDataImplementation = &Buffer::setSubDataImplementationDSA; + mapImplementation = &Buffer::mapImplementationDSA; + mapRangeImplementation = &Buffer::mapRangeImplementationDSA; + flushMappedRangeImplementation = &Buffer::flushMappedRangeImplementationDSA; + unmapImplementation = &Buffer::unmapImplementationDSA; } #else static_cast(context); @@ -114,4 +122,44 @@ void Buffer::setSubDataImplementationDSA(GLintptr offset, GLsizeiptr size, const } #endif +void* Buffer::mapImplementationDefault(MapAccess access) { + return glMapBuffer(static_cast(bindInternal(_targetHint)), GLenum(access)); +} + +#ifndef MAGNUM_TARGET_GLES +void* Buffer::mapImplementationDSA(MapAccess access) { + return glMapNamedBufferEXT(_id, GLenum(access)); +} +#endif + +void* Buffer::mapRangeImplementationDefault(GLintptr offset, GLsizeiptr length, MapFlags access) { + return glMapBufferRange(static_cast(bindInternal(_targetHint)), offset, length, GLenum(access)); +} + +#ifndef MAGNUM_TARGET_GLES +void* Buffer::mapRangeImplementationDSA(GLintptr offset, GLsizeiptr length, MapFlags access) { + return glMapNamedBufferRangeEXT(_id, offset, length, GLenum(access)); +} +#endif + +void Buffer::flushMappedRangeImplementationDefault(GLintptr offset, GLsizeiptr length) { + glFlushMappedBufferRange(static_cast(bindInternal(_targetHint)), offset, length); +} + +#ifndef MAGNUM_TARGET_GLES +void Buffer::flushMappedRangeImplementationDSA(GLintptr offset, GLsizeiptr length) { + glFlushMappedNamedBufferRangeEXT(_id, offset, length); +} +#endif + +bool Buffer::unmapImplementationDefault() { + return glUnmapBuffer(static_cast(bindInternal(_targetHint))); +} + +#ifndef MAGNUM_TARGET_GLES +bool Buffer::unmapImplementationDSA() { + return glUnmapNamedBufferEXT(_id); +} +#endif + } diff --git a/src/Buffer.h b/src/Buffer.h index 235833fef..5f7bff8fe 100644 --- a/src/Buffer.h +++ b/src/Buffer.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "Magnum.h" @@ -60,19 +61,46 @@ std::vector data; buffer.setData(data, Buffer::Usage::StaticDraw); @endcode +@subsection Buffer-data-mapping Memory mapping + +%Buffer data can be also updated asynchronously. First you need to allocate +the buffer to desired size by passing `nullptr` to setData(), e.g.: +@code +buffer.setData(200*sizeof(Vector3)), nullptr, Buffer::Usage::StaticDraw); +@endcode +Then you can map the buffer to client memory and operate with the memory +directly. After you are done with the operation, call unmap() to unmap the +buffer again. +@code +Vector3* data = static_cast(buffer.map(0, 200*sizeof(Vector3), Buffer::MapFlag::Write|Buffer::MapFlag::InvalidateBuffer)); +for(std::size_t i = 0; i != 200; ++i) + data[i] = ...; +CORRADE_INTERNAL_ASSERT(buffer.unmap()); +@endcode +If you are updating only a few discrete portions of the buffer, you can use +@ref MapFlag "MapFlag::FlushExplicit" and flushMappedRange() to reduce number +of memory operations performed by OpenGL on unmapping. Example: +@code +Vector3* data = static_cast(buffer.map(0, 200*sizeof(Vector3), Buffer::MapFlag::Write|Buffer::MapFlag::FlushExplicit)); +for(std::size_t i: {7, 27, 56, 128}) { + data[i] = ...; + buffer.flushMappedRange(i*sizeof(Vector3), sizeof(Vector3)); +} +CORRADE_INTERNAL_ASSERT(buffer.unmap()); +@endcode + @section Buffer-performance-optimization Performance optimizations The engine tracks currently bound buffers to avoid unnecessary calls to -@fn_gl{BindBuffer}. If the buffer is already bound to some target, -functions copy(), setData() and setSubData() use that target in -@fn_gl{CopyBufferSubData}, @fn_gl{BufferData} and @fn_gl{BufferSubData} -functions instead of binding the buffer to some specific target. You can also -use setTargetHint() to possibly reduce unnecessary rebinding. +@fn_gl{BindBuffer}. If the buffer is already bound to some target, functions +copy(), setData(), setSubData(), map(), flushMappedRange() and unmap() use +that target instead of binding the buffer to some specific target. You can +also use setTargetHint() to possibly reduce unnecessary rebinding. If extension @extension{EXT,direct_state_access} is available, functions -copy(), setData() and setSubData() use DSA functions to avoid unnecessary -calls to @fn_gl{BindBuffer}. See their respective documentation for more -information. +copy(), setData(), setSubData(), map(), flushMappedRange() and unmap() use DSA +functions to avoid unnecessary calls to @fn_gl{BindBuffer}. See their +respective documentation for more information. @todo Support for AMD's query buffer (@extension{AMD,query_buffer_object}) @todo BindBufferRange/BindBufferOffset/BindBufferBase for transform feedback (3.0, @extension{EXT,transform_feedback}) @@ -273,6 +301,90 @@ class MAGNUM_EXPORT Buffer { #endif }; + /** + * @brief Memory mapping access + * + * @deprecated Prefer to use map(GLintptr, GLsizeiptr, MapFlags) + * instead, as it has more complete set of features. + * @see map(MapAccess) + * @requires_es_extension %Extension @es_extension{OES,mapbuffer} in + * OpenGL ES 2.0, use @ref Magnum::Buffer::map(GLintptr, GLsizeiptr, MapFlags) "map(GLintptr, GLsizeiptr, MapFlags)" + * in OpenGL ES 3.0 instead. + */ + enum class MapAccess: GLenum { + #ifndef MAGNUM_TARGET_GLES + /** + * Map buffer for reading only. + * @requires_gl Only @ref Magnum::Buffer::MapAccess "MapAccess::WriteOnly" + * is available in OpenGL ES 2.0. + */ + ReadOnly = GL_READ_ONLY, + #endif + + /** + * Map buffer for writing only. + */ + WriteOnly = GL_WRITE_ONLY + + #ifndef MAGNUM_TARGET_GLES + , + + /** + * Map buffer for both reading and writing. + * @requires_gl Only @ref Magnum::Buffer::MapAccess "MapAccess::WriteOnly" + * is available in OpenGL ES 2.0. + */ + ReadWrite = GL_READ_WRITE + #endif + }; + + /** + * @brief Memory mapping flag + * + * @see MapFlags, map(GLintptr, GLsizeiptr, MapFlags) + * @requires_gl30 %Extension @extension{ARB,map_buffer_range} + * @requires_gles30 %Extension @es_extension{EXT,map_buffer_range} + */ + enum class MapFlag: GLbitfield { + /** Map buffer for reading. */ + Read = GL_MAP_READ_BIT, + + /** Map buffer for writing. */ + Write = GL_MAP_WRITE_BIT, + + /** + * Previous contents of the entire buffer may be discarded. May + * not be used in combination with @ref MapFlag "MapFlag::Read". + */ + InvalidateBuffer = GL_MAP_INVALIDATE_BUFFER_BIT, + + /** + * Previous contents of mapped range may be discarded. May not + * be used in combination with @ref MapFlag "MapFlag::Read". + */ + InvalidateRange = GL_MAP_INVALIDATE_RANGE_BIT, + + /** + * Only one or more discrete subranges of the mapping will be + * modified. See flushMappedRange() for more information. May only + * be used in conjuction with @ref MapFlag "MapFlag::Write". + */ + FlushExplicit = GL_MAP_FLUSH_EXPLICIT_BIT, + + /** + * No pending operations on the buffer should be synchronized + * before mapping. + */ + Unsynchronized = GL_MAP_UNSYNCHRONIZED_BIT + }; + + /** + * @brief Memory mapping flags + * + * @see map(GLintptr, GLsizeiptr, MapFlags) + */ + typedef Corrade::Containers::EnumSet MapFlags; + /** * @brief Unbind any buffer from given target * @param target %Target @@ -444,6 +556,85 @@ class MAGNUM_EXPORT Buffer { setSubData(offset, data.size()*sizeof(T), data.data()); } + /** + * @brief Map buffer to client memory + * @param access Access + * @return Pointer to buffer data + * + * If @extension{EXT,direct_state_access} is not available and the + * buffer is not already bound somewhere, it is bound to hinted target + * before the operation. + * @deprecated Prefer to use map(GLintptr, GLsizeiptr, MapFlags) + * instead, as it has more complete set of features. + * @see unmap(), setTargetHint(), @fn_gl{BindBuffer} and @fn_gl{MapBuffer} + * or @fn_gl_extension{MapNamedBuffer,EXT,direct_state_access} + * @requires_es_extension %Extension @es_extension{OES,mapbuffer} in + * OpenGL ES 2.0, use @ref Magnum::Buffer::map(GLintptr, GLsizeiptr, MapFlags) "map(GLintptr, GLsizeiptr, MapFlags)" + * in OpenGL ES 3.0 instead. + */ + inline void* map(MapAccess access) { + return (this->*mapImplementation)(access); + } + + /** + * @brief Map buffer to client memory + * @param offset Offset into the buffer + * @param length Length of the mapped memory + * @param flags Flags. At least @ref MapFlag "MapFlag::Read" or + * @ref MapFlag "MapFlag::Write" must be specified. + * @return Pointer to buffer data + * + * If @extension{EXT,direct_state_access} is not available and the + * buffer is not already bound somewhere, it is bound to hinted target + * before the operation. + * @see flushMappedRange(), unmap(), map(MapAccess), setTargetHint(), @fn_gl{BindBuffer} + * and @fn_gl{MapBufferRange} or @fn_gl_extension{MapNamedBufferRange,EXT,direct_state_access} + * @requires_gl30 %Extension @extension{ARB,map_buffer_range} + * @requires_gles30 %Extension @es_extension{EXT,map_buffer_range} + */ + inline void* map(GLintptr offset, GLsizeiptr length, MapFlags flags) { + return (this->*mapRangeImplementation)(offset, length, flags); + } + + /** + * @brief Flush mapped range + * @param offset Offset relative to start of mapped range + * @param length Length of the flushed memory + * + * Flushes specified subsection of mapped range. Use only if you called + * map() with @ref MapFlag "MapFlag::FlushExplicit" flag. See + * @ref Buffer-data-mapping "class documentation" for usage example. + * + * If @extension{EXT,direct_state_access} is not available and the + * buffer is not already bound somewhere, it is bound to hinted target + * before the operation. + * @see setTargetHint(), @fn_gl{BindBuffer} and @fn_gl{FlushMappedBufferRange} + * or @fn_gl_extension{FlushMappedNamedBufferRange,EXT,direct_state_access} + * @requires_gl30 %Extension @extension{ARB,map_buffer_range} + * @requires_gles30 %Extension @es_extension{EXT,map_buffer_range} + */ + inline void flushMappedRange(GLintptr offset, GLsizeiptr length) { + (this->*flushMappedRangeImplementation)(offset, length); + } + + /** + * @brief Unmap buffer + * @return `False` if the data have become corrupt during the time + * the buffer was mapped (e.g. after screen was resized), `true` + * otherwise. + * + * Unmaps buffer previously mapped with map(), invalidating the + * pointer returned by these functions. If @extension{EXT,direct_state_access} + * is not available and the buffer is not already bound somewhere, it + * is bound to hinted target before the operation. + * @see setTargetHint(), @fn_gl{BindBuffer} and @fn_gl{UnmapBuffer} or + * @fn_gl_extension{UnmapNamedBuffer,EXT,direct_state_access} + * @requires_gles30 %Extension @es_extension{OES,mapbuffer} + */ + inline bool unmap() { + return (this->*unmapImplementation)(); + } + private: static void MAGNUM_LOCAL initializeContextBasedFunctionality(Context* context); @@ -473,10 +664,40 @@ class MAGNUM_EXPORT Buffer { #endif static SetSubDataImplementation setSubDataImplementation; + typedef void*(Buffer::*MapImplementation)(MapAccess); + void MAGNUM_LOCAL * mapImplementationDefault(MapAccess access); + #ifndef MAGNUM_TARGET_GLES + void MAGNUM_LOCAL * mapImplementationDSA(MapAccess access); + #endif + static MapImplementation mapImplementation; + + typedef void*(Buffer::*MapRangeImplementation)(GLintptr, GLsizeiptr, MapFlags); + void MAGNUM_LOCAL * mapRangeImplementationDefault(GLintptr offset, GLsizeiptr length, MapFlags access); + #ifndef MAGNUM_TARGET_GLES + void MAGNUM_LOCAL * mapRangeImplementationDSA(GLintptr offset, GLsizeiptr length, MapFlags access); + #endif + static MapRangeImplementation mapRangeImplementation; + + typedef void(Buffer::*FlushMappedRangeImplementation)(GLintptr, GLsizeiptr); + void MAGNUM_LOCAL flushMappedRangeImplementationDefault(GLintptr offset, GLsizeiptr length); + #ifndef MAGNUM_TARGET_GLES + void MAGNUM_LOCAL flushMappedRangeImplementationDSA(GLintptr offset, GLsizeiptr length); + #endif + static FlushMappedRangeImplementation flushMappedRangeImplementation; + + typedef bool(Buffer::*UnmapImplementation)(); + bool MAGNUM_LOCAL unmapImplementationDefault(); + #ifndef MAGNUM_TARGET_GLES + bool MAGNUM_LOCAL unmapImplementationDSA(); + #endif + static UnmapImplementation unmapImplementation; + GLuint _id; Target _targetHint; }; +CORRADE_ENUMSET_OPERATORS(Buffer::MapFlags) + } #endif