Browse Source

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
Alex Szpakowski 11 years ago
parent
commit
e0f045c310

+ 14 - 20
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -48,6 +48,8 @@ SpriteBatch::SpriteBatch(Texture *texture, int size, int usage)
 	, color(0)
 	, color(0)
 	, array_buf(0)
 	, array_buf(0)
 	, element_buf(0)
 	, element_buf(0)
+	, buffer_used_offset(0)
+	, buffer_used_size(0)
 {
 {
 	if (size <= 0)
 	if (size <= 0)
 		throw love::Exception("Invalid SpriteBatch size.");
 		throw love::Exception("Invalid SpriteBatch size.");
@@ -163,7 +165,9 @@ void SpriteBatch::clear()
 void SpriteBatch::flush()
 void SpriteBatch::flush()
 {
 {
 	VertexBuffer::Bind bind(*array_buf);
 	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)
 void SpriteBatch::setTexture(Texture *newtexture)
@@ -222,32 +226,21 @@ void SpriteBatch::setBufferSize(int newsize)
 
 
 	VertexBuffer *new_array_buf = nullptr;
 	VertexBuffer *new_array_buf = nullptr;
 	VertexIndex *new_element_buf = nullptr;
 	VertexIndex *new_element_buf = nullptr;
-	void *new_data = nullptr;
 
 
 	try
 	try
 	{
 	{
 		new_array_buf = VertexBuffer::Create(vertex_size, array_buf->getTarget(), array_buf->getUsage());
 		new_array_buf = VertexBuffer::Create(vertex_size, array_buf->getTarget(), array_buf->getUsage());
 		new_element_buf = new VertexIndex(newsize);
 		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 &)
 	catch (love::Exception &)
 	{
 	{
 		delete new_array_buf;
 		delete new_array_buf;
 		delete new_element_buf;
 		delete new_element_buf;
-
-		{
-			VertexBuffer::Bind bind(*array_buf);
-			array_buf->unmap();
-		}
-
 		throw;
 		throw;
 	}
 	}
 
 
 	// Copy as much of the old data into the new VertexBuffer as can fit.
 	// 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.
 	// We don't need to unmap the old VertexBuffer since we're deleting it.
 	delete array_buf;
 	delete array_buf;
@@ -259,11 +252,8 @@ void SpriteBatch::setBufferSize(int newsize)
 
 
 	next = std::min(next, 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
 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());
 	VertexBuffer::Bind element_bind(*element_buf->getVertexBuffer());
 
 
 	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
 	// 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();
 	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)
 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);
 	VertexBuffer::Bind bind(*array_buf);
 
 
@@ -338,6 +329,9 @@ void SpriteBatch::addv(const Vertex *v, int index)
 	array_buf->map();
 	array_buf->map();
 
 
 	array_buf->fill(index * sprite_size, sprite_size, v);
 	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)
 void SpriteBatch::setColorv(Vertex *v, const Color &color)

+ 4 - 0
src/modules/graphics/opengl/SpriteBatch.h

@@ -141,6 +141,10 @@ private:
 	VertexBuffer *array_buf;
 	VertexBuffer *array_buf;
 	VertexIndex *element_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<UsageHint, USAGE_MAX_ENUM>::Entry usageHintEntries[];
 	static StringMap<UsageHint, USAGE_MAX_ENUM>::Entry usageHintEntries[];
 	static StringMap<UsageHint, USAGE_MAX_ENUM> usageHints;
 	static StringMap<UsageHint, USAGE_MAX_ENUM> usageHints;
 
 

+ 37 - 14
src/modules/graphics/opengl/VertexBuffer.cpp

@@ -59,7 +59,7 @@ VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacki
 	, is_dirty(true)
 	, is_dirty(true)
 {
 {
 	if (getMemoryBacking() == BACKING_FULL)
 	if (getMemoryBacking() == BACKING_FULL)
-		memory_map = malloc(getSize());
+		memory_map = (char *) malloc(getSize());
 
 
 	bool ok = load(false);
 	bool ok = load(false);
 
 
@@ -86,7 +86,7 @@ void *VertexBuffer::map()
 
 
 	if (!memory_map)
 	if (!memory_map)
 	{
 	{
-		memory_map = malloc(getSize());
+		memory_map = (char *) malloc(getSize());
 		if (!memory_map)
 		if (!memory_map)
 			throw love::Exception("Out of memory (oh the humanity!)");
 			throw love::Exception("Out of memory (oh the humanity!)");
 	}
 	}
@@ -102,11 +102,28 @@ void *VertexBuffer::map()
 	return memory_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)
 	if (!is_mapped)
 		return;
 		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
 	// VBO::bind is a no-op when the VBO is mapped, so we have to make sure it's
 	// bound here.
 	// bound here.
 	if (!is_bound)
 	if (!is_bound)
@@ -115,17 +132,23 @@ void VertexBuffer::unmap()
 		is_bound = true;
 		is_bound = true;
 	}
 	}
 
 
-	if (getUsage() == GL_STATIC_DRAW)
-	{
-		// Upload the mapped data to the buffer.
-		glBufferSubData(getTarget(), 0, (GLsizeiptr) getSize(), memory_map);
-	}
-	else
+	switch (getUsage())
 	{
 	{
-		// "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;
 	is_mapped = false;
@@ -151,7 +174,7 @@ void VertexBuffer::unbind()
 void VertexBuffer::fill(size_t offset, size_t size, const void *data)
 void VertexBuffer::fill(size_t offset, size_t size, const void *data)
 {
 {
 	if (is_mapped || getMemoryBacking() == BACKING_FULL)
 	if (is_mapped || getMemoryBacking() == BACKING_FULL)
-		memcpy(static_cast<char *>(memory_map) + offset, data, size);
+		memcpy(memory_map + offset, data, size);
 
 
 	if (!is_mapped)
 	if (!is_mapped)
 	{
 	{

+ 11 - 2
src/modules/graphics/opengl/VertexBuffer.h

@@ -28,6 +28,9 @@
 // OpenGL
 // OpenGL
 #include "OpenGL.h"
 #include "OpenGL.h"
 
 
+// C
+#include <stddef.h>
+
 namespace love
 namespace love
 {
 {
 namespace graphics
 namespace graphics
@@ -145,8 +148,12 @@ public:
 	 * when used to draw elements.
 	 * when used to draw elements.
 	 *
 	 *
 	 * The VertexBuffer must be bound to use this function.
 	 * 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.
 	 * Bind the VertexBuffer to its specified target.
@@ -267,6 +274,8 @@ private:
 	 */
 	 */
 	void unload(bool save);
 	void unload(bool save);
 
 
+	void unmapStatic(size_t offset, size_t size);
+	void unmapStream();
 
 
 	// Whether the buffer is currently bound.
 	// Whether the buffer is currently bound.
 	bool is_bound;
 	bool is_bound;
@@ -291,7 +300,7 @@ private:
 
 
 	// A pointer to mapped memory. Will be inialized on the first
 	// A pointer to mapped memory. Will be inialized on the first
 	// call to map().
 	// call to map().
-	void *memory_map;
+	char *memory_map;
 
 
 	// Set if the buffer was modified while operating on gpu memory
 	// Set if the buffer was modified while operating on gpu memory
 	// and needs to be synchronized.
 	// and needs to be synchronized.