Browse Source

Refactor Buffer internals.

Allows buffers created with love.graphics.newBuffer to only allocate VRAM data. Previously they had a copy of their contents in RAM.
Also makes it easier to implement Buffers in other graphics APIs and to implement more types of Buffers in the future.
Alex Szpakowski 4 years ago
parent
commit
1f9d98263d

+ 1 - 0
CMakeLists.txt

@@ -266,6 +266,7 @@ set(LOVE_SRC_COMMON
 	src/common/Optional.h
 	src/common/pixelformat.cpp
 	src/common/pixelformat.h
+	src/common/Range.h
 	src/common/Reference.cpp
 	src/common/Reference.h
 	src/common/runtime.cpp

+ 4 - 0
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -829,6 +829,7 @@
 		FAB2D5AA1AABDD8A008224A4 /* TrueTypeRasterizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */; };
 		FAB2D5AB1AABDD8A008224A4 /* TrueTypeRasterizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */; };
 		FAB2D5AC1AABDD8A008224A4 /* TrueTypeRasterizer.h in Headers */ = {isa = PBXBuildFile; fileRef = FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */; };
+		FAB922C6257D99EF0035DAD6 /* Range.h in Headers */ = {isa = PBXBuildFile; fileRef = FAB922C3257D99EF0035DAD6 /* Range.h */; };
 		FABDA9762552448200B5C523 /* b2_joint.h in Headers */ = {isa = PBXBuildFile; fileRef = FABDA9112552448200B5C523 /* b2_joint.h */; };
 		FABDA9772552448200B5C523 /* b2_shape.h in Headers */ = {isa = PBXBuildFile; fileRef = FABDA9122552448200B5C523 /* b2_shape.h */; };
 		FABDA9782552448200B5C523 /* b2_block_allocator.h in Headers */ = {isa = PBXBuildFile; fileRef = FABDA9132552448200B5C523 /* b2_block_allocator.h */; };
@@ -1828,6 +1829,7 @@
 		FAB17BF41ABFC4B100F9BA27 /* lz4hc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lz4hc.h; sourceTree = "<group>"; };
 		FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrueTypeRasterizer.cpp; sourceTree = "<group>"; };
 		FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrueTypeRasterizer.h; sourceTree = "<group>"; };
+		FAB922C3257D99EF0035DAD6 /* Range.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Range.h; sourceTree = "<group>"; };
 		FABDA9112552448200B5C523 /* b2_joint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = b2_joint.h; sourceTree = "<group>"; };
 		FABDA9122552448200B5C523 /* b2_shape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = b2_shape.h; sourceTree = "<group>"; };
 		FABDA9132552448200B5C523 /* b2_block_allocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = b2_block_allocator.h; sourceTree = "<group>"; };
@@ -2223,6 +2225,7 @@
 				FAF1889C1E9DA834008C1479 /* Optional.h */,
 				FA9D8DCF1DEB56C3002CD881 /* pixelformat.cpp */,
 				FA9D8DD01DEB56C3002CD881 /* pixelformat.h */,
+				FAB922C3257D99EF0035DAD6 /* Range.h */,
 				FA0B790C1A958E3B000E1D17 /* Reference.cpp */,
 				FA0B790D1A958E3B000E1D17 /* Reference.h */,
 				FA0B790E1A958E3B000E1D17 /* runtime.cpp */,
@@ -4158,6 +4161,7 @@
 				FA41A3CA1C0A1F950084430C /* ASTCHandler.h in Headers */,
 				FADF54041E3D77B500012CC0 /* wrap_Text.h in Headers */,
 				FA0B7ED31A95902C000E1D17 /* wrap_ThreadModule.h in Headers */,
+				FAB922C6257D99EF0035DAD6 /* Range.h in Headers */,
 				FAC756F61E4F99B400B91289 /* Effect.h in Headers */,
 				FA0B7ADD1A958EA3000E1D17 /* gladfuncs.hpp in Headers */,
 				FAF1405D1E20934C00F898D2 /* intermediate.h in Headers */,

+ 83 - 0
src/common/Range.h

@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2006-2020 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+#include <stddef.h>
+#include <algorithm>
+#include <limits>
+
+namespace love
+{
+
+struct Range
+{
+	size_t first;
+	size_t last;
+
+	Range()
+		: first(std::numeric_limits<size_t>::max())
+		, last(0)
+	{}
+
+	Range(size_t offset, size_t size)
+		: first(offset)
+		, last(offset + size - 1)
+	{}
+
+	bool isValid() const { return first <= last; }
+
+	void invalidate()
+	{
+		first = std::numeric_limits<size_t>::max();
+		last = 0;
+	}
+
+	size_t getMin() const { return first; }
+	size_t getMax() const { return last; }
+
+	size_t getOffset() const { return first; }
+	size_t getSize() const { return (last - first) + 1; }
+
+	bool contains(const Range &other) const
+	{
+		return first <= other.first && last >= other.last;
+	}
+
+	void encapsulate(size_t index)
+	{
+		first = std::min(first, index);
+		last = std::max(last, index);
+	}
+
+	void encapsulate(size_t offset, size_t size)
+	{
+		first = std::min(first, offset);
+		last = std::max(last, offset + size - 1);
+	}
+
+	void encapsulate(const Range &other)
+	{
+		first = std::min(first, other.first);
+		last = std::max(last, other.last);
+	}
+};
+
+} // love

+ 0 - 1
src/modules/graphics/Buffer.cpp

@@ -34,7 +34,6 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	, size(size)
 	, typeFlags(settings.typeFlags)
 	, usage(settings.usage)
-	, mapFlags(settings.mapFlags)
 	, mapped(false)
 {
 	if (size == 0 && arraylength == 0)

+ 11 - 32
src/modules/graphics/Buffer.h

@@ -48,11 +48,9 @@ public:
 
 	static love::Type type;
 
-	enum MapFlags
+	enum MapType
 	{
-		MAP_NONE = 0,
-		MAP_EXPLICIT_RANGE_MODIFY = (1 << 0), // see setMappedRangeModified.
-		MAP_READ = (1 << 1),
+		MAP_WRITE_INVALIDATE,
 	};
 
 	enum TypeFlags
@@ -94,13 +92,13 @@ public:
 	struct Settings
 	{
 		TypeFlags typeFlags;
-		MapFlags mapFlags;
 		BufferUsage usage;
+		bool zeroInitialize;
 
-		Settings(uint32 typeflags, uint32 mapflags, BufferUsage usage)
+		Settings(uint32 typeflags, BufferUsage usage)
 			: typeFlags((TypeFlags)typeflags)
-			, mapFlags((MapFlags)mapflags)
 			, usage(usage)
+			, zeroInitialize(false)
 		{}
 	};
 
@@ -111,7 +109,6 @@ public:
 	TypeFlags getTypeFlags() const { return typeFlags; }
 	BufferUsage getUsage() const { return usage; }
 	bool isMapped() const { return mapped; }
-	uint32 getMapFlags() const { return mapFlags; }
 
 	size_t getArrayLength() const { return arrayLength; }
 	size_t getArrayStride() const { return arrayStride; }
@@ -121,35 +118,21 @@ public:
 	int getDataMemberIndex(const std::string &name) const;
 
 	/**
-	 * Map the Buffer to client memory.
-	 *
-	 * This can be faster for large changes to the buffer. For smaller
-	 * changes, see fill().
+	 * Map a portion of the Buffer to client memory.
 	 */
-	virtual void *map() = 0;
+	virtual void *map(MapType map, size_t offset, size_t size) = 0;
 
 	/**
 	 * Unmap a previously mapped Buffer. The buffer must be unmapped when used
 	 * to draw.
 	 */
-	virtual void unmap() = 0;
+	virtual void unmap(size_t usedoffset, size_t usedsize) = 0;
 
 	/**
-	 * Marks a range of mapped data as modified.
-	 * NOTE: Buffer::fill calls this internally for you.
-	 **/
-	virtual void setMappedRangeModified(size_t offset, size_t size) = 0;
-
-	/**
-	 * Fill a portion of the buffer with data and marks the range as modified.
+	 * Fill a portion of the buffer with data.
 	 */
 	virtual void fill(size_t offset, size_t size, const void *data) = 0;
 
-	/**
-	 * Copy the contents of this Buffer to another Buffer object.
-	 **/
-	virtual void copyTo(size_t offset, size_t size, Buffer *other, size_t otheroffset) = 0;
-
 	/**
 	 * Texel buffers may use an additional texture handle as well as a buffer
 	 * handle.
@@ -165,14 +148,12 @@ public:
 		Mapper(Buffer &buffer)
 			: buffer(buffer)
 		{
-			data = buffer.map();
+			data = buffer.map(MAP_WRITE_INVALIDATE, 0, buffer.getSize());
 		}
 
 		~Mapper()
 		{
-			if (buffer.getMapFlags() & MAP_EXPLICIT_RANGE_MODIFY)
-				buffer.setMappedRangeModified(0, buffer.getSize());
-			buffer.unmap();
+			buffer.unmap(0, buffer.getSize());
 		}
 
 		Buffer &buffer;
@@ -194,8 +175,6 @@ protected:
 
 	// Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW.
 	BufferUsage usage;
-	
-	uint32 mapFlags;
 
 	bool mapped;
 	

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

@@ -177,7 +177,7 @@ void Graphics::createQuadIndexBuffer()
 
 	size_t size = sizeof(uint16) * (LOVE_UINT16_MAX / 4) * 6;
 
-	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, 0, BUFFERUSAGE_STATIC);
+	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, BUFFERUSAGE_STATIC);
 	quadIndexBuffer = newBuffer(settings, DATAFORMAT_UINT16, nullptr, size, 0);
 
 	Buffer::Mapper map(*quadIndexBuffer);

+ 84 - 154
src/modules/graphics/Mesh.cpp

@@ -46,20 +46,21 @@ std::vector<Buffer::DataDeclaration> Mesh::getDefaultVertexFormat()
 love::Type Mesh::type("Mesh", &Drawable::type);
 
 Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
-	: vertexBuffer(nullptr)
-	, vertexCount(0)
-	, vertexStride(0)
-	, vertexScratchBuffer(nullptr)
-	, indexBuffer(nullptr)
-	, useIndexBuffer(false)
-	, indexCount(0)
-	, indexDataType(INDEX_UINT16)
-	, primitiveType(drawmode)
-	, rangeStart(-1)
-	, rangeCount(-1)
+	: primitiveType(drawmode)
 {
-	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ, usage);
-	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, data, datasize, 0), Acquire::NORETAIN);
+	try
+	{
+		vertexData = new uint8[datasize];
+	}
+	catch (std::exception &)
+	{
+		throw love::Exception("Out of memory");
+	}
+
+	memcpy(vertexData, data, datasize);
+
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage);
+	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, vertexData, datasize, 0), Acquire::NORETAIN);
 
 	vertexCount = vertexBuffer->getArrayLength();
 	vertexStride = vertexBuffer->getArrayStride();
@@ -68,27 +69,17 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &
 	setupAttachedAttributes();
 
 	indexDataType = getIndexDataTypeFromMax(vertexCount);
-
-	vertexScratchBuffer = new char[vertexStride];
 }
 
 Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
-	: vertexBuffer(nullptr)
-	, vertexCount((size_t) vertexcount)
-	, vertexStride(0)
-	, vertexScratchBuffer(nullptr)
-	, indexBuffer(nullptr)
-	, useIndexBuffer(false)
-	, indexCount(0)
+	: vertexCount((size_t) vertexcount)
 	, indexDataType(getIndexDataTypeFromMax(vertexcount))
 	, primitiveType(drawmode)
-	, rangeStart(-1)
-	, rangeCount(-1)
 {
 	if (vertexcount <= 0)
 		throw love::Exception("Invalid number of vertices (%d).", vertexcount);
 
-	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ, usage);
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage);
 	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN);
 
 	vertexStride = vertexBuffer->getArrayStride();
@@ -96,25 +87,21 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &
 
 	setupAttachedAttributes();
 
-	memset(vertexBuffer->map(), 0, vertexBuffer->getSize());
-	vertexBuffer->setMappedRangeModified(0, vertexBuffer->getSize());
-	vertexBuffer->unmap();
+	try
+	{
+		vertexData = new uint8[vertexBuffer->getSize()];
+	}
+	catch (std::exception &)
+	{
+		throw love::Exception("Out of memory");
+	}
 
-	vertexScratchBuffer = new char[vertexStride];
+	memset(vertexData, 0, vertexBuffer->getSize());
+	vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
 }
 
 Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
-	: vertexBuffer(nullptr)
-	, vertexCount(0)
-	, vertexStride(0)
-	, vertexScratchBuffer(nullptr)
-	, indexBuffer(nullptr)
-	, useIndexBuffer(false)
-	, indexCount(0)
-	, indexDataType(INDEX_UINT16)
-	, primitiveType(drawmode)
-	, rangeStart(-1)
-	, rangeCount(-1)
+	: primitiveType(drawmode)
 {
 	if (attributes.size() == 0)
 		throw love::Exception("At least one buffer attribute must be specified in this constructor.");
@@ -139,7 +126,9 @@ Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType d
 
 Mesh::~Mesh()
 {
-	delete vertexScratchBuffer;
+	delete vertexData;
+	if (indexData != nullptr)
+		free(indexData);
 }
 
 void Mesh::setupAttachedAttributes()
@@ -151,7 +140,7 @@ void Mesh::setupAttachedAttributes()
 		if (getAttachedAttributeIndex(name) != -1)
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
-		attachedAttributes.push_back({name, vertexBuffer, (int) i, STEP_PER_VERTEX, true});
+		attachedAttributes.push_back({name, vertexBuffer, nullptr, (int) i, STEP_PER_VERTEX, true});
 	}
 }
 
@@ -166,89 +155,19 @@ int Mesh::getAttachedAttributeIndex(const std::string &name) const
 	return -1;
 }
 
-void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
-{
-	if (vertindex >= vertexCount)
-		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
-
-	if (vertexBuffer.get() == nullptr)
-		throw love::Exception("setVertex can only be called on a Mesh which owns its own vertex buffer.");
-
-	size_t offset = vertindex * vertexStride;
-	size_t size = std::min(datasize, vertexStride);
-
-	uint8 *bufferdata = (uint8 *) vertexBuffer->map();
-	memcpy(bufferdata + offset, data, size);
-
-	vertexBuffer->setMappedRangeModified(offset, size);
-}
-
-size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize)
+void *Mesh::checkVertexDataOffset(size_t vertindex, size_t *byteoffset)
 {
 	if (vertindex >= vertexCount)
 		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
 
-	if (vertexBuffer.get() == nullptr)
-		throw love::Exception("getVertex can only be called on a Mesh which owns its own vertex buffer.");
+	if (vertexData == nullptr)
+		throw love::Exception("Mesh must own its own vertex buffer.");
 
 	size_t offset = vertindex * vertexStride;
-	size_t size = std::min(datasize, vertexStride);
 
-	// We're relying on map() returning read/write data... ew.
-	const uint8 *bufferdata = (const uint8 *) vertexBuffer->map();
-	memcpy(data, bufferdata + offset, size);
-
-	return size;
-}
-
-void *Mesh::getVertexScratchBuffer()
-{
-	return vertexScratchBuffer;
-}
-
-void Mesh::setVertexAttribute(size_t vertindex, int attribindex, const void *data, size_t datasize)
-{
-	if (vertindex >= vertexCount)
-		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
-
-	if (attribindex >= (int) vertexFormat.size())
-		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
-
-	if (vertexBuffer.get() == nullptr)
-		throw love::Exception("setVertexAttribute can only be called on a Mesh which owns its own vertex buffer.");
-
-	const auto &member = vertexFormat[attribindex];
-
-	size_t offset = vertindex * vertexStride + member.offset;
-	size_t size = std::min(datasize, member.info.size);
-
-	uint8 *bufferdata = (uint8 *) vertexBuffer->map();
-	memcpy(bufferdata + offset, data, size);
-
-	vertexBuffer->setMappedRangeModified(offset, size);
-}
-
-size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize)
-{
-	if (vertindex >= vertexCount)
-		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
-
-	if (attribindex >= (int) vertexFormat.size())
-		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
-
-	if (vertexBuffer.get() == nullptr)
-		throw love::Exception("getVertexAttribute can only be called on a Mesh which owns its own vertex buffer.");
-
-	const auto &member = vertexFormat[attribindex];
-
-	size_t offset = vertindex * vertexStride + member.offset;
-	size_t size = std::min(datasize, member.info.size);
-
-	// We're relying on map() returning read/write data... ew.
-	const uint8 *bufferdata = (const uint8 *) vertexBuffer->map();
-	memcpy(data, bufferdata + offset, size);
-
-	return size;
+	if (byteoffset != nullptr)
+		*byteoffset = offset;
+	return vertexData + offset;
 }
 
 size_t Mesh::getVertexCount() const
@@ -289,7 +208,7 @@ bool Mesh::isAttributeEnabled(const std::string &name) const
 	return attachedAttributes[index].enabled;
 }
 
-void Mesh::attachAttribute(const std::string &name, Buffer *buffer, const std::string &attachname, AttributeStep step)
+void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, AttributeStep step)
 {
 	if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
 		throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
@@ -309,6 +228,7 @@ void Mesh::attachAttribute(const std::string &name, Buffer *buffer, const std::s
 
 	newattrib.name = name;
 	newattrib.buffer = buffer;
+	newattrib.mesh = mesh;
 	newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true;
 	newattrib.indexInBuffer = buffer->getDataMemberIndex(attachname);
 	newattrib.step = step;
@@ -331,7 +251,7 @@ bool Mesh::detachAttribute(const std::string &name)
 	attachedAttributes.erase(attachedAttributes.begin() + index);
 
 	if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(name) != -1)
-		attachAttribute(name, vertexBuffer, name);
+		attachAttribute(name, vertexBuffer, nullptr, name);
 
 	return true;
 }
@@ -341,36 +261,49 @@ const std::vector<Mesh::BufferAttribute> &Mesh::getAttachedAttributes() const
 	return attachedAttributes;
 }
 
-void *Mesh::mapVertexData()
+void *Mesh::getVertexData() const
 {
-	return vertexBuffer.get() != nullptr ? vertexBuffer->map() : nullptr;
+	return vertexData;
 }
 
-void Mesh::unmapVertexData(size_t modifiedoffset, size_t modifiedsize)
+void Mesh::setVertexDataModified(size_t offset, size_t size)
 {
-	if (!vertexBuffer.get())
-		return;
-
-	vertexBuffer->setMappedRangeModified(modifiedoffset, modifiedsize);
-	vertexBuffer->unmap();
+	if (vertexData != nullptr)
+		modifiedVertexData.encapsulate(offset, size);
 }
 
 void Mesh::flush()
 {
-	if (vertexBuffer.get())
-		vertexBuffer->unmap();
+	if (vertexBuffer.get() && vertexData != nullptr && modifiedVertexData.isValid())
+	{
+		if (vertexBuffer->getUsage() == BUFFERUSAGE_STREAM)
+		{
+			vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
+		}
+		else
+		{
+			size_t offset = modifiedVertexData.getOffset();
+			size_t size = modifiedVertexData.getSize();
+			vertexBuffer->fill(offset, size, vertexData + offset);
+		}
 
-	if (indexBuffer.get())
-		indexBuffer->unmap();
+		modifiedVertexData.invalidate();
+	}
+
+	if (indexDataModified && indexData != nullptr && indexBuffer != nullptr)
+	{
+		indexBuffer->fill(0, indexBuffer->getSize(), indexData);
+		indexDataModified = false;
+	}
 }
 
 /**
  * Copies index data from a vector to a mapped index buffer.
  **/
 template <typename T>
-static void copyToIndexBuffer(const std::vector<uint32> &indices, Buffer::Mapper &buffermap, size_t maxval)
+static void copyToIndexBuffer(const std::vector<uint32> &indices, void *data, size_t maxval)
 {
-	T *elems = (T *) buffermap.data;
+	T *elems = (T *) data;
 
 	for (size_t i = 0; i < indices.size(); i++)
 	{
@@ -395,7 +328,7 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
-		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage);
+		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, usage);
 		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
 	}
 
@@ -405,17 +338,15 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	if (!indexBuffer || indexCount == 0)
 		return;
 
-	Buffer::Mapper ibomap(*indexBuffer);
-
 	// Fill the buffer with the index values from the vector.
 	switch (datatype)
 	{
 	case INDEX_UINT16:
-		copyToIndexBuffer<uint16>(map, ibomap, maxval);
+		copyToIndexBuffer<uint16>(map, indexData, maxval);
 		break;
 	case INDEX_UINT32:
 	default:
-		copyToIndexBuffer<uint32>(map, ibomap, maxval);
+		copyToIndexBuffer<uint32>(map, indexData, maxval);
 		break;
 	}
 
@@ -430,7 +361,7 @@ void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasiz
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
 		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
-		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage);
+		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, usage);
 		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
 	}
 
@@ -470,24 +401,18 @@ bool Mesh::getVertexMap(std::vector<uint32> &map) const
 	map.clear();
 	map.reserve(indexCount);
 
-	if (!indexBuffer || indexCount == 0)
+	if (indexData == nullptr || indexCount == 0)
 		return true;
 
-	if ((indexBuffer->getMapFlags() & Buffer::MAP_READ) == 0)
-		return false;
-
-	// We unmap the buffer in Mesh::draw, Mesh::setVertexMap, and Mesh::flush.
-	void *buffer = indexBuffer->map();
-
 	// Fill the vector from the buffer.
 	switch (indexDataType)
 	{
 	case INDEX_UINT16:
-		copyFromIndexBuffer<uint16>(buffer, indexCount, map);
+		copyFromIndexBuffer<uint16>(indexData, indexCount, map);
 		break;
 	case INDEX_UINT32:
 	default:
-		copyFromIndexBuffer<uint32>(buffer, indexCount, map);
+		copyFromIndexBuffer<uint32>(indexData, indexCount, map);
 		break;
 	}
 
@@ -507,6 +432,12 @@ void Mesh::setIndexBuffer(Buffer *buffer)
 
 	if (buffer != nullptr)
 		indexDataType = getIndexDataType(buffer->getDataMember(0).decl.format);
+
+	if (indexData != nullptr)
+	{
+		free(indexData);
+		indexData = nullptr;
+	}
 }
 
 Buffer *Mesh::getIndexBuffer() const
@@ -583,6 +514,8 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 
 	gfx->flushBatchedDraws();
 
+	flush();
+
 	if (Shader::isDefaultActive())
 		Shader::attachDefault(Shader::STANDARD_DEFAULT);
 
@@ -612,8 +545,8 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 
 		if (attributeindex >= 0)
 		{
-			// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-			buffer->unmap();
+			if (attrib.mesh.get())
+				attrib.mesh->flush();
 
 			const auto &member = buffer->getDataMember(attrib.indexInBuffer);
 
@@ -637,9 +570,6 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 
 	if (useIndexBuffer && indexBuffer != nullptr && indexCount > 0)
 	{
-		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
-		indexBuffer->unmap();
-
 		Graphics::DrawIndexedCommand cmd(&attributes, &buffers, indexBuffer);
 
 		cmd.primitiveType = primitiveType;

+ 23 - 28
src/modules/graphics/Mesh.h

@@ -25,6 +25,7 @@
 #include "common/int.h"
 #include "common/math.h"
 #include "common/StringMap.h"
+#include "common/Range.h"
 #include "Drawable.h"
 #include "Texture.h"
 #include "vertex.h"
@@ -54,6 +55,7 @@ public:
 	{
 		std::string name;
 		StrongRef<Buffer> buffer;
+		StrongRef<Mesh> mesh;
 		int indexInBuffer;
 		AttributeStep step;
 		bool enabled;
@@ -68,21 +70,10 @@ public:
 	virtual ~Mesh();
 
 	/**
-	 * Sets the values of all attributes at a specific vertex index in the Mesh.
-	 * The size of the data must be less than or equal to the total size of all
-	 * vertex attributes.
+	 * Validates a vertex index and whether the Mesh has its own vertex buffer,
+	 * and returns a pointer to the vertex data at the given vertex index.
 	 **/
-	void setVertex(size_t vertindex, const void *data, size_t datasize);
-	size_t getVertex(size_t vertindex, void *data, size_t datasize);
-	void *getVertexScratchBuffer();
-
-	/**
-	 * Sets the values for a single attribute at a specific vertex index in the
-	 * Mesh. The size of the data must be less than or equal to the size of the
-	 * attribute.
-	 **/
-	void setVertexAttribute(size_t vertindex, int attribindex, const void *data, size_t datasize);
-	size_t getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize);
+	void *checkVertexDataOffset(size_t vertindex, size_t *byteoffset);
 
 	/**
 	 * Gets the total number of vertices that can be used when drawing the Mesh.
@@ -114,13 +105,16 @@ public:
 	/**
 	 * Attaches a vertex attribute from another vertex buffer to this Mesh. The
 	 * attribute will be used when drawing this Mesh.
+	 * Attributes from other Meshes should also pass in the Mesh as an argument,
+	 * to make sure this Mesh knows to flush the passed in Mesh's data to its
+	 * buffer when drawing.
 	 **/
-	void attachAttribute(const std::string &name, Buffer *buffer, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX);
+	void attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX);
 	bool detachAttribute(const std::string &name);
 	const std::vector<BufferAttribute> &getAttachedAttributes() const;
 
-	void *mapVertexData();
-	void unmapVertexData(size_t modifiedoffset = 0, size_t modifiedsize = -1);
+	void *getVertexData() const;
+	void setVertexDataModified(size_t offset, size_t size);
 
 	/**
 	 * Flushes all modified data to the GPU.
@@ -197,23 +191,24 @@ private:
 
 	// Vertex buffer, for the vertex data.
 	StrongRef<Buffer> vertexBuffer;
-	size_t vertexCount;
-	size_t vertexStride;
+	uint8 *vertexData = nullptr;
+	Range modifiedVertexData = Range();
 
-	// Block of memory whose size is at least as large as a single vertex. Helps
-	// avoid memory allocations when using Mesh::setVertex etc.
-	char *vertexScratchBuffer;
+	size_t vertexCount = 0;
+	size_t vertexStride = 0;
 
 	// Index buffer, for the vertex map.
 	StrongRef<Buffer> indexBuffer;
-	bool useIndexBuffer;
-	size_t indexCount;
-	IndexDataType indexDataType;
+	uint8 *indexData = nullptr;
+	bool indexDataModified = false;
+	bool useIndexBuffer = false;
+	size_t indexCount = 0;
+	IndexDataType indexDataType = INDEX_UINT16;
 
-	PrimitiveType primitiveType;
+	PrimitiveType primitiveType = PRIMITIVE_TRIANGLES;
 
-	int rangeStart;
-	int rangeCount;
+	int rangeStart = -1;
+	int rangeCount = -1;
 
 	StrongRef<Texture> texture;
 

+ 3 - 3
src/modules/graphics/ParticleSystem.cpp

@@ -191,7 +191,7 @@ void ParticleSystem::createBuffers(size_t size)
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 
 		size_t bytes = sizeof(Vertex) * size * 4;
-		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, 0, BUFFERUSAGE_STREAM);
+		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_STREAM);
 		auto decl = Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
 		buffer = gfx->newBuffer(settings, decl, nullptr, bytes, 0);
 	}
@@ -1043,7 +1043,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 	const Vector2 *positions = texture->getQuad()->getVertexPositions();
 	const Vector2 *texcoords = texture->getQuad()->getVertexTexCoords();
 
-	Vertex *pVerts = (Vertex *) buffer->map();
+	Vertex *pVerts = (Vertex *) buffer->map(Buffer::MAP_WRITE_INVALIDATE, 0, buffer->getSize());
 	Particle *p = pHead;
 
 	bool useQuads = !quads.empty();
@@ -1079,7 +1079,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 		p = p->next;
 	}
 
-	buffer->unmap();
+	buffer->unmap(0, pCount * sizeof(Vertex) * 4);
 
 	Graphics::TempTransform transform(gfx, m);
 

+ 17 - 21
src/modules/graphics/SpriteBatch.cpp

@@ -48,8 +48,7 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage
 	, colorf(1.0f, 1.0f, 1.0f, 1.0f)
 	, array_buf(nullptr)
 	, vertex_data(nullptr)
-	, modified_sprite_first(LOVE_INT32_MAX)
-	, modified_sprite_last(0)
+	, modified_sprites()
 	, range_start(-1)
 	, range_count(-1)
 {
@@ -74,7 +73,7 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage
 
 	memset(vertex_data, 0, vertex_size);
 
-	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, usage);
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage);
 	auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
 
 	array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN);
@@ -118,8 +117,7 @@ int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
 		verts[i].color = color;
 	}
 
-	modified_sprite_first = std::min(modified_sprite_first, spriteindex);
-	modified_sprite_last = std::max(modified_sprite_last, spriteindex);
+	modified_sprites.encapsulate(spriteindex);
 
 	// Increment counter.
 	if (index == -1)
@@ -165,8 +163,7 @@ int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
 		verts[i].color = color;
 	}
 
-	modified_sprite_first = std::min(modified_sprite_first, spriteindex);
-	modified_sprite_last = std::max(modified_sprite_last, spriteindex);
+	modified_sprites.encapsulate(spriteindex);
 
 	// Increment counter.
 	if (index == -1)
@@ -183,19 +180,17 @@ void SpriteBatch::clear()
 
 void SpriteBatch::flush()
 {
-	if (modified_sprite_first <= modified_sprite_last)
+	if (modified_sprites.isValid())
 	{
-		size_t offset = modified_sprite_first * vertex_stride * 4;
-		size_t size = (modified_sprite_last - modified_sprite_first) * vertex_stride * 4;
+		size_t offset = modified_sprites.getOffset() * vertex_stride * 4;
+		size_t size = modified_sprites.getSize() * vertex_stride * 4;
 
-		// TODO: switch this to fill() once it gets cleaned up.
-		memcpy(((uint8 *)array_buf->map() + offset), vertex_data + offset, size);
+		if (array_buf->getUsage() == BUFFERUSAGE_STREAM)
+			array_buf->fill(0, array_buf->getSize(), vertex_data);
+		else
+			array_buf->fill(offset, size, vertex_data + offset);
 
-		array_buf->setMappedRangeModified(offset, size);
-		array_buf->unmap();
-
-		modified_sprite_first = LOVE_INT32_MAX;
-		modified_sprite_last = 0;
+		modified_sprites.invalidate();
 	}
 }
 
@@ -249,7 +244,7 @@ void SpriteBatch::setBufferSize(int newsize)
 		throw love::Exception("Out of memory.");
 
 	auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-	Buffer::Settings settings(array_buf->getTypeFlags(), array_buf->getMapFlags(), array_buf->getUsage());
+	Buffer::Settings settings(array_buf->getTypeFlags(), array_buf->getUsage());
 	auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
 
 	array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN);
@@ -267,7 +262,7 @@ int SpriteBatch::getBufferSize() const
 	return size;
 }
 
-void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer)
+void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh)
 {
 	if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
 		throw love::Exception("GraphicsBuffer must be created with vertex buffer support to be used as a SpriteBatch vertex attribute.");
@@ -288,6 +283,7 @@ void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer)
 		throw love::Exception("The specified Buffer does not have a vertex attribute named '%s'", name.c_str());
 
 	newattrib.buffer = buffer;
+	newattrib.mesh = mesh;
 
 	attached_attributes[name] = newattrib;
 }
@@ -371,8 +367,8 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 
 		if (attributeindex >= 0)
 		{
-			// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-			buffer->unmap();
+			if (it.second.mesh.get())
+				it.second.mesh->flush();
 
 			const auto &member = buffer->getDataMember(it.second.index);
 

+ 8 - 4
src/modules/graphics/SpriteBatch.h

@@ -30,6 +30,7 @@
 #include "common/math.h"
 #include "common/Matrix.h"
 #include "common/Color.h"
+#include "common/Range.h"
 #include "Drawable.h"
 #include "Mesh.h"
 #include "vertex.h"
@@ -90,10 +91,13 @@ public:
 	int getBufferSize() const;
 
 	/**
-	 * Attaches a specific vertex attribute from a Mesh to this SpriteBatch.
+	 * Attaches a specific vertex attribute from a Buffer to this SpriteBatch.
 	 * The vertex attribute will be used when drawing the SpriteBatch.
+	 * If the attribute comes from a Mesh, it should be given as an argument as
+	 * well, to make sure the SpriteBatch flushes its data to its Buffer when
+	 * the SpriteBatch is drawn.
 	 **/
-	void attachAttribute(const std::string &name, Buffer *buffer);
+	void attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh);
 
 	void setDrawRange(int start, int count);
 	void setDrawRange();
@@ -107,6 +111,7 @@ private:
 	struct AttachedAttribute
 	{
 		StrongRef<Buffer> buffer;
+		StrongRef<Mesh> mesh;
 		int index;
 	};
 
@@ -134,8 +139,7 @@ private:
 	StrongRef<love::graphics::Buffer> array_buf;
 	uint8 *vertex_data;
 
-	int modified_sprite_first;
-	int modified_sprite_last;
+	Range modified_sprites;
 
 	std::unordered_map<std::string, AttachedAttribute> attached_attributes;
 	

+ 80 - 56
src/modules/graphics/Text.cpp

@@ -33,14 +33,18 @@ love::Type Text::type("Text", &Drawable::type);
 Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
 	: font(font)
 	, vertexAttributes(Font::vertexFormat, 0)
-	, vert_offset(0)
-	, texture_cache_id((uint32) -1)
+	, vertexData(nullptr)
+	, modifiedVertices()
+	, vertOffset(0)
+	, textureCacheID((uint32) -1)
 {
 	set(text);
 }
 
 Text::~Text()
 {
+	if (vertexData != nullptr)
+		free(vertexData);
 }
 
 void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset)
@@ -49,32 +53,40 @@ void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t
 	size_t datasize = vertices.size() * sizeof(Font::GlyphVertex);
 
 	// If we haven't created a VBO or the vertices are too big, make a new one.
-	if (datasize > 0 && (!vertex_buffer || (offset + datasize) > vertex_buffer->getSize()))
+	if (datasize > 0 && (!vertexBuffer || (offset + datasize) > vertexBuffer->getSize()))
 	{
 		// Make it bigger than necessary to reduce potential future allocations.
 		size_t newsize = size_t((offset + datasize) * 1.5);
 
-		if (vertex_buffer != nullptr)
-			newsize = std::max(size_t(vertex_buffer->getSize() * 1.5), newsize);
+		if (vertexBuffer != nullptr)
+			newsize = std::max(size_t(vertexBuffer->getSize() * 1.5), newsize);
 
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, 0, BUFFERUSAGE_DYNAMIC);
+
+		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_DYNAMIC);
 		auto decl = Buffer::getCommonFormatDeclaration(Font::vertexFormat);
-		Buffer *new_buffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0);
+		Buffer *newbuffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0);
+
+		void *newdata = nullptr;
+		if (vertexData != nullptr)
+			newdata = realloc(vertexData, newsize);
+		else
+			newdata = malloc(newsize);
 
-		if (vertex_buffer != nullptr)
-			vertex_buffer->copyTo(0, vertex_buffer->getSize(), new_buffer, 0);
+		if (newdata == nullptr)
+			throw love::Exception("Out of memory.");
+		else
+			vertexData = (uint8 *) newdata;
 
-		vertex_buffer = new_buffer;
+		vertexBuffer = newbuffer;
 
-		vertexBuffers.set(0, vertex_buffer, 0);
+		vertexBuffers.set(0, vertexBuffer, 0);
 	}
 
-	if (vertex_buffer != nullptr && datasize > 0)
+	if (vertexData != nullptr && datasize > 0)
 	{
-		uint8 *bufferdata = (uint8 *) vertex_buffer->map();
-		memcpy(bufferdata + offset, &vertices[0], datasize);
-		// We unmap when we draw, to avoid unnecessary full map()/unmap() calls.
+		memcpy(vertexData + offset, &vertices[0], datasize);
+		modifiedVertices.encapsulate(offset, datasize);
 	}
 }
 
@@ -82,85 +94,85 @@ void Text::regenerateVertices()
 {
 	// If the font's texture cache was invalidated then we need to recreate the
 	// text's vertices, since glyph texcoords might have changed.
-	if (font->getTextureCacheID() != texture_cache_id)
+	if (font->getTextureCacheID() != textureCacheID)
 	{
-		std::vector<TextData> textdata = text_data;
+		std::vector<TextData> textdata = textData;
 
 		clear();
 
 		for (const TextData &t : textdata)
 			addTextData(t);
 
-		texture_cache_id = font->getTextureCacheID();
+		textureCacheID = font->getTextureCacheID();
 	}
 }
 
 void Text::addTextData(const TextData &t)
 {
 	std::vector<Font::GlyphVertex> vertices;
-	std::vector<Font::DrawCommand> new_commands;
+	std::vector<Font::DrawCommand> newcommands;
 
-	Font::TextInfo text_info;
+	Font::TextInfo textinfo;
 
 	Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f);
 
 	// We only have formatted text if the align mode is valid.
 	if (t.align == Font::ALIGN_MAX_ENUM)
-		new_commands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &text_info);
+		newcommands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo);
 	else
-		new_commands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &text_info);
+		newcommands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &textinfo);
 
-	size_t voffset = vert_offset;
+	size_t voffset = vertOffset;
 
 	// Must be before the early exit below.
-	if (!t.append_vertices)
+	if (!t.appendVertices)
 	{
 		voffset = 0;
-		vert_offset = 0;
-		draw_commands.clear();
-		text_data.clear();
+		vertOffset = 0;
+		drawCommands.clear();
+		textData.clear();
 	}
 
 	if (vertices.empty())
 		return;
 
-	if (t.use_matrix)
+	if (t.useMatrix)
 		t.matrix.transformXY(&vertices[0], &vertices[0], (int) vertices.size());
 
 	uploadVertices(vertices, voffset);
 
-	if (!new_commands.empty())
+	if (!newcommands.empty())
 	{
 		// The start vertex should be adjusted to account for the vertex offset.
-		for (Font::DrawCommand &cmd : new_commands)
+		for (Font::DrawCommand &cmd : newcommands)
 			cmd.startvertex += (int) voffset;
 
-		auto firstcmd = new_commands.begin();
+		auto firstcmd = newcommands.begin();
 
 		// If the first draw command in the new list has the same texture as the
 		// last one in the existing list we're building and its vertices are
 		// in-order, we can combine them (saving a draw call.)
-		if (!draw_commands.empty())
+		if (!drawCommands.empty())
 		{
-			auto prevcmd = draw_commands.back();
+			auto prevcmd = drawCommands.back();
 			if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
 			{
-				draw_commands.back().vertexcount += firstcmd->vertexcount;
+				drawCommands.back().vertexcount += firstcmd->vertexcount;
 				++firstcmd;
 			}
 		}
 
 		// Append the new draw commands to the list we're building.
-		draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end());
+		drawCommands.insert(drawCommands.end(), firstcmd, newcommands.end());
 	}
 
-	vert_offset = voffset + vertices.size();
+	vertOffset = voffset + vertices.size();
 
-	text_data.push_back(t);
-	text_data.back().text_info = text_info;
+	textData.push_back(t);
+	textData.back().textInfo = textinfo;
 
 	// Font::generateVertices can invalidate the font's texture cache.
-	if (font->getTextureCacheID() != texture_cache_id)
+	if (font->getTextureCacheID() != textureCacheID)
 		regenerateVertices();
 }
 
@@ -192,15 +204,15 @@ int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::A
 
 	addTextData({codepoints, wrap, align, {}, true, true, m});
 
-	return (int) text_data.size() - 1;
+	return (int) textData.size() - 1;
 }
 
 void Text::clear()
 {
-	text_data.clear();
-	draw_commands.clear();
-	texture_cache_id = font->getTextureCacheID();
-	vert_offset = 0;
+	textData.clear();
+	drawCommands.clear();
+	textureCacheID = font->getTextureCacheID();
+	vertOffset = 0;
 }
 
 void Text::setFont(Font *f)
@@ -209,7 +221,7 @@ void Text::setFont(Font *f)
 	
 	// Invalidate the texture cache ID since the font is different. We also have
 	// to re-upload all the vertices based on the new font's textures.
-	texture_cache_id = (uint32) -1;
+	textureCacheID = (uint32) -1;
 	regenerateVertices();
 }
 
@@ -221,28 +233,28 @@ Font *Text::getFont() const
 int Text::getWidth(int index) const
 {
 	if (index < 0)
-		index = std::max((int) text_data.size() - 1, 0);
+		index = std::max((int) textData.size() - 1, 0);
 
-	if (index >= (int) text_data.size())
+	if (index >= (int) textData.size())
 		return 0;
 
-	return text_data[index].text_info.width;
+	return textData[index].textInfo.width;
 }
 
 int Text::getHeight(int index) const
 {
 	if (index < 0)
-		index = std::max((int) text_data.size() - 1, 0);
+		index = std::max((int) textData.size() - 1, 0);
 
-	if (index >= (int) text_data.size())
+	if (index >= (int) textData.size())
 		return 0;
 
-	return text_data[index].text_info.height;
+	return textData[index].textInfo.height;
 }
 
 void Text::draw(Graphics *gfx, const Matrix4 &m)
 {
-	if (vertex_buffer == nullptr || draw_commands.empty())
+	if (vertexBuffer == nullptr || vertexData == nullptr || drawCommands.empty())
 		return;
 
 	gfx->flushBatchedDraws();
@@ -254,18 +266,30 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 		Shader::current->checkMainTextureType(TEXTURE_2D, false);
 
 	// Re-generate the text if the Font's texture cache was invalidated.
-	if (font->getTextureCacheID() != texture_cache_id)
+	if (font->getTextureCacheID() != textureCacheID)
 		regenerateVertices();
 
 	int totalverts = 0;
-	for (const Font::DrawCommand &cmd : draw_commands)
+	for (const Font::DrawCommand &cmd : drawCommands)
 		totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
 
-	vertex_buffer->unmap(); // Make sure all pending data is flushed to the GPU.
+	// Make sure all pending data is uploaded to the GPU.
+	if (modifiedVertices.isValid())
+	{
+		size_t offset = modifiedVertices.getOffset();
+		size_t size = modifiedVertices.getSize();
+
+		if (vertexBuffer->getUsage() == BUFFERUSAGE_STREAM)
+			vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
+		else
+			vertexBuffer->fill(offset, size, vertexData + offset);
+
+		modifiedVertices.invalidate();
+	}
 
 	Graphics::TempTransform transform(gfx, m);
 
-	for (const Font::DrawCommand &cmd : draw_commands)
+	for (const Font::DrawCommand &cmd : drawCommands)
 		gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture);
 }
 

+ 11 - 8
src/modules/graphics/Text.h

@@ -22,6 +22,7 @@
 
 // LOVE
 #include "common/config.h"
+#include "common/Range.h"
 #include "Drawable.h"
 #include "Font.h"
 #include "Buffer.h"
@@ -73,9 +74,9 @@ private:
 		Font::ColoredCodepoints codepoints;
 		float wrap;
 		Font::AlignMode align;
-		Font::TextInfo text_info;
-		bool use_matrix;
-		bool append_vertices;
+		Font::TextInfo textInfo;
+		bool useMatrix;
+		bool appendVertices;
 		Matrix4 matrix;
 	};
 
@@ -88,16 +89,18 @@ private:
 	VertexAttributes vertexAttributes;
 	BufferBindings vertexBuffers;
 
-	StrongRef<Buffer> vertex_buffer;
+	StrongRef<Buffer> vertexBuffer;
+	uint8 *vertexData;
+	Range modifiedVertices;
 
-	std::vector<Font::DrawCommand> draw_commands;
+	std::vector<Font::DrawCommand> drawCommands;
 
-	std::vector<TextData> text_data;
+	std::vector<TextData> textData;
 
-	size_t vert_offset;
+	size_t vertOffset;
 	
 	// Used so we know when the font's texture cache is invalidated.
-	uint32 texture_cache_id;
+	uint32 textureCacheID;
 	
 }; // Text
 

+ 84 - 114
src/modules/graphics/opengl/Buffer.cpp

@@ -22,6 +22,7 @@
 
 #include "common/Exception.h"
 #include "graphics/vertex.h"
+#include "Graphics.h"
 
 #include <cstdlib>
 #include <cstring>
@@ -80,22 +81,26 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st
 
 	target = OpenGL::getGLBufferType(mapType);
 
-	try
-	{
-		memoryMap = new char[size];
-	}
-	catch (std::bad_alloc &)
+	if (usage == BUFFERUSAGE_STREAM)
+		ownsMemoryMap = true;
+
+	std::vector<uint8> emptydata;
+	if (settings.zeroInitialize && data == nullptr)
 	{
-		throw love::Exception("Out of memory.");
+		try
+		{
+			emptydata.resize(getSize());
+			data = emptydata.data();
+		}
+		catch (std::exception &)
+		{
+			data = nullptr;
+		}
 	}
 
-	if (data != nullptr)
-		memcpy(memoryMap, data, size);
-
-	if (!load(data != nullptr))
+	if (!load(data))
 	{
 		unloadVolatile();
-		delete[] memoryMap;
 		throw love::Exception("Could not create buffer (out of VRAM?)");
 	}
 }
@@ -103,7 +108,8 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st
 Buffer::~Buffer()
 {
 	unloadVolatile();
-	delete[] memoryMap;
+	if (memoryMap != nullptr && ownsMemoryMap)
+		free(memoryMap);
 }
 
 bool Buffer::loadVolatile()
@@ -111,7 +117,7 @@ bool Buffer::loadVolatile()
 	if (buffer != 0)
 		return true;
 
-	return load(true);
+	return load(nullptr);
 }
 
 void Buffer::unloadVolatile()
@@ -125,7 +131,7 @@ void Buffer::unloadVolatile()
 	texture = 0;
 }
 
-bool Buffer::load(bool restore)
+bool Buffer::load(const void *initialdata)
 {
 	while (glGetError() != GL_NO_ERROR)
 		/* Clear the error buffer. */;
@@ -133,11 +139,8 @@ bool Buffer::load(bool restore)
 	glGenBuffers(1, &buffer);
 	gl.bindBuffer(mapType, buffer);
 
-	// Copy the old buffer only if 'restore' was requested.
-	const GLvoid *src = restore ? memoryMap : nullptr;
-
-	// Note that if 'src' is '0', no data will be copied.
-	glBufferData(target, (GLsizeiptr) getSize(), src, OpenGL::getGLBufferUsage(getUsage()));
+	// initialdata can be null.
+	glBufferData(target, (GLsizeiptr) getSize(), initialdata, OpenGL::getGLBufferUsage(getUsage()));
 
 	if (getTypeFlags() & TYPEFLAG_TEXEL)
 	{
@@ -150,137 +153,104 @@ bool Buffer::load(bool restore)
 	return (glGetError() == GL_NO_ERROR);
 }
 
-void *Buffer::map()
-{
-	if (mapped)
-		return memoryMap;
-
-	mapped = true;
-
-	modifiedOffset = 0;
-	modifiedSize = 0;
-	isMappedDataModified = false;
-
-	return memoryMap;
-}
-
-void Buffer::unmapStatic(size_t offset, size_t size)
+void *Buffer::map(MapType /*map*/, size_t offset, size_t size)
 {
 	if (size == 0)
-		return;
+		return nullptr;
 
-	// Upload the mapped data to the buffer.
-	gl.bindBuffer(mapType, buffer);
-	glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memoryMap + offset);
-}
+	Range r(offset, size);
 
-void Buffer::unmapStream()
-{
-	GLenum glusage = OpenGL::getGLBufferUsage(getUsage());
+	if (!Range(0, getSize()).contains(r))
+		return nullptr;
 
-	// "orphan" current buffer to avoid implicit synchronisation on the GPU:
-	// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
-	gl.bindBuffer(mapType, buffer);
-	glBufferData(target, (GLsizeiptr) getSize(), nullptr, glusage);
+	char *data = nullptr;
 
-#if LOVE_WINDOWS
-	// TODO: Verify that this codepath is a useful optimization.
-	if (gl.getVendor() == OpenGL::VENDOR_INTEL)
-		glBufferData(target, (GLsizeiptr) getSize(), memoryMap, glusage);
-	else
-#endif
-		glBufferSubData(target, 0, (GLsizeiptr) getSize(), memoryMap);
-}
-
-void Buffer::unmap()
-{
-	if (!mapped)
-		return;
-
-	mapped = false;
-
-	if ((mapFlags & MAP_EXPLICIT_RANGE_MODIFY) != 0)
+	if (ownsMemoryMap)
 	{
-		if (!isMappedDataModified)
-			return;
-
-		modifiedOffset = std::min(modifiedOffset, getSize() - 1);
-		modifiedSize = std::min(modifiedSize, getSize() - modifiedOffset);
+		if (memoryMap == nullptr)
+			memoryMap = (char *) malloc(getSize());
+		data = memoryMap;
 	}
 	else
 	{
-		modifiedOffset = 0;
-		modifiedSize = getSize();
+		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+		data = (char *) gfx->getBufferMapMemory(size);
 	}
 
-	if (modifiedSize > 0)
+	if (data != nullptr)
 	{
-		switch (getUsage())
-		{
-		case BUFFERUSAGE_STATIC:
-			unmapStatic(modifiedOffset, modifiedSize);
-			break;
-		case BUFFERUSAGE_STREAM:
-			unmapStream();
-			break;
-		case BUFFERUSAGE_DYNAMIC:
-		default:
-			// It's probably more efficient to treat it like a streaming buffer if
-			// at least a third of its contents have been modified during the map().
-			if (modifiedSize >= getSize() / 3)
-				unmapStream();
-			else
-				unmapStatic(modifiedOffset, modifiedSize);
-			break;
-		}
+		mapped = true;
+		mappedRange = r;
+		if (!ownsMemoryMap)
+			memoryMap = data;
 	}
 
-	modifiedOffset = 0;
-	modifiedSize = 0;
+	return data;
 }
 
-void Buffer::setMappedRangeModified(size_t offset, size_t modifiedsize)
+void Buffer::unmap(size_t usedoffset, size_t usedsize)
 {
-	if (!mapped || !(mapFlags & MAP_EXPLICIT_RANGE_MODIFY))
+	Range r(usedoffset, usedsize);
+
+	if (!mapped || !mappedRange.contains(r))
 		return;
 
-	if (!isMappedDataModified)
+	mapped = false;
+
+	// Orphan optimization - see fill().
+	if (usage != BUFFERUSAGE_STATIC && mappedRange.first == 0 && mappedRange.getSize() == getSize())
 	{
-		modifiedOffset = offset;
-		modifiedSize = modifiedsize;
-		isMappedDataModified = true;
-		return;
+		usedoffset = 0;
+		usedsize = getSize();
 	}
 
-	// We're being conservative right now by internally marking the whole range
-	// from the start of section a to the end of section b as modified if both
-	// a and b are marked as modified.
+	char *data = memoryMap + (usedoffset - mappedRange.getOffset());
 
-	size_t oldrangeend = modifiedOffset + modifiedSize;
-	modifiedOffset = std::min(modifiedOffset, offset);
+	fill(usedoffset, usedsize, data);
 
-	size_t newrangeend = std::max(offset + modifiedsize, oldrangeend);
-	modifiedSize = newrangeend - modifiedOffset;
+	if (!ownsMemoryMap)
+	{
+		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+		gfx->releaseBufferMapMemory(memoryMap);
+		memoryMap = nullptr;
+	}
 }
 
 void Buffer::fill(size_t offset, size_t size, const void *data)
 {
-	memcpy(memoryMap + offset, data, size);
+	if (size == 0)
+		return;
 
-	if (mapped)
-		setMappedRangeModified(offset, size);
-	else
+	size_t buffersize = getSize();
+
+	if (!Range(0, buffersize).contains(Range(offset, size)))
+		return;
+
+	GLenum glusage = OpenGL::getGLBufferUsage(usage);
+
+	gl.bindBuffer(mapType, buffer);
+
+	if (usage != BUFFERUSAGE_STATIC && size == buffersize)
 	{
+		// "orphan" current buffer to avoid implicit synchronisation on the GPU:
+		// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
 		gl.bindBuffer(mapType, buffer);
+		glBufferData(target, (GLsizeiptr) buffersize, nullptr, glusage);
+
+#if LOVE_WINDOWS
+		// TODO: Verify that this codepath is a useful optimization.
+		if (gl.getVendor() == OpenGL::VENDOR_INTEL)
+			glBufferData(target, (GLsizeiptr) buffersize, data, glusage);
+		else
+#endif
+			glBufferSubData(target, 0, (GLsizeiptr) buffersize, data);
+	}
+	else
+	{
 		glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data);
 	}
 }
 
-void Buffer::copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset)
-{
-	other->fill(otheroffset, size, memoryMap + offset);
-}
-
 } // opengl
 } // graphics
 } // love

+ 6 - 9
src/modules/graphics/opengl/Buffer.h

@@ -22,6 +22,7 @@
 
 // LOVE
 #include "common/config.h"
+#include "common/Range.h"
 #include "graphics/Buffer.h"
 #include "graphics/Volatile.h"
 
@@ -49,19 +50,16 @@ public:
 	bool loadVolatile() override;
 	void unloadVolatile() override;
 
-	void *map() override;
-	void unmap() override;
-	void setMappedRangeModified(size_t offset, size_t size) override;
+	void *map(MapType map, size_t offset, size_t size) override;
+	void unmap(size_t usedoffset, size_t usedsize) override;
 	void fill(size_t offset, size_t size, const void *data) override;
 
 	ptrdiff_t getHandle() const override { return buffer; };
 	ptrdiff_t getTexelBufferHandle() const override { return texture; };
 
-	void copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) override;
-
 private:
 
-	bool load(bool restore);
+	bool load(const void *initialdata);
 
 	void unmapStatic(size_t offset, size_t size);
 	void unmapStream();
@@ -77,10 +75,9 @@ private:
 
 	// A pointer to mapped memory.
 	char *memoryMap = nullptr;
+	bool ownsMemoryMap = false;
 
-	size_t modifiedOffset = 0;
-	size_t modifiedSize = 0;
-	bool isMappedDataModified = false;
+	Range mappedRange;
 
 }; // Buffer
 

+ 28 - 1
src/modules/graphics/opengl/Graphics.cpp

@@ -93,11 +93,22 @@ Graphics::Graphics()
 	, mainVAO(0)
 	, internalBackbufferFBO(0)
 	, requestedBackbufferMSAA(0)
+	, bufferMapMemory(nullptr)
+	, bufferMapMemorySize(2 * 1024 * 1024)
 	, defaultBuffers()
 	, supportedFormats()
 {
 	gl = OpenGL();
 
+	try
+	{
+		bufferMapMemory = new char[bufferMapMemorySize];
+	}
+	catch (std::exception &)
+	{
+		// Handled in getBufferMapMemory.
+	}
+
 	auto window = getInstance<love::window::Window>(M_WINDOW);
 
 	if (window != nullptr)
@@ -121,6 +132,7 @@ Graphics::Graphics()
 
 Graphics::~Graphics()
 {
+	delete[] bufferMapMemory;
 }
 
 const char *Graphics::getName() const
@@ -328,7 +340,7 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b
 
 	if (capabilities.features[FEATURE_TEXEL_BUFFER] && defaultBuffers[BUFFERTYPE_TEXEL].get() == nullptr)
 	{
-		Buffer::Settings settings(Buffer::TYPEFLAG_TEXEL, 0, BUFFERUSAGE_STATIC);
+		Buffer::Settings settings(Buffer::TYPEFLAG_TEXEL, BUFFERUSAGE_STATIC);
 		std::vector<Buffer::DataDeclaration> format = {{"", DATAFORMAT_FLOAT_VEC4, 0}};
 
 		const float texel[] = {0.0f, 0.0f, 0.0f, 1.0f};
@@ -1436,6 +1448,21 @@ void Graphics::setWireframe(bool enable)
 	states.back().wireframe = enable;
 }
 
+void *Graphics::getBufferMapMemory(size_t size)
+{
+	// We don't need anything more complicated because get/release calls are
+	// never interleaved (as of when this comment was written.)
+	if (bufferMapMemory == nullptr || size > bufferMapMemorySize)
+		return malloc(size);
+	return bufferMapMemory;
+}
+
+void Graphics::releaseBufferMapMemory(void *mem)
+{
+	if (mem != bufferMapMemory)
+		free(mem);
+}
+
 Graphics::Renderer Graphics::getRenderer() const
 {
 	return GLAD_ES_VERSION_2_0 ? RENDERER_OPENGLES : RENDERER_OPENGL;

+ 6 - 0
src/modules/graphics/opengl/Graphics.h

@@ -112,6 +112,9 @@ public:
 	// Internal use.
 	void cleanupRenderTexture(love::graphics::Texture *texture);
 
+	void *getBufferMapMemory(size_t size);
+	void releaseBufferMapMemory(void *mem);
+
 private:
 
 	struct CachedFBOHasher
@@ -159,6 +162,9 @@ private:
 	GLuint internalBackbufferFBO;
 	int requestedBackbufferMSAA;
 
+	char *bufferMapMemory;
+	size_t bufferMapMemorySize;
+
 	// Only needed for buffer types that can be bound to shaders.
 	StrongRef<love::graphics::Buffer> defaultBuffers[BUFFERTYPE_MAX_ENUM];
 

+ 1 - 30
src/modules/graphics/opengl/Shader.cpp

@@ -761,30 +761,8 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
 			buffer->retain();
 		}
 
-		bool addbuffertoarray = true;
-
 		if (info->buffers[i] != nullptr)
-		{
-			Buffer *oldbuffer = info->buffers[i];
-			auto it = std::find(buffersToUnmap.begin(), buffersToUnmap.end(), oldbuffer);
-			if (it != buffersToUnmap.end())
-			{
-				addbuffertoarray = false;
-				if (buffer != nullptr)
-					*it = buffer;
-				else
-				{
-					auto last = buffersToUnmap.end() - 1;
-					*it = *last;
-					buffersToUnmap.erase(last);
-				}
-			}
-
-			oldbuffer->release();
-		}
-
-		if (addbuffertoarray && buffer != nullptr)
-			buffersToUnmap.push_back(buffer);
+			info->buffers[i]->release();
 
 		info->buffers[i] = buffer;
 
@@ -916,13 +894,6 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW,
 	GLint location = builtinUniforms[BUILTIN_UNIFORMS_PER_DRAW];
 	if (location >= 0)
 		glUniform4fv(location, 13, (const GLfloat *) &data);
-
-	// TODO: Find a better place to put this.
-	// Buffers used in this shader can be mapped by external code without
-	// unmapping. We need to make sure the data on the GPU is up to date,
-	// otherwise the shader can read from old data.
-	for (Buffer *buffer : buffersToUnmap)
-		buffer->unmap();
 }
 
 int Shader::getUniformTypeComponents(GLenum type) const

+ 0 - 2
src/modules/graphics/opengl/Shader.h

@@ -119,8 +119,6 @@ private:
 
 	std::vector<std::pair<const UniformInfo *, int>> pendingUniformUpdates;
 
-	std::vector<Buffer *> buffersToUnmap;
-
 	float lastPointSize;
 
 }; // Shader

+ 3 - 16
src/modules/graphics/wrap_Buffer.cpp

@@ -186,13 +186,6 @@ Buffer *luax_checkbuffer(lua_State *L, int idx)
 	return luax_checktype<Buffer>(L, idx);
 }
 
-static int w_Buffer_flush(lua_State *L)
-{
-	Buffer *t = luax_checkbuffer(L, 1);
-	t->unmap();
-	return 0;
-}
-
 static int w_Buffer_setArrayData(lua_State *L)
 {
 	Buffer *t = luax_checkbuffer(L, 1);
@@ -223,12 +216,8 @@ static int w_Buffer_setArrayData(lua_State *L)
 			return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - startindex, count);
 
 		size_t datasize = std::min(d->getSize(), count * stride);
-		char *bytedata = (char *) t->map() + offset;
-
-		memcpy(bytedata, d->getData(), datasize);
 
-		t->setMappedRangeModified(offset, datasize);
-		t->unmap();
+		t->fill(offset, datasize, d->getData());
 		return 0;
 	}
 
@@ -256,7 +245,7 @@ static int w_Buffer_setArrayData(lua_State *L)
 	if (startindex + count > arraylength)
 		return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - startindex, count);
 
-	char *data = (char *) t->map() + offset;
+	char *data = (char *) t->map(Buffer::MAP_WRITE_INVALIDATE, offset, count * stride);
 
 	if (tableoftables)
 	{
@@ -303,8 +292,7 @@ static int w_Buffer_setArrayData(lua_State *L)
 		}
 	}
 
-	t->setMappedRangeModified(offset, count * stride);
-	t->unmap();
+	t->unmap(offset, count * stride);
 
 	return 0;
 }
@@ -376,7 +364,6 @@ static int w_Buffer_isBufferType(lua_State *L)
 
 static const luaL_Reg w_Buffer_functions[] =
 {
-	{ "flush", w_Buffer_flush },
 	{ "setArrayData", w_Buffer_setArrayData },
 	{ "getElementCount", w_Buffer_getElementCount },
 	{ "getElementStride", w_Buffer_getElementStride },

+ 15 - 13
src/modules/graphics/wrap_Graphics.cpp

@@ -1559,7 +1559,7 @@ static void luax_checkbufferformat(lua_State *L, int idx, std::vector<Buffer::Da
 	}
 }
 
-static Buffer *luax_newbuffer(lua_State *L, int idx, const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format)
+static Buffer *luax_newbuffer(lua_State *L, int idx, Buffer::Settings settings, const std::vector<Buffer::DataDeclaration> &format)
 {
 	size_t arraylength = 0;
 	size_t bytesize = 0;
@@ -1600,6 +1600,7 @@ static Buffer *luax_newbuffer(lua_State *L, int idx, const Buffer::Settings &set
 		if (len <= 0)
 			luaL_argerror(L, idx, "number of elements must be greater than 0");
 		arraylength = (size_t) len;
+		settings.zeroInitialize = true;
 	}
 
 	Buffer *b = nullptr;
@@ -1663,7 +1664,7 @@ static Buffer *luax_newbuffer(lua_State *L, int idx, const Buffer::Settings &set
 
 int w_newBuffer(lua_State *L)
 {
-	Buffer::Settings settings(0, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+	Buffer::Settings settings(0, BUFFERUSAGE_DYNAMIC);
 
 	luaL_checktype(L, 3, LUA_TTABLE);
 
@@ -1691,7 +1692,7 @@ int w_newBuffer(lua_State *L)
 
 int w_newVertexBuffer(lua_State *L)
 {
-	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_DYNAMIC);
 	luax_optbuffersettings(L, 3, settings);
 
 	std::vector<Buffer::DataDeclaration> format;
@@ -1706,7 +1707,7 @@ int w_newVertexBuffer(lua_State *L)
 
 int w_newIndexBuffer(lua_State *L)
 {
-	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, BUFFERUSAGE_DYNAMIC);
 	luax_optbuffersettings(L, 3, settings);
 
 	size_t arraylength = 0;
@@ -1747,6 +1748,7 @@ int w_newIndexBuffer(lua_State *L)
 		if (len <= 0)
 			return luaL_argerror(L, 1, "number of elements must be greater than 0");
 		arraylength = (size_t) len;
+		settings.zeroInitialize = true;
 	}
 
 	if (data != nullptr || !lua_isnoneornil(L, 2))
@@ -1953,8 +1955,9 @@ static Mesh *newCustomMesh(lua_State *L)
 
 		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertexformat, numvertices, drawmode, usage); });
 
-		// Maximum possible data size for a single vertex attribute.
-		char data[sizeof(float) * 4];
+		char *data = (char *) t->getVertexData();
+		size_t stride = t->getVertexStride();
+		const auto &members = t->getVertexFormat();
 
 		for (size_t vertindex = 0; vertindex < numvertices; vertindex++)
 		{
@@ -1965,7 +1968,8 @@ static Mesh *newCustomMesh(lua_State *L)
 			int n = 0;
 			for (size_t i = 0; i < vertexformat.size(); i++)
 			{
-				const auto &info = getDataFormatInfo(vertexformat[i].format);
+				const auto &member = members[i];
+				const auto &info = getDataFormatInfo(member.decl.format);
 
 				// get vertices[vertindex][n]
 				for (int c = 0; c < info.components; c++)
@@ -1974,20 +1978,18 @@ static Mesh *newCustomMesh(lua_State *L)
 					lua_rawgeti(L, -(c + 1), n);
 				}
 
+				size_t offset = vertindex * stride + member.offset;
+
 				// Fetch the values from Lua and store them in data buffer.
-				luax_writebufferdata(L, -info.components, vertexformat[i].format, data);
+				luax_writebufferdata(L, -info.components, member.decl.format, data + offset);
 
 				lua_pop(L, info.components);
-
-				luax_catchexcept(L,
-					[&](){ t->setVertexAttribute(vertindex, i, data, sizeof(float) * 4); },
-					[&](bool diderror){ if (diderror) t->release(); }
-				);
 			}
 
 			lua_pop(L, 1); // pop vertices[vertindex]
 		}
 
+		t->setVertexDataModified(0, stride * numvertices);
 		t->flush();
 	}
 

+ 26 - 21
src/modules/graphics/wrap_Mesh.cpp

@@ -67,11 +67,13 @@ int w_Mesh_setVertices(lua_State *L)
 			return luaL_error(L, "Too many vertices (expected at most %d, got %d)", totalverts - vertstart, vertcount);
 
 		size_t datasize = std::min(d->getSize(), vertcount * stride);
-		char *bytedata = (char *) t->mapVertexData() + byteoffset;
+		char *bytedata = (char *) t->getVertexData() + byteoffset;
 
 		memcpy(bytedata, d->getData(), datasize);
 
-		t->unmapVertexData(byteoffset, datasize);
+		t->setVertexDataModified(byteoffset, datasize);
+		t->flush();
+
 		return 0;
 	}
 
@@ -88,7 +90,7 @@ int w_Mesh_setVertices(lua_State *L)
 	for (const Buffer::DataMember &member : vertexformat)
 		ncomponents += member.info.components;
 
-	char *data = (char *) t->mapVertexData() + byteoffset;
+	char *data = (char *) t->getVertexData() + byteoffset;
 
 	for (int i = 0; i < vertcount; i++)
 	{
@@ -114,7 +116,9 @@ int w_Mesh_setVertices(lua_State *L)
 		data += stride;
 	}
 
-	t->unmapVertexData(byteoffset, vertcount * stride);
+	t->setVertexDataModified(byteoffset, vertcount * stride);
+	t->flush();
+
 	return 0;
 }
 
@@ -126,7 +130,10 @@ int w_Mesh_setVertex(lua_State *L)
 	bool istable = lua_istable(L, 3);
 
 	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
-	char *data = (char *) t->getVertexScratchBuffer();
+
+	char *data = nullptr;
+	size_t offset = 0;
+	luax_catchexcept(L, [&](){ data = (char *) t->checkVertexDataOffset(index, &offset); });
 
 	int idx = istable ? 1 : 3;
 
@@ -156,7 +163,7 @@ int w_Mesh_setVertex(lua_State *L)
 		}
 	}
 
-	luax_catchexcept(L, [&](){ t->setVertex(index, data, t->getVertexStride()); });
+	t->setVertexDataModified(offset, t->getVertexStride());
 	return 0;
 }
 
@@ -167,9 +174,8 @@ int w_Mesh_getVertex(lua_State *L)
 
 	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 
-	char *data = (char *) t->getVertexScratchBuffer();
-
-	luax_catchexcept(L, [&](){ t->getVertex(index, data, t->getVertexStride()); });
+	const char *data = nullptr;
+	luax_catchexcept(L, [&](){ data = (const char *) t->checkVertexDataOffset(index, nullptr); });
 
 	int n = 0;
 
@@ -195,13 +201,14 @@ int w_Mesh_setVertexAttribute(lua_State *L)
 
 	const Buffer::DataMember &member = vertexformat[attribindex];
 
-	// Maximum possible size for a single vertex attribute.
-	char data[sizeof(float) * 4];
+	char *data = nullptr;
+	size_t offset = 0;
+	luax_catchexcept(L, [&](){ data = (char *) t->checkVertexDataOffset(vertindex, &offset); });
 
 	// Fetch the values from Lua and store them in the data buffer.
-	luax_writebufferdata(L, 4, member.decl.format, data);
+	luax_writebufferdata(L, 4, member.decl.format, data + member.offset);
 
-	luax_catchexcept(L, [&](){ t->setVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
+	t->setVertexDataModified(offset + member.offset, member.size);
 	return 0;
 }
 
@@ -218,12 +225,10 @@ int w_Mesh_getVertexAttribute(lua_State *L)
 
 	const Buffer::DataMember &member = vertexformat[attribindex];
 
-	// Maximum possible size for a single vertex attribute.
-	char data[sizeof(float) * 4];
-
-	luax_catchexcept(L, [&](){ t->getVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
+	const char *data = nullptr;
+	luax_catchexcept(L, [&](){ data = (const char *) t->checkVertexDataOffset(vertindex, nullptr); });
 
-	luax_readbufferdata(L, member.decl.format, data);
+	luax_readbufferdata(L, member.decl.format, data + member.offset);
 	return member.info.components;
 }
 
@@ -290,17 +295,17 @@ int w_Mesh_attachAttribute(lua_State *L)
 	const char *name = luaL_checkstring(L, 2);
 
 	Buffer *buffer = nullptr;
+	Mesh *mesh = nullptr;
 	if (luax_istype(L, 3, Buffer::type))
 	{
 		buffer = luax_checktype<Buffer>(L, 3);
 	}
 	else
 	{
-		Mesh *mesh = luax_checkmesh(L, 3);
+		mesh = luax_checkmesh(L, 3);
 		buffer = mesh->getVertexBuffer();
 		if (buffer == nullptr)
 			return luaL_error(L, "Mesh does not have its own vertex buffer.");
-		luax_markdeprecated(L, "Mesh:attachAttribute(name, mesh, ...)", API_METHOD, DEPRECATED_REPLACED, "Mesh:attachAttribute(name, buffer, ...)");
 	}
 
 	AttributeStep step = STEP_PER_VERTEX;
@@ -310,7 +315,7 @@ int w_Mesh_attachAttribute(lua_State *L)
 
 	const char *attachname = luaL_optstring(L, 5, name);
 
-	luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, attachname, step); });
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, mesh, attachname, step); });
 	return 0;
 }
 

+ 3 - 3
src/modules/graphics/wrap_SpriteBatch.cpp

@@ -218,20 +218,20 @@ int w_SpriteBatch_attachAttribute(lua_State *L)
 	const char *name = luaL_checkstring(L, 2);
 
 	Buffer *buffer = nullptr;
+	Mesh *mesh = nullptr;
 	if (luax_istype(L, 3, Buffer::type))
 	{
 		buffer = luax_checktype<Buffer>(L, 3);
 	}
 	else
 	{
-		Mesh *mesh = luax_checktype<Mesh>(L, 3);
+		mesh = luax_checktype<Mesh>(L, 3);
 		buffer = mesh->getVertexBuffer();
 		if (buffer == nullptr)
 			return luaL_error(L, "Mesh does not have its own vertex buffer.");
-		luax_markdeprecated(L, "SpriteBatch:attachAttribute(name, mesh)", API_METHOD, DEPRECATED_REPLACED, "SpriteBatch:attachAttribute(name, buffer)");
 	}
 
-	luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer); });
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, mesh); });
 	return 0;
 }