From e0f045c310625f468fdecc83cda856d7e3c98aaa Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 29 May 2014 17:11:03 -0300 Subject: [PATCH] SpriteBatches are a little more intelligent about how much data they upload to the GPU when they're flushed or drawn, now --HG-- branch : minor --- src/modules/graphics/opengl/SpriteBatch.cpp | 34 ++++++------- src/modules/graphics/opengl/SpriteBatch.h | 4 ++ src/modules/graphics/opengl/VertexBuffer.cpp | 51 ++++++++++++++------ src/modules/graphics/opengl/VertexBuffer.h | 13 ++++- 4 files changed, 66 insertions(+), 36 deletions(-) diff --git a/src/modules/graphics/opengl/SpriteBatch.cpp b/src/modules/graphics/opengl/SpriteBatch.cpp index 360ccdd4e..7e7efc609 100644 --- a/src/modules/graphics/opengl/SpriteBatch.cpp +++ b/src/modules/graphics/opengl/SpriteBatch.cpp @@ -48,6 +48,8 @@ SpriteBatch::SpriteBatch(Texture *texture, int size, int usage) , color(0) , array_buf(0) , element_buf(0) + , buffer_used_offset(0) + , buffer_used_size(0) { if (size <= 0) throw love::Exception("Invalid SpriteBatch size."); @@ -163,7 +165,9 @@ void SpriteBatch::clear() void SpriteBatch::flush() { VertexBuffer::Bind bind(*array_buf); - array_buf->unmap(); + array_buf->unmap(buffer_used_offset, buffer_used_size); + + buffer_used_offset = buffer_used_size = 0; } void SpriteBatch::setTexture(Texture *newtexture) @@ -222,32 +226,21 @@ void SpriteBatch::setBufferSize(int newsize) VertexBuffer *new_array_buf = nullptr; VertexIndex *new_element_buf = nullptr; - void *new_data = 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; - - { - VertexBuffer::Bind bind(*array_buf); - array_buf->unmap(); - } - 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; @@ -259,11 +252,8 @@ void SpriteBatch::setBufferSize(int newsize) next = std::min(next, newsize); - // But we should unmap the new one! - { - VertexBuffer::Bind bind(*new_array_buf); - new_array_buf->unmap(); - } + // The new VertexBuffer isn't mapped, so we should reset these variables. + buffer_used_offset = buffer_used_size = 0; } int SpriteBatch::getBufferSize() const @@ -293,7 +283,8 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float 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(); + array_buf->unmap(buffer_used_offset, buffer_used_size); + buffer_used_offset = buffer_used_size = 0; Color curcolor = gl.getColor(); @@ -329,7 +320,7 @@ 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); @@ -338,6 +329,9 @@ void SpriteBatch::addv(const Vertex *v, int index) 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 b057cefac..1f3de1c99 100644 --- a/src/modules/graphics/opengl/SpriteBatch.h +++ b/src/modules/graphics/opengl/SpriteBatch.h @@ -141,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 7d935af65..a23d63880 100644 --- a/src/modules/graphics/opengl/VertexBuffer.cpp +++ b/src/modules/graphics/opengl/VertexBuffer.cpp @@ -59,7 +59,7 @@ VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacki , is_dirty(true) { if (getMemoryBacking() == BACKING_FULL) - memory_map = malloc(getSize()); + memory_map = (char *) malloc(getSize()); bool ok = load(false); @@ -86,7 +86,7 @@ void *VertexBuffer::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!)"); } @@ -102,11 +102,28 @@ void *VertexBuffer::map() return memory_map; } -void VertexBuffer::unmap() +void VertexBuffer::unmapStatic(size_t offset, size_t size) +{ + // Upload the mapped data to the buffer. + glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, memory_map + offset); +} + +void VertexBuffer::unmapStream() +{ + // "orphan" current buffer to avoid implicit synchronisation on the GPU: + // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf + glBufferData(getTarget(), (GLsizeiptr) getSize(), nullptr, getUsage()); + glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage()); +} + +void VertexBuffer::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) @@ -115,17 +132,23 @@ void VertexBuffer::unmap() is_bound = true; } - if (getUsage() == GL_STATIC_DRAW) + switch (getUsage()) { - // Upload the mapped data to the buffer. - glBufferSubData(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 - glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage()); - glBufferData(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; @@ -151,7 +174,7 @@ void VertexBuffer::unbind() void VertexBuffer::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) { diff --git a/src/modules/graphics/opengl/VertexBuffer.h b/src/modules/graphics/opengl/VertexBuffer.h index 9a79fff96..e2c221145 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 @@ -145,8 +148,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(); + virtual void unmap(size_t usedOffset = 0, size_t usedSize = -1); /** * Bind the VertexBuffer to its specified target. @@ -267,6 +274,8 @@ private: */ void unload(bool save); + void unmapStatic(size_t offset, size_t size); + void unmapStream(); // Whether the buffer is currently bound. bool is_bound; @@ -291,7 +300,7 @@ private: // 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.