Browse Source

Enable (when available) and improve performance of persistently mapped buffer path for the vertex/index buffers used with automatic batching.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
b4f8f08b70

+ 2 - 2
CMakeLists.txt

@@ -542,10 +542,10 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
 set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
 	src/modules/graphics/opengl/Buffer.cpp
 	src/modules/graphics/opengl/Buffer.cpp
 	src/modules/graphics/opengl/Buffer.h
 	src/modules/graphics/opengl/Buffer.h
-	src/modules/graphics/opengl/BufferSync.cpp
-	src/modules/graphics/opengl/BufferSync.h
 	src/modules/graphics/opengl/Canvas.cpp
 	src/modules/graphics/opengl/Canvas.cpp
 	src/modules/graphics/opengl/Canvas.h
 	src/modules/graphics/opengl/Canvas.h
+	src/modules/graphics/opengl/FenceSync.cpp
+	src/modules/graphics/opengl/FenceSync.h
 	src/modules/graphics/opengl/Graphics.cpp
 	src/modules/graphics/opengl/Graphics.cpp
 	src/modules/graphics/opengl/Graphics.h
 	src/modules/graphics/opengl/Graphics.h
 	src/modules/graphics/opengl/Image.cpp
 	src/modules/graphics/opengl/Image.cpp

+ 10 - 10
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -844,9 +844,9 @@
 		FA27B3C11B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3B91B4985BF008A9DCE /* wrap_VideoStream.cpp */; };
 		FA27B3C11B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA27B3B91B4985BF008A9DCE /* wrap_VideoStream.cpp */; };
 		FA27B3C21B4985BF008A9DCE /* wrap_VideoStream.h in Headers */ = {isa = PBXBuildFile; fileRef = FA27B3BA1B4985BF008A9DCE /* wrap_VideoStream.h */; };
 		FA27B3C21B4985BF008A9DCE /* wrap_VideoStream.h in Headers */ = {isa = PBXBuildFile; fileRef = FA27B3BA1B4985BF008A9DCE /* wrap_VideoStream.h */; };
 		FA27B3C91B498623008A9DCE /* Theora.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA27B3C81B498623008A9DCE /* Theora.framework */; };
 		FA27B3C91B498623008A9DCE /* Theora.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA27B3C81B498623008A9DCE /* Theora.framework */; };
-		FA28EBD51E352DB5003446F4 /* BufferSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA28EBD31E352DB5003446F4 /* BufferSync.cpp */; };
-		FA28EBD61E352DB5003446F4 /* BufferSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA28EBD31E352DB5003446F4 /* BufferSync.cpp */; };
-		FA28EBD71E352DB5003446F4 /* BufferSync.h in Headers */ = {isa = PBXBuildFile; fileRef = FA28EBD41E352DB5003446F4 /* BufferSync.h */; };
+		FA28EBD51E352DB5003446F4 /* FenceSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA28EBD31E352DB5003446F4 /* FenceSync.cpp */; };
+		FA28EBD61E352DB5003446F4 /* FenceSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA28EBD31E352DB5003446F4 /* FenceSync.cpp */; };
+		FA28EBD71E352DB5003446F4 /* FenceSync.h in Headers */ = {isa = PBXBuildFile; fileRef = FA28EBD41E352DB5003446F4 /* FenceSync.h */; };
 		FA29C0051E12355B00268CD8 /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */; };
 		FA29C0051E12355B00268CD8 /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */; };
 		FA29C0061E12355B00268CD8 /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */; };
 		FA29C0061E12355B00268CD8 /* StreamBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */; };
 		FA2AF6741DAD64970032B62C /* vertex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA2AF6731DAD64970032B62C /* vertex.cpp */; };
 		FA2AF6741DAD64970032B62C /* vertex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA2AF6731DAD64970032B62C /* vertex.cpp */; };
@@ -1738,8 +1738,8 @@
 		FA27B3C81B498623008A9DCE /* Theora.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Theora.framework; path = /Library/Frameworks/Theora.framework; sourceTree = "<absolute>"; };
 		FA27B3C81B498623008A9DCE /* Theora.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Theora.framework; path = /Library/Frameworks/Theora.framework; sourceTree = "<absolute>"; };
 		FA283EDC1B27CFAA00C70067 /* nogame.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = nogame.lua; sourceTree = "<group>"; };
 		FA283EDC1B27CFAA00C70067 /* nogame.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = nogame.lua; sourceTree = "<group>"; };
 		FA283EDD1B27CFAA00C70067 /* nogame.lua.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nogame.lua.h; sourceTree = "<group>"; };
 		FA283EDD1B27CFAA00C70067 /* nogame.lua.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nogame.lua.h; sourceTree = "<group>"; };
-		FA28EBD31E352DB5003446F4 /* BufferSync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BufferSync.cpp; sourceTree = "<group>"; };
-		FA28EBD41E352DB5003446F4 /* BufferSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BufferSync.h; sourceTree = "<group>"; };
+		FA28EBD31E352DB5003446F4 /* FenceSync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FenceSync.cpp; sourceTree = "<group>"; };
+		FA28EBD41E352DB5003446F4 /* FenceSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FenceSync.h; sourceTree = "<group>"; };
 		FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
 		FA29C0041E12355B00268CD8 /* StreamBuffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StreamBuffer.cpp; sourceTree = "<group>"; };
 		FA2AF6711DAC76FF0032B62C /* vertex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vertex.h; sourceTree = "<group>"; };
 		FA2AF6711DAC76FF0032B62C /* vertex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vertex.h; sourceTree = "<group>"; };
 		FA2AF6721DAD62710032B62C /* StreamBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
 		FA2AF6721DAD62710032B62C /* StreamBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StreamBuffer.h; sourceTree = "<group>"; };
@@ -2733,10 +2733,10 @@
 			children = (
 			children = (
 				FA0B7BA41A95902C000E1D17 /* Buffer.cpp */,
 				FA0B7BA41A95902C000E1D17 /* Buffer.cpp */,
 				FA0B7BA51A95902C000E1D17 /* Buffer.h */,
 				FA0B7BA51A95902C000E1D17 /* Buffer.h */,
-				FA28EBD31E352DB5003446F4 /* BufferSync.cpp */,
-				FA28EBD41E352DB5003446F4 /* BufferSync.h */,
 				FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */,
 				FA0B7B8D1A95902C000E1D17 /* Canvas.cpp */,
 				FA0B7B8E1A95902C000E1D17 /* Canvas.h */,
 				FA0B7B8E1A95902C000E1D17 /* Canvas.h */,
+				FA28EBD31E352DB5003446F4 /* FenceSync.cpp */,
+				FA28EBD41E352DB5003446F4 /* FenceSync.h */,
 				FA0B7B911A95902C000E1D17 /* Graphics.cpp */,
 				FA0B7B911A95902C000E1D17 /* Graphics.cpp */,
 				FA0B7B921A95902C000E1D17 /* Graphics.h */,
 				FA0B7B921A95902C000E1D17 /* Graphics.h */,
 				FA0B7B931A95902C000E1D17 /* Image.cpp */,
 				FA0B7B931A95902C000E1D17 /* Image.cpp */,
@@ -3837,7 +3837,7 @@
 				FA0B7DDE1A95902C000E1D17 /* wrap_BezierCurve.h in Headers */,
 				FA0B7DDE1A95902C000E1D17 /* wrap_BezierCurve.h in Headers */,
 				FA0B7DED1A95902C000E1D17 /* Cursor.h in Headers */,
 				FA0B7DED1A95902C000E1D17 /* Cursor.h in Headers */,
 				FA0B7E501A95902C000E1D17 /* wrap_Fixture.h in Headers */,
 				FA0B7E501A95902C000E1D17 /* wrap_Fixture.h in Headers */,
-				FA28EBD71E352DB5003446F4 /* BufferSync.h in Headers */,
+				FA28EBD71E352DB5003446F4 /* FenceSync.h in Headers */,
 				FADF542C1E3DAADA00012CC0 /* wrap_Mesh.h in Headers */,
 				FADF542C1E3DAADA00012CC0 /* wrap_Mesh.h in Headers */,
 				FAA3A9B01B7D465A00CED060 /* android.h in Headers */,
 				FAA3A9B01B7D465A00CED060 /* android.h in Headers */,
 				217DFBDE1D9F6D490055D849 /* compat.h in Headers */,
 				217DFBDE1D9F6D490055D849 /* compat.h in Headers */,
@@ -4253,7 +4253,7 @@
 				FA57FB991AE1993600F2AD6D /* noise1234.cpp in Sources */,
 				FA57FB991AE1993600F2AD6D /* noise1234.cpp in Sources */,
 				FA0B7E221A95902C000E1D17 /* PolygonShape.cpp in Sources */,
 				FA0B7E221A95902C000E1D17 /* PolygonShape.cpp in Sources */,
 				FA0B7A651A958EA3000E1D17 /* b2Fixture.cpp in Sources */,
 				FA0B7A651A958EA3000E1D17 /* b2Fixture.cpp in Sources */,
-				FA28EBD61E352DB5003446F4 /* BufferSync.cpp in Sources */,
+				FA28EBD61E352DB5003446F4 /* FenceSync.cpp in Sources */,
 				FA0B7D521A95902C000E1D17 /* Text.cpp in Sources */,
 				FA0B7D521A95902C000E1D17 /* Text.cpp in Sources */,
 				FA0B7DA31A95902C000E1D17 /* PKMHandler.cpp in Sources */,
 				FA0B7DA31A95902C000E1D17 /* PKMHandler.cpp in Sources */,
 				FA0B7AB21A958EA3000E1D17 /* b2Rope.cpp in Sources */,
 				FA0B7AB21A958EA3000E1D17 /* b2Rope.cpp in Sources */,
@@ -4613,7 +4613,7 @@
 				FA0B7EA61A95902C000E1D17 /* wrap_Decoder.cpp in Sources */,
 				FA0B7EA61A95902C000E1D17 /* wrap_Decoder.cpp in Sources */,
 				217DFBF21D9F6D490055D849 /* mime.c in Sources */,
 				217DFBF21D9F6D490055D849 /* mime.c in Sources */,
 				217DFBDF1D9F6D490055D849 /* except.c in Sources */,
 				217DFBDF1D9F6D490055D849 /* except.c in Sources */,
-				FA28EBD51E352DB5003446F4 /* BufferSync.cpp in Sources */,
+				FA28EBD51E352DB5003446F4 /* FenceSync.cpp in Sources */,
 				FA0B7E1B1A95902C000E1D17 /* MouseJoint.cpp in Sources */,
 				FA0B7E1B1A95902C000E1D17 /* MouseJoint.cpp in Sources */,
 				FA0B7CF41A95902C000E1D17 /* File.cpp in Sources */,
 				FA0B7CF41A95902C000E1D17 /* File.cpp in Sources */,
 				FA0B7E331A95902C000E1D17 /* WeldJoint.cpp in Sources */,
 				FA0B7E331A95902C000E1D17 /* WeldJoint.cpp in Sources */,

+ 2 - 2
src/modules/graphics/Graphics.cpp

@@ -798,7 +798,7 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawCommand &
 		if (state.vbMap[i].data != nullptr && datasize > state.vbMap[i].size)
 		if (state.vbMap[i].data != nullptr && datasize > state.vbMap[i].size)
 			shouldflush = true;
 			shouldflush = true;
 
 
-		if (datasize > state.vb[i]->getSize())
+		if (datasize > state.vb[i]->getUsableSize())
 		{
 		{
 			buffersizes[i] = std::max(datasize, state.vb[i]->getSize() * 2);
 			buffersizes[i] = std::max(datasize, state.vb[i]->getSize() * 2);
 			shouldresize = true;
 			shouldresize = true;
@@ -814,7 +814,7 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawCommand &
 		if (state.indexBufferMap.data != nullptr && datasize > state.indexBufferMap.size)
 		if (state.indexBufferMap.data != nullptr && datasize > state.indexBufferMap.size)
 			shouldflush = true;
 			shouldflush = true;
 
 
-		if (datasize > state.indexBuffer->getSize())
+		if (datasize > state.indexBuffer->getUsableSize())
 		{
 		{
 			buffersizes[2] = std::max(datasize, state.indexBuffer->getSize() * 2);
 			buffersizes[2] = std::max(datasize, state.indexBuffer->getSize() * 2);
 			shouldresize = true;
 			shouldresize = true;

+ 3 - 0
src/modules/graphics/StreamBuffer.h

@@ -54,10 +54,13 @@ public:
 	size_t getSize() const { return bufferSize; }
 	size_t getSize() const { return bufferSize; }
 	BufferType getMode() const { return mode; }
 	BufferType getMode() const { return mode; }
 
 
+	virtual size_t getUsableSize() const = 0;
 	virtual MapInfo map(size_t minsize) = 0;
 	virtual MapInfo map(size_t minsize) = 0;
 	virtual size_t unmap(size_t usedsize) = 0;
 	virtual size_t unmap(size_t usedsize) = 0;
 	virtual void markUsed(size_t usedsize) = 0;
 	virtual void markUsed(size_t usedsize) = 0;
 
 
+	virtual void nextFrame() {}
+
 	virtual ptrdiff_t getHandle() const = 0;
 	virtual ptrdiff_t getHandle() const = 0;
 
 
 protected:
 protected:

+ 54 - 1
src/modules/graphics/opengl/BufferSync.cpp → src/modules/graphics/opengl/FenceSync.cpp

@@ -19,7 +19,7 @@
  **/
  **/
 
 
 
 
-#include "BufferSync.h"
+#include "FenceSync.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -28,6 +28,59 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
+FenceSync::~FenceSync()
+{
+	cleanup();
+}
+
+bool FenceSync::fence()
+{
+	bool wasActive = sync != 0;
+
+	if (wasActive)
+		cleanup();
+
+	sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+	return !wasActive;
+}
+
+bool FenceSync::cpuWait()
+{
+	if (sync == 0)
+		return false;
+
+	GLbitfield flags = 0;
+	GLuint64 duration = 0;
+
+	while (true)
+	{
+		GLenum status = glClientWaitSync(sync, flags, duration);
+
+		if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED)
+			break;
+
+		if (status == GL_WAIT_FAILED)
+			break;
+
+		flags = GL_SYNC_FLUSH_COMMANDS_BIT;
+		duration = 1000000000; // 1 second in nanoseconds.
+	}
+
+	cleanup();
+
+	return true;
+}
+
+void FenceSync::cleanup()
+{
+	if (sync != 0)
+	{
+		glDeleteSync(sync);
+		sync = 0;
+	}
+}
+
 BufferSync::~BufferSync()
 BufferSync::~BufferSync()
 {
 {
 	cleanup();
 	cleanup();

+ 16 - 0
src/modules/graphics/opengl/BufferSync.h → src/modules/graphics/opengl/FenceSync.h

@@ -34,6 +34,22 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
+class FenceSync
+{
+public:
+
+	~FenceSync();
+
+	bool fence();
+	bool cpuWait();
+	void cleanup();
+
+private:
+
+	GLsync sync;
+
+}; // FenceSync
+
 class BufferSync
 class BufferSync
 {
 {
 public:
 public:

+ 4 - 0
src/modules/graphics/opengl/Graphics.cpp

@@ -1004,6 +1004,10 @@ void Graphics::present(void *screenshotCallbackData)
 	glBindRenderbuffer(GL_RENDERBUFFER, info.info.uikit.colorbuffer);
 	glBindRenderbuffer(GL_RENDERBUFFER, info.info.uikit.colorbuffer);
 #endif
 #endif
 
 
+	for (StreamBuffer *buffer : streamBufferState.vb)
+		buffer->nextFrame();
+	streamBufferState.indexBuffer->nextFrame();
+
 	auto window = getInstance<love::window::Window>(M_WINDOW);
 	auto window = getInstance<love::window::Window>(M_WINDOW);
 	if (window != nullptr)
 	if (window != nullptr)
 		window->swapBuffers();
 		window->swapBuffers();

+ 139 - 62
src/modules/graphics/opengl/StreamBuffer.cpp

@@ -18,13 +18,15 @@
  * 3. This notice may not be removed or altered from any source distribution.
  * 3. This notice may not be removed or altered from any source distribution.
  **/
  **/
 
 
+#include "common/config.h"
 #include "StreamBuffer.h"
 #include "StreamBuffer.h"
 #include "OpenGL.h"
 #include "OpenGL.h"
-#include "BufferSync.h"
+#include "FenceSync.h"
 #include "graphics/Volatile.h"
 #include "graphics/Volatile.h"
 #include "common/Exception.h"
 #include "common/Exception.h"
 
 
 #include <vector>
 #include <vector>
+#include <algorithm>
 
 
 namespace love
 namespace love
 {
 {
@@ -33,7 +35,8 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-static int BUFFER_FRAMES = 3;
+static const int BUFFER_FRAMES = 3;
+static const int MAX_SYNCS_PER_FRAME = 4;
 
 
 class StreamBufferClientMemory final : public love::graphics::StreamBuffer
 class StreamBufferClientMemory final : public love::graphics::StreamBuffer
 {
 {
@@ -58,6 +61,11 @@ public:
 		delete[] data;
 		delete[] data;
 	}
 	}
 
 
+	size_t getUsableSize() const override
+	{
+		return bufferSize;
+	}
+
 	MapInfo map(size_t /*minsize*/) override
 	MapInfo map(size_t /*minsize*/) override
 	{
 	{
 		return MapInfo(data, bufferSize);
 		return MapInfo(data, bufferSize);
@@ -87,6 +95,7 @@ public:
 		, glMode(OpenGL::getGLBufferType(mode))
 		, glMode(OpenGL::getGLBufferType(mode))
 		, data(nullptr)
 		, data(nullptr)
 		, offset(0)
 		, offset(0)
+		, frameOffset(0)
 	{
 	{
 		try
 		try
 		{
 		{
@@ -106,11 +115,17 @@ public:
 		delete[] data;
 		delete[] data;
 	}
 	}
 
 
+	size_t getUsableSize() const override
+	{
+		return bufferSize - frameOffset;
+	}
+
 	MapInfo map(size_t minsize) override
 	MapInfo map(size_t minsize) override
 	{
 	{
 		if (offset + minsize > bufferSize)
 		if (offset + minsize > bufferSize)
 		{
 		{
 			offset = 0;
 			offset = 0;
+			frameOffset = 0;
 			gl.bindBuffer(mode, vbo);
 			gl.bindBuffer(mode, vbo);
 			glBufferData(glMode, bufferSize, nullptr, GL_STREAM_DRAW);
 			glBufferData(glMode, bufferSize, nullptr, GL_STREAM_DRAW);
 		}
 		}
@@ -128,6 +143,12 @@ public:
 	void markUsed(size_t usedsize) override
 	void markUsed(size_t usedsize) override
 	{
 	{
 		offset += usedsize;
 		offset += usedsize;
+		frameOffset += usedsize;
+	}
+
+	void nextFrame() override
+	{
+		frameOffset = 0;
 	}
 	}
 
 
 	ptrdiff_t getHandle() const override { return vbo; }
 	ptrdiff_t getHandle() const override { return vbo; }
@@ -142,6 +163,7 @@ public:
 		glBufferData(glMode, bufferSize, nullptr, GL_STREAM_DRAW);
 		glBufferData(glMode, bufferSize, nullptr, GL_STREAM_DRAW);
 
 
 		offset = 0;
 		offset = 0;
+		frameOffset = 0;
 
 
 		return true;
 		return true;
 	}
 	}
@@ -163,17 +185,69 @@ protected:
 	uint8 *data;
 	uint8 *data;
 
 
 	size_t offset;
 	size_t offset;
+	size_t frameOffset;
 
 
 }; // StreamBufferSubDataOrphan
 }; // StreamBufferSubDataOrphan
 
 
-class StreamBufferMapSync final : public love::graphics::StreamBuffer, public Volatile
+class StreamBufferSync : public love::graphics::StreamBuffer
 {
 {
 public:
 public:
 
 
-	StreamBufferMapSync(BufferType type, size_t size)
+	StreamBufferSync(BufferType type, size_t size)
 		: love::graphics::StreamBuffer(type, size)
 		: love::graphics::StreamBuffer(type, size)
+		, syncSize((size + MAX_SYNCS_PER_FRAME - 1) / MAX_SYNCS_PER_FRAME)
+		, frameIndex(0)
+		, frameGPUReadOffset(0)
+		, syncs()
+	{}
+
+	virtual ~StreamBufferSync() {}
+
+	void nextFrame() override
+	{
+		getCurrentSync()->fence();
+
+		frameIndex = (frameIndex + 1) % BUFFER_FRAMES;
+		frameGPUReadOffset = 0;
+	}
+
+	void markUsed(size_t usedsize) override
+	{
+		int firstSyncIndex = frameGPUReadOffset / syncSize;
+		int lastSyncIndex = std::min((frameGPUReadOffset + usedsize), bufferSize - 1) / syncSize;
+
+		// Insert fences for all sync buckets completely filled by this section
+		// of the data. The last bucket before the end of the frame will also be
+		// handled by nextFrame().
+		for (int i = firstSyncIndex; i < lastSyncIndex; i++)
+			syncs[frameIndex * MAX_SYNCS_PER_FRAME + i].fence();
+
+		frameGPUReadOffset += usedsize;
+	}
+
+protected:
+
+	const size_t syncSize;
+
+	int frameIndex;
+	size_t frameGPUReadOffset;
+
+	FenceSync syncs[MAX_SYNCS_PER_FRAME * BUFFER_FRAMES];
+
+	FenceSync *getCurrentSync()
+	{
+		return &syncs[frameIndex * MAX_SYNCS_PER_FRAME + frameGPUReadOffset / syncSize];
+	}
+
+}; // StreamBufferSync
+
+class StreamBufferMapSync final : public StreamBufferSync, public Volatile
+{
+public:
+
+	StreamBufferMapSync(BufferType type, size_t size)
+		: StreamBufferSync(type, size)
 		, vbo(0)
 		, vbo(0)
-		, gpuReadOffset(0)
 		, glMode(OpenGL::getGLBufferType(mode))
 		, glMode(OpenGL::getGLBufferType(mode))
 	{
 	{
 		loadVolatile();
 		loadVolatile();
@@ -184,21 +258,32 @@ public:
 		unloadVolatile();
 		unloadVolatile();
 	}
 	}
 
 
-	MapInfo map(size_t minsize) override
+	size_t getUsableSize() const override
 	{
 	{
-		gl.bindBuffer(mode, vbo);
+		return bufferSize - frameGPUReadOffset;
+	}
 
 
-		if (gpuReadOffset + minsize > bufferSize * BUFFER_FRAMES)
-			gpuReadOffset = 0;
+	MapInfo map(size_t /*minsize*/) override
+	{
+		gl.bindBuffer(mode, vbo);
 
 
 		MapInfo info;
 		MapInfo info;
-		info.size = bufferSize - (gpuReadOffset % bufferSize);
+		info.size = bufferSize - frameGPUReadOffset;
+
+		int firstSyncIndex = frameGPUReadOffset / syncSize;
+		int lastSyncIndex = (bufferSize - 1) / syncSize;
 
 
-		sync.wait(gpuReadOffset, info.size);
+		// We're mapping the full range of space left in the buffer, so we
+		// need to wait on all of it...
+		// FIXME: is it even worth it to have multiple sync objects per frame?
+		for (int i = firstSyncIndex; i <= lastSyncIndex; i++)
+			syncs[frameIndex * MAX_SYNCS_PER_FRAME + i].cpuWait();
 
 
 		GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
 		GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
 
 
-		info.data = (uint8 *) glMapBufferRange(glMode, gpuReadOffset, info.size, flags);
+		size_t mapoffset = (frameIndex * bufferSize) + frameGPUReadOffset;
+		info.data = (uint8 *) glMapBufferRange(glMode, mapoffset, info.size, flags);
+
 		return info;
 		return info;
 	}
 	}
 
 
@@ -208,13 +293,7 @@ public:
 		glFlushMappedBufferRange(glMode, 0, usedsize);
 		glFlushMappedBufferRange(glMode, 0, usedsize);
 		glUnmapBuffer(glMode);
 		glUnmapBuffer(glMode);
 
 
-		return gpuReadOffset;
-	}
-
-	void markUsed(size_t usedsize) override
-	{
-		sync.lock(gpuReadOffset, usedsize);
-		gpuReadOffset += usedsize;
+		return (frameIndex * bufferSize) + frameGPUReadOffset;
 	}
 	}
 
 
 	ptrdiff_t getHandle() const override { return vbo; }
 	ptrdiff_t getHandle() const override { return vbo; }
@@ -228,40 +307,38 @@ public:
 		gl.bindBuffer(mode, vbo);
 		gl.bindBuffer(mode, vbo);
 		glBufferData(glMode, bufferSize * BUFFER_FRAMES, nullptr, GL_STREAM_DRAW);
 		glBufferData(glMode, bufferSize * BUFFER_FRAMES, nullptr, GL_STREAM_DRAW);
 
 
-		gpuReadOffset = 0;
+		frameGPUReadOffset = 0;
+		frameIndex = 0;
 
 
 		return true;
 		return true;
 	}
 	}
 
 
 	void unloadVolatile() override
 	void unloadVolatile() override
 	{
 	{
-		if (vbo == 0)
-			return;
-
-		gl.deleteBuffer(vbo);
-		vbo = 0;
+		if (vbo != 0)
+		{
+			gl.deleteBuffer(vbo);
+			vbo = 0;
+		}
 
 
-		sync.cleanup();
+		for (FenceSync &sync : syncs)
+			sync.cleanup();
 	}
 	}
 
 
 private:
 private:
 
 
 	GLuint vbo;
 	GLuint vbo;
-	size_t gpuReadOffset;
 	GLenum glMode;
 	GLenum glMode;
 
 
-	BufferSync sync;
-
 }; // StreamBufferMapSync
 }; // StreamBufferMapSync
 
 
-class StreamBufferPersistentMapSync final : public love::graphics::StreamBuffer, public Volatile
+class StreamBufferPersistentMapSync final : public StreamBufferSync, public Volatile
 {
 {
 public:
 public:
 
 
 	StreamBufferPersistentMapSync(BufferType type, size_t size)
 	StreamBufferPersistentMapSync(BufferType type, size_t size)
-		: love::graphics::StreamBuffer(type, size)
+		: StreamBufferSync(type, size)
 		, vbo(0)
 		, vbo(0)
-		, gpuReadOffset(0)
 		, glMode(OpenGL::getGLBufferType(mode))
 		, glMode(OpenGL::getGLBufferType(mode))
 		, data(nullptr)
 		, data(nullptr)
 	{
 	{
@@ -273,32 +350,37 @@ public:
 		unloadVolatile();
 		unloadVolatile();
 	}
 	}
 
 
-	MapInfo map(size_t minsize) override
+	size_t getUsableSize() const override
 	{
 	{
-		if (gpuReadOffset + minsize > bufferSize * BUFFER_FRAMES)
-			gpuReadOffset = 0;
+		return bufferSize - frameGPUReadOffset;
+	}
 
 
+	MapInfo map(size_t /*minsize*/) override
+	{
 		MapInfo info;
 		MapInfo info;
-		info.size = bufferSize - (gpuReadOffset % bufferSize);
-		info.data = data + gpuReadOffset;
+		info.size = bufferSize - frameGPUReadOffset;
+		info.data = data + (frameIndex * bufferSize) + frameGPUReadOffset;
+
+		int firstSyncIndex = frameGPUReadOffset / syncSize;
+		int lastSyncIndex = (bufferSize - 1) / syncSize;
 
 
-		sync.wait(gpuReadOffset, info.size);
+		// We're mapping the full range of space left in the buffer, so we
+		// need to wait on all of it...
+		// FIXME: is it even worth it to have multiple sync objects per frame?
+		for (int i = firstSyncIndex; i <= lastSyncIndex; i++)
+			syncs[frameIndex * MAX_SYNCS_PER_FRAME + i].cpuWait();
 
 
 		return info;
 		return info;
 	}
 	}
 
 
 	size_t unmap(size_t usedsize) override
 	size_t unmap(size_t usedsize) override
 	{
 	{
-		gl.bindBuffer(mode, vbo);
-		glFlushMappedBufferRange(glMode, gpuReadOffset, usedsize);
+		size_t offset = (frameIndex * bufferSize) + frameGPUReadOffset;
 
 
-		return gpuReadOffset;
-	}
+		gl.bindBuffer(mode, vbo);
+		glFlushMappedBufferRange(glMode, offset, usedsize);
 
 
-	void markUsed(size_t usedsize) override
-	{
-		sync.lock(gpuReadOffset, usedsize);
-		gpuReadOffset += usedsize;
+		return offset;
 	}
 	}
 
 
 	ptrdiff_t getHandle() const override { return vbo; }
 	ptrdiff_t getHandle() const override { return vbo; }
@@ -317,46 +399,41 @@ public:
 		glBufferStorage(glMode, bufferSize * BUFFER_FRAMES, nullptr, storageflags);
 		glBufferStorage(glMode, bufferSize * BUFFER_FRAMES, nullptr, storageflags);
 		data = (uint8 *) glMapBufferRange(glMode, 0, bufferSize * BUFFER_FRAMES, mapflags);
 		data = (uint8 *) glMapBufferRange(glMode, 0, bufferSize * BUFFER_FRAMES, mapflags);
 
 
-		gpuReadOffset = 0;
+		frameGPUReadOffset = 0;
+		frameIndex = 0;
 
 
 		return true;
 		return true;
 	}
 	}
 
 
 	void unloadVolatile() override
 	void unloadVolatile() override
 	{
 	{
-		if (vbo == 0)
-			return;
-
-		gl.bindBuffer(mode, vbo);
-		glUnmapBuffer(glMode);
-		gl.deleteBuffer(vbo);
-		vbo = 0;
+		if (vbo != 0)
+		{
+			gl.bindBuffer(mode, vbo);
+			glUnmapBuffer(glMode);
+			gl.deleteBuffer(vbo);
+			vbo = 0;
+		}
 
 
-		sync.cleanup();
+		for (FenceSync &sync : syncs)
+			sync.cleanup();
 	}
 	}
 
 
 private:
 private:
 
 
 	GLuint vbo;
 	GLuint vbo;
-	size_t gpuReadOffset;
 	GLenum glMode;
 	GLenum glMode;
 	uint8 *data;
 	uint8 *data;
 
 
-	BufferSync sync;
-
 }; // StreamBufferPersistentMapSync
 }; // StreamBufferPersistentMapSync
 
 
 love::graphics::StreamBuffer *CreateStreamBuffer(BufferType mode, size_t size)
 love::graphics::StreamBuffer *CreateStreamBuffer(BufferType mode, size_t size)
 {
 {
 	if (gl.isCoreProfile())
 	if (gl.isCoreProfile())
 	{
 	{
-		// FIXME: This is disabled until more efficient manual syncing can be
-		// implemented.
-#if 0
 		if (GLAD_VERSION_4_4 || GLAD_ARB_buffer_storage)
 		if (GLAD_VERSION_4_4 || GLAD_ARB_buffer_storage)
 			return new StreamBufferPersistentMapSync(mode, size);
 			return new StreamBufferPersistentMapSync(mode, size);
 		else
 		else
-#endif
 			return new StreamBufferSubDataOrphan(mode, size);
 			return new StreamBufferSubDataOrphan(mode, size);
 	}
 	}
 	else
 	else