diff --git a/src/modules/graphics/opengl/SpriteBatch.cpp b/src/modules/graphics/opengl/SpriteBatch.cpp index 8b311f514..b59618ee3 100644 --- a/src/modules/graphics/opengl/SpriteBatch.cpp +++ b/src/modules/graphics/opengl/SpriteBatch.cpp @@ -46,8 +46,10 @@ SpriteBatch::SpriteBatch(Texture *texture, int size, int usage) , size(size) , next(0) , color(0) - , array_buf(0) - , element_buf(0) + , array_buf(nullptr) + , element_buf(nullptr) + , buffer_used_offset(0) + , buffer_used_size(0) { if (size <= 0) throw love::Exception("Invalid SpriteBatch size."); @@ -160,18 +162,12 @@ void SpriteBatch::clear() next = 0; } -void *SpriteBatch::lock() +void SpriteBatch::flush() { VertexBuffer::Bind bind(*array_buf); + array_buf->unmap(buffer_used_offset, buffer_used_size); - return array_buf->map(); -} - -void SpriteBatch::unlock() -{ - VertexBuffer::Bind bind(*array_buf); - - array_buf->unmap(); + buffer_used_offset = buffer_used_size = 0; } void SpriteBatch::setTexture(Texture *newtexture) @@ -220,33 +216,31 @@ void SpriteBatch::setBufferSize(int newsize) return; // Map (lock) the old VertexBuffer to get a pointer to its data. - void *old_data = lock(); + void *old_data = nullptr; + { + VertexBuffer::Bind bind(*array_buf); + old_data = array_buf->map(); + } size_t vertex_size = sizeof(Vertex) * 4 * newsize; - VertexBuffer *new_array_buf = 0; - VertexIndex *new_element_buf = 0; - void *new_data = 0; + VertexBuffer *new_array_buf = nullptr; + VertexIndex *new_element_buf = nullptr; try { new_array_buf = VertexBuffer::Create(vertex_size, array_buf->getTarget(), array_buf->getUsage()); new_element_buf = new VertexIndex(newsize); - - // VBO::map can throw an exception. Also we want to scope the bind. - VertexBuffer::Bind bind(*new_array_buf); - new_data = new_array_buf->map(); } catch (love::Exception &) { delete new_array_buf; delete new_element_buf; - unlock(); throw; } // Copy as much of the old data into the new VertexBuffer as can fit. - memcpy(new_data, old_data, sizeof(Vertex) * 4 * std::min(newsize, size)); + new_array_buf->fill(0, sizeof(Vertex) * 4 * std::min(newsize, size), old_data); // We don't need to unmap the old VertexBuffer since we're deleting it. delete array_buf; @@ -258,8 +252,8 @@ void SpriteBatch::setBufferSize(int newsize) next = std::min(next, newsize); - // But we should unmap (unlock) the new one! - unlock(); + // The new VertexBuffer isn't mapped, so we should reset these variables. + buffer_used_offset = buffer_used_size = 0; } int SpriteBatch::getBufferSize() const @@ -269,7 +263,7 @@ int SpriteBatch::getBufferSize() const void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { - const size_t vertex_offset = offsetof(Vertex, x); + const size_t pos_offset = offsetof(Vertex, x); const size_t texel_offset = offsetof(Vertex, s); const size_t color_offset = offsetof(Vertex, r); @@ -288,6 +282,10 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float VertexBuffer::Bind array_bind(*array_buf); VertexBuffer::Bind element_bind(*element_buf->getVertexBuffer()); + // Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.) + array_buf->unmap(buffer_used_offset, buffer_used_size); + buffer_used_offset = buffer_used_size = 0; + Color curcolor = gl.getColor(); // Apply per-sprite color, if a color is set. @@ -298,7 +296,7 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float } glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(2, GL_FLOAT, sizeof(Vertex), array_buf->getPointer(vertex_offset)); + glVertexPointer(2, GL_FLOAT, sizeof(Vertex), array_buf->getPointer(pos_offset)); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), array_buf->getPointer(texel_offset)); @@ -322,9 +320,18 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float void SpriteBatch::addv(const Vertex *v, int index) { - static const int sprite_size = 4 * sizeof(Vertex); // bytecount + static const size_t sprite_size = 4 * sizeof(Vertex); // bytecount + VertexBuffer::Bind bind(*array_buf); + + // Always keep the VBO mapped when adding data for now (it'll be unmapped + // on draw.) + array_buf->map(); + array_buf->fill(index * sprite_size, sprite_size, v); + + buffer_used_offset = std::min(buffer_used_offset, index * sprite_size); + buffer_used_size = std::max(buffer_used_size, (index + 1) * sprite_size - buffer_used_offset); } void SpriteBatch::setColorv(Vertex *v, const Color &color) diff --git a/src/modules/graphics/opengl/SpriteBatch.h b/src/modules/graphics/opengl/SpriteBatch.h index 6f87f8fa5..1f3de1c99 100644 --- a/src/modules/graphics/opengl/SpriteBatch.h +++ b/src/modules/graphics/opengl/SpriteBatch.h @@ -65,8 +65,7 @@ public: int addq(Quad *quad, float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky, int index = -1); void clear(); - void *lock(); - void unlock(); + void flush(); void setTexture(Texture *newtexture); Texture *getTexture(); @@ -142,6 +141,10 @@ private: VertexBuffer *array_buf; VertexIndex *element_buf; + // The portion of the vertex buffer that's been modified while mapped. + size_t buffer_used_offset; + size_t buffer_used_size; + static StringMap::Entry usageHintEntries[]; static StringMap usageHints; diff --git a/src/modules/graphics/opengl/VertexBuffer.cpp b/src/modules/graphics/opengl/VertexBuffer.cpp index d0e8c0f2f..9ec772865 100644 --- a/src/modules/graphics/opengl/VertexBuffer.cpp +++ b/src/modules/graphics/opengl/VertexBuffer.cpp @@ -89,7 +89,7 @@ void *VertexArray::map() return buf; } -void VertexArray::unmap() +void VertexArray::unmap(size_t /*usedOffset*/, size_t /*usedSize*/) { is_mapped = false; } @@ -119,14 +119,14 @@ const void *VertexArray::getPointer(size_t offset) const VBO::VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing) : VertexBuffer(size, target, usage, backing) , vbo(0) - , memory_map(0) + , memory_map(nullptr) , is_dirty(true) { if (!(GLEE_ARB_vertex_buffer_object || GLEE_VERSION_1_5)) throw love::Exception("Not supported"); if (getMemoryBacking() == BACKING_FULL) - memory_map = malloc(getSize()); + memory_map = (char *) malloc(getSize()); bool ok = load(false); @@ -153,7 +153,7 @@ void *VBO::map() if (!memory_map) { - memory_map = malloc(getSize()); + memory_map = (char * ) malloc(getSize()); if (!memory_map) throw love::Exception("Out of memory (oh the humanity!)"); } @@ -169,11 +169,28 @@ void *VBO::map() return memory_map; } -void VBO::unmap() +void VBO::unmapStatic(size_t offset, size_t size) +{ + // Upload the mapped data to the buffer. + glBufferSubDataARB(getTarget(), (GLintptr) offset, (GLsizeiptr) size, memory_map + offset); +} + +void VBO::unmapStream() +{ + // "orphan" current buffer to avoid implicit synchronisation on the GPU: + // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf + glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), nullptr, getUsage()); + glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage()); +} + +void VBO::unmap(size_t usedOffset, size_t usedSize) { if (!is_mapped) return; + usedOffset = std::min(usedOffset, getSize()); + usedSize = std::min(usedSize, getSize() - usedOffset); + // VBO::bind is a no-op when the VBO is mapped, so we have to make sure it's // bound here. if (!is_bound) @@ -182,17 +199,23 @@ void VBO::unmap() is_bound = true; } - if (getUsage() == GL_STATIC_DRAW) + switch (getUsage()) { - // Upload the mapped data to the buffer. - glBufferSubDataARB(getTarget(), 0, (GLsizeiptr) getSize(), memory_map); - } - else - { - // "orphan" current buffer to avoid implicit synchronisation on the GPU: - // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf - glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage()); - glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage()); + case GL_STATIC_DRAW: + unmapStatic(usedOffset, usedSize); + break; + case GL_STREAM_DRAW: + unmapStream(); + break; + case GL_DYNAMIC_DRAW: + default: + // It's probably more efficient to treat it like a streaming buffer if + // more than a third of its contents have been modified during the map(). + if (usedSize >= getSize() / 3) + unmapStream(); + else + unmapStatic(usedOffset, usedSize); + break; } is_mapped = false; @@ -218,31 +241,11 @@ void VBO::unbind() void VBO::fill(size_t offset, size_t size, const void *data) { if (is_mapped || getMemoryBacking() == BACKING_FULL) - memcpy(static_cast(memory_map) + offset, data, size); + memcpy(memory_map + offset, data, size); if (!is_mapped) { - // Not all systems have access to some faster paths... - if (GLEE_APPLE_flush_buffer_range) - { - void *mapdata = glMapBufferARB(getTarget(), GL_WRITE_ONLY); - - if (mapdata) - { - // We specified in VBO::load that we'll do manual flushing. - // Now we tell the driver it only needs to deal with the data - // we changed. - memcpy(static_cast(mapdata) + offset, data, size); - glFlushMappedBufferRangeAPPLE(getTarget(), (GLintptr) offset, (GLsizei) size); - } - - glUnmapBufferARB(getTarget()); - } - else - { - // Fall back to a possibly slower SubData (more chance of syncing.) - glBufferSubDataARB(getTarget(), (GLintptr) offset, (GLsizeiptr) size, data); - } + glBufferSubDataARB(getTarget(), (GLintptr) offset, (GLsizeiptr) size, data); if (getMemoryBacking() != BACKING_FULL) is_dirty = true; @@ -271,17 +274,11 @@ bool VBO::load(bool restore) VertexBuffer::Bind bind(*this); // Copy the old buffer only if 'restore' was requested. - const GLvoid *src = restore ? memory_map : 0; + const GLvoid *src = restore ? memory_map : nullptr; while (GL_NO_ERROR != glGetError()) /* clear error messages */; - // We don't want to flush the entire buffer when we just modify a small - // portion of it (VBO::fill without VBO::map), so we'll handle the flushing - // ourselves when we can. - if (GLEE_APPLE_flush_buffer_range) - glBufferParameteriAPPLE(getTarget(), GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE); - // Note that if 'src' is '0', no data will be copied. glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), src, getUsage()); GLenum err = glGetError(); diff --git a/src/modules/graphics/opengl/VertexBuffer.h b/src/modules/graphics/opengl/VertexBuffer.h index d29710cc4..cfaa42db1 100644 --- a/src/modules/graphics/opengl/VertexBuffer.h +++ b/src/modules/graphics/opengl/VertexBuffer.h @@ -28,6 +28,9 @@ // OpenGL #include "OpenGL.h" +// C +#include + namespace love { namespace graphics @@ -152,8 +155,12 @@ public: * when used to draw elements. * * The VertexBuffer must be bound to use this function. + * + * @param usedOffset The offset into the mapped buffer indicating the + * sub-range of data modified. Optional. + * @param usedSize The size of the sub-range of modified data. Optional. */ - virtual void unmap() = 0; + virtual void unmap(size_t usedOffset = 0, size_t usedSize = -1) = 0; /** * Bind the VertexBuffer to its specified target. @@ -294,7 +301,7 @@ public: // Implements VertexBuffer. virtual void *map(); - virtual void unmap(); + virtual void unmap(size_t usedOffset = 0, size_t usedSize = -1); virtual void bind(); virtual void unbind(); virtual void fill(size_t offset, size_t size, const void *data); @@ -328,7 +335,7 @@ public: // Implements VertexBuffer. virtual void *map(); - virtual void unmap(); + virtual void unmap(size_t usedOffset = 0, size_t usedSize = -1); virtual void bind(); virtual void unbind(); virtual void fill(size_t offset, size_t size, const void *data); @@ -355,12 +362,15 @@ private: */ void unload(bool save); + void unmapStatic(size_t offset, size_t size); + void unmapStream(); + // The VBO identifier. Assigned by OpenGL. GLuint vbo; // A pointer to mapped memory. Will be inialized on the first // call to map(). - void *memory_map; + char *memory_map; // Set if the buffer was modified while operating on gpu memory // and needs to be synchronized. diff --git a/src/modules/graphics/opengl/wrap_SpriteBatch.cpp b/src/modules/graphics/opengl/wrap_SpriteBatch.cpp index 1200743e8..97d2675e8 100644 --- a/src/modules/graphics/opengl/wrap_SpriteBatch.cpp +++ b/src/modules/graphics/opengl/wrap_SpriteBatch.cpp @@ -118,17 +118,16 @@ int w_SpriteBatch_clear(lua_State *L) return 0; } -int w_SpriteBatch_bind(lua_State *L) +int w_SpriteBatch_bind(lua_State* /*L*/) { - SpriteBatch *t = luax_checkspritebatch(L, 1); - luax_catchexcept(L, [&](){ t->lock(); }); + // No-op (deprecated.) return 0; } -int w_SpriteBatch_unbind(lua_State *L) +int w_SpriteBatch_flush(lua_State *L) { SpriteBatch *t = luax_checkspritebatch(L, 1); - t->unlock(); + t->flush(); return 0; } @@ -239,8 +238,7 @@ static const luaL_Reg functions[] = { "add", w_SpriteBatch_add }, { "set", w_SpriteBatch_set }, { "clear", w_SpriteBatch_clear }, - { "bind", w_SpriteBatch_bind }, - { "unbind", w_SpriteBatch_unbind }, + { "flush", w_SpriteBatch_flush }, { "setTexture", w_SpriteBatch_setTexture }, { "getTexture", w_SpriteBatch_getTexture }, { "setColor", w_SpriteBatch_setColor }, @@ -252,6 +250,11 @@ static const luaL_Reg functions[] = // Deprecated since 0.9.1. { "setImage", w_SpriteBatch_setTexture }, { "getImage", w_SpriteBatch_getTexture }, + + // Deprecated since 0.9.2. + { "bind", w_SpriteBatch_bind }, + { "unbind", w_SpriteBatch_flush }, + { 0, 0 } }; diff --git a/src/modules/graphics/opengl/wrap_SpriteBatch.h b/src/modules/graphics/opengl/wrap_SpriteBatch.h index 76828b8f2..f40d588e5 100644 --- a/src/modules/graphics/opengl/wrap_SpriteBatch.h +++ b/src/modules/graphics/opengl/wrap_SpriteBatch.h @@ -38,7 +38,7 @@ int w_SpriteBatch_set(lua_State *L); int w_SpriteBatch_setg(lua_State *L); int w_SpriteBatch_clear(lua_State *L); int w_SpriteBatch_bind(lua_State *L); -int w_SpriteBatch_unbind(lua_State *L); +int w_SpriteBatch_flush(lua_State *L); int w_SpriteBatch_setTexture(lua_State *L); int w_SpriteBatch_getTexture(lua_State *L); int w_SpriteBatch_setColor(lua_State *L);