Browse Source

Merge branch 'GraphicsBuffer' into 12.0-development

Alex Szpakowski 5 years ago
parent
commit
6045b0f483
38 changed files with 2213 additions and 1115 deletions
  1. 2 0
      CMakeLists.txt
  2. 10 0
      platform/xcode/liblove.xcodeproj/project.pbxproj
  3. 15 0
      src/common/StringMap.h
  4. 157 6
      src/modules/graphics/Buffer.cpp
  5. 85 34
      src/modules/graphics/Buffer.h
  6. 2 2
      src/modules/graphics/Font.cpp
  7. 2 2
      src/modules/graphics/Font.h
  8. 27 28
      src/modules/graphics/Graphics.cpp
  9. 21 21
      src/modules/graphics/Graphics.h
  10. 184 188
      src/modules/graphics/Mesh.cpp
  11. 30 30
      src/modules/graphics/Mesh.h
  12. 7 4
      src/modules/graphics/ParticleSystem.cpp
  13. 1 1
      src/modules/graphics/ParticleSystem.h
  14. 4 4
      src/modules/graphics/Polyline.cpp
  15. 3 3
      src/modules/graphics/Polyline.h
  16. 0 5
      src/modules/graphics/Shader.h
  17. 30 30
      src/modules/graphics/SpriteBatch.cpp
  18. 4 4
      src/modules/graphics/SpriteBatch.h
  19. 6 3
      src/modules/graphics/Text.cpp
  20. 2 2
      src/modules/graphics/Text.h
  21. 6 10
      src/modules/graphics/Texture.cpp
  22. 4 4
      src/modules/graphics/Video.cpp
  23. 93 83
      src/modules/graphics/opengl/Buffer.cpp
  24. 15 11
      src/modules/graphics/opengl/Buffer.h
  25. 17 17
      src/modules/graphics/opengl/Graphics.cpp
  26. 3 3
      src/modules/graphics/opengl/Graphics.h
  27. 150 86
      src/modules/graphics/opengl/OpenGL.cpp
  28. 4 4
      src/modules/graphics/opengl/OpenGL.h
  29. 3 2
      src/modules/graphics/opengl/Shader.cpp
  30. 188 216
      src/modules/graphics/vertex.cpp
  31. 117 80
      src/modules/graphics/vertex.h
  32. 474 0
      src/modules/graphics/wrap_Buffer.cpp
  33. 39 0
      src/modules/graphics/wrap_Buffer.h
  34. 359 43
      src/modules/graphics/wrap_Graphics.cpp
  35. 1 0
      src/modules/graphics/wrap_Graphics.h
  36. 133 184
      src/modules/graphics/wrap_Mesh.cpp
  37. 0 3
      src/modules/graphics/wrap_Mesh.h
  38. 15 2
      src/modules/graphics/wrap_SpriteBatch.cpp

+ 2 - 0
CMakeLists.txt

@@ -527,6 +527,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 	src/modules/graphics/Video.h
 	src/modules/graphics/Volatile.cpp
 	src/modules/graphics/Volatile.h
+	src/modules/graphics/wrap_Buffer.cpp
+	src/modules/graphics/wrap_Buffer.h
 	src/modules/graphics/wrap_Font.cpp
 	src/modules/graphics/wrap_Font.h
 	src/modules/graphics/wrap_Graphics.cpp

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

@@ -775,6 +775,9 @@
 		FA15DFB01F9B8D6A0042AB22 /* wrap_Data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA6A2B651F5F7B6B0074C308 /* wrap_Data.cpp */; };
 		FA15DFB11F9B8D820042AB22 /* OggDemuxer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAA54AC91F91660400A8FA7B /* OggDemuxer.cpp */; };
 		FA15DFB21F9B8D840042AB22 /* TheoraVideoStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAA54AC81F91660400A8FA7B /* TheoraVideoStream.cpp */; };
+		FA18CEC523D3AE6700263725 /* wrap_Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA18CEC323D3AE6700263725 /* wrap_Buffer.cpp */; };
+		FA18CEC623D3AE6800263725 /* wrap_Buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA18CEC323D3AE6700263725 /* wrap_Buffer.cpp */; };
+		FA18CEC723D3AE6800263725 /* wrap_Buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = FA18CEC423D3AE6700263725 /* wrap_Buffer.h */; };
 		FA1BA09D1E16CFCE00AA2803 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA09B1E16CFCE00AA2803 /* Font.cpp */; };
 		FA1BA09E1E16CFCE00AA2803 /* Font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA1BA09B1E16CFCE00AA2803 /* Font.cpp */; };
 		FA1BA09F1E16CFCE00AA2803 /* Font.h in Headers */ = {isa = PBXBuildFile; fileRef = FA1BA09C1E16CFCE00AA2803 /* Font.h */; };
@@ -1782,6 +1785,8 @@
 		FA1557C11CE90BD200AFF582 /* EXRHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EXRHandler.cpp; sourceTree = "<group>"; };
 		FA1557C21CE90BD200AFF582 /* EXRHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXRHandler.h; sourceTree = "<group>"; };
 		FA15DFAB1F9B8C850042AB22 /* StringMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StringMap.cpp; sourceTree = "<group>"; };
+		FA18CEC323D3AE6700263725 /* wrap_Buffer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Buffer.cpp; sourceTree = "<group>"; };
+		FA18CEC423D3AE6700263725 /* wrap_Buffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wrap_Buffer.h; sourceTree = "<group>"; };
 		FA1BA09B1E16CFCE00AA2803 /* Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Font.cpp; sourceTree = "<group>"; };
 		FA1BA09C1E16CFCE00AA2803 /* Font.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Font.h; sourceTree = "<group>"; };
 		FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Font.cpp; sourceTree = "<group>"; };
@@ -2849,6 +2854,8 @@
 				FADF54061E3D78F700012CC0 /* Video.h */,
 				FA0B7BC01A95902C000E1D17 /* Volatile.cpp */,
 				FA0B7BC11A95902C000E1D17 /* Volatile.h */,
+				FA18CEC323D3AE6700263725 /* wrap_Buffer.cpp */,
+				FA18CEC423D3AE6700263725 /* wrap_Buffer.h */,
 				FA1BA0A01E16D97500AA2803 /* wrap_Font.cpp */,
 				FA1BA0A11E16D97500AA2803 /* wrap_Font.h */,
 				FADF54391E3DAFF700012CC0 /* wrap_Graphics.cpp */,
@@ -3929,6 +3936,7 @@
 				FA0B7A631A958EA3000E1D17 /* b2ContactManager.h in Headers */,
 				FA0B7E771A95902C000E1D17 /* wrap_WeldJoint.h in Headers */,
 				FA0B7A911A958EA3000E1D17 /* b2FrictionJoint.h in Headers */,
+				FA18CEC723D3AE6800263725 /* wrap_Buffer.h in Headers */,
 				FA0B7E291A95902C000E1D17 /* PulleyJoint.h in Headers */,
 				FA6BDE5C1F31725300786805 /* Color.h in Headers */,
 				FA0B7E231A95902C000E1D17 /* PolygonShape.h in Headers */,
@@ -4497,6 +4505,7 @@
 				FAF6C9E023C2DE2900D7B5BC /* SpvTools.cpp in Sources */,
 				FAE64A8F2071364200BC7981 /* physfs_platform_unix.c in Sources */,
 				FA0B7A681A958EA3000E1D17 /* b2Island.cpp in Sources */,
+				FA18CEC623D3AE6800263725 /* wrap_Buffer.cpp in Sources */,
 				FA0B7E2B1A95902C000E1D17 /* RevoluteJoint.cpp in Sources */,
 				FA0B7B291A958EA3000E1D17 /* simplexnoise1234.cpp in Sources */,
 				FA0B7D261A95902C000E1D17 /* wrap_Font.cpp in Sources */,
@@ -4748,6 +4757,7 @@
 				FAF140A91E20934C00F898D2 /* SymbolTable.cpp in Sources */,
 				FA0B7E181A95902C000E1D17 /* MotorJoint.cpp in Sources */,
 				FA0B7E4E1A95902C000E1D17 /* wrap_Fixture.cpp in Sources */,
+				FA18CEC523D3AE6700263725 /* wrap_Buffer.cpp in Sources */,
 				FAF6C9F423C2DE2900D7B5BC /* Logger.cpp in Sources */,
 				FA0B7EBE1A95902C000E1D17 /* Thread.cpp in Sources */,
 				FAC7CD8F1FE35E95006A60C7 /* physfs_platform_posix.c in Sources */,

+ 15 - 0
src/common/StringMap.h

@@ -182,6 +182,21 @@ private:
 
 }; // StringMap
 
+#define STRINGMAP_DECLARE(type) \
+bool getConstant(const char *in, type &out); \
+bool getConstant(type in, const char *&out); \
+std::vector<std::string> getConstants(type); \
+
+#define STRINGMAP_BEGIN(type, count, name) \
+static StringMap<type, count>::Entry name##Entries[] =
+
+#define STRINGMAP_END(type, count, name) \
+; \
+static StringMap<type, count> name##s(name##Entries, sizeof(name##Entries)); \
+bool getConstant(const char *in, type &out) { return name##s.find(in, out); } \
+bool getConstant(type in, const char *&out) { return name##s.find(in, out); } \
+std::vector<std::string> getConstants(type) { return name##s.getNames(); }
+
 } // love
 
 #endif // LOVE_STRING_MAP_H

+ 157 - 6
src/modules/graphics/Buffer.cpp

@@ -19,24 +19,175 @@
  **/
 
 #include "Buffer.h"
+#include "Graphics.h"
 
 namespace love
 {
 namespace graphics
 {
 
-Buffer::Buffer(size_t size, BufferType type, vertex::Usage usage, uint32 mapflags)
-	: size(size)
-	, type(type)
-	, usage(usage)
-	, map_flags(mapflags)
-	, is_mapped(false)
+love::Type Buffer::type("GraphicsBuffer", &Object::type);
+
+Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &bufferformat, size_t size, size_t arraylength)
+	: arrayLength(0)
+	, arrayStride(0)
+	, size(size)
+	, typeFlags(settings.typeFlags)
+	, usage(settings.usage)
+	, mapFlags(settings.mapFlags)
+	, mapped(false)
 {
+	if (size == 0 && arraylength == 0)
+		throw love::Exception("Size or array length must be specified.");
+
+	if (bufferformat.size() == 0)
+		throw love::Exception("Data format must contain values.");
+
+	bool supportsGLSL3 = gfx->getCapabilities().features[Graphics::FEATURE_GLSL3];
+
+	bool indexbuffer = settings.typeFlags & TYPEFLAG_INDEX;
+	bool vertexbuffer = settings.typeFlags & TYPEFLAG_VERTEX;
+
+	if (!indexbuffer && !vertexbuffer)
+		throw love::Exception("Buffer must be created with at least one buffer type (index or vertex).");
+
+	size_t offset = 0;
+	size_t stride = 0;
+
+	for (const DataDeclaration &decl : bufferformat)
+	{
+		DataMember member(decl);
+
+		DataFormat format = member.decl.format;
+		const DataFormatInfo &info = member.info;
+
+		if (indexbuffer)
+		{
+			if (format != DATAFORMAT_UINT16 && format != DATAFORMAT_UINT32)
+				throw love::Exception("Index buffers only support uint16 and uint32 data types.");
+
+			if (bufferformat.size() > 1)
+				throw love::Exception("Index buffers only support a single value per element.");
+		}
+
+		if (vertexbuffer)
+		{
+			if (decl.arrayLength > 0)
+				throw love::Exception("Arrays are not supported in vertex buffers.");
+
+			if (info.isMatrix)
+				throw love::Exception("Matrix types are not supported in vertex buffers.");
+
+			if (info.baseType == DATA_BASETYPE_BOOL)
+				throw love::Exception("Bool types are not supported in vertex buffers.");
+
+			if ((info.baseType == DATA_BASETYPE_INT || info.baseType == DATA_BASETYPE_UINT) && !supportsGLSL3)
+				throw love::Exception("Integer vertex attribute data types require GLSL 3 support.");
+		}
+
+		// TODO: alignment
+		member.offset = offset;
+		member.size = member.info.size;
+
+		offset += member.size;
+
+		dataMembers.push_back(member);
+	}
+
+	stride = offset;
+
+	if (size != 0)
+	{
+		size_t remainder = size % stride;
+		if (remainder > 0)
+			size += stride - remainder;
+		arraylength = size / stride;
+	}
+	else
+	{
+		size = arraylength * stride;
+	}
+
+	this->arrayStride = stride;
+	this->arrayLength = arraylength;
+	this->size = size;
 }
 
 Buffer::~Buffer()
 {
 }
 
+int Buffer::getDataMemberIndex(const std::string &name) const
+{
+	for (size_t i = 0; i < dataMembers.size(); i++)
+	{
+		if (dataMembers[i].decl.name == name)
+			return (int) i;
+	}
+
+	return -1;
+}
+
+std::vector<Buffer::DataDeclaration> Buffer::getCommonFormatDeclaration(CommonFormat format)
+{
+	switch (format)
+	{
+	case CommonFormat::NONE:
+		return {};
+	case CommonFormat::XYf:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }
+		};
+	case CommonFormat::XYZf:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC3 }
+		};
+	case CommonFormat::RGBAub:
+		return {
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }
+		};
+	case CommonFormat::STf_RGBAub:
+		return {
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	case CommonFormat::STPf_RGBAub:
+		return {
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	case CommonFormat::XYf_STf:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
+		};
+	case CommonFormat::XYf_STPf:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 },
+		};
+	case CommonFormat::XYf_STf_RGBAub:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	case CommonFormat::XYf_STus_RGBAub:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_UNORM16_VEC2 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	case CommonFormat::XYf_STPf_RGBAub:
+		return {
+			{ getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 },
+			{ getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 },
+		};
+	}
+
+	return {};
+}
+
 } // graphics
 } // love

+ 85 - 34
src/modules/graphics/Buffer.h

@@ -23,40 +23,101 @@
 // LOVE
 #include "common/config.h"
 #include "common/int.h"
+#include "common/Object.h"
 #include "vertex.h"
 #include "Resource.h"
 
 // C
 #include <stddef.h>
+#include <string>
+#include <vector>
 
 namespace love
 {
 namespace graphics
 {
 
+class Graphics;
+
 /**
- * A block of GPU-owned memory. Currently meant for internal use.
+ * A block of GPU-owned memory.
  **/
-class Buffer : public Resource
+class Buffer : public love::Object, public Resource
 {
 public:
 
+	static love::Type type;
+
 	enum MapFlags
 	{
+		MAP_NONE = 0,
 		MAP_EXPLICIT_RANGE_MODIFY = (1 << 0), // see setMappedRangeModified.
 		MAP_READ = (1 << 1),
 	};
 
-	Buffer(size_t size, BufferType type, vertex::Usage usage, uint32 mapflags);
-	virtual ~Buffer();
+	enum TypeFlags
+	{
+		TYPEFLAG_NONE = 0,
+		TYPEFLAG_VERTEX = 1 << BUFFERTYPE_VERTEX,
+		TYPEFLAG_INDEX = 1 << BUFFERTYPE_INDEX,
+	};
 
-	size_t getSize() const { return size; }
+	struct DataDeclaration
+	{
+		std::string name;
+		DataFormat format;
+		int arrayLength;
+
+		DataDeclaration(const std::string &name, DataFormat format, int arrayLength = 0)
+			: name(name)
+			, format(format)
+			, arrayLength(arrayLength)
+		{}
+	};
+
+	struct DataMember
+	{
+		DataDeclaration decl;
+		DataFormatInfo info;
+		size_t offset;
+		size_t size;
+
+		DataMember(const DataDeclaration &decl)
+			: decl(decl)
+			, info(getDataFormatInfo(decl.format))
+			, offset(0)
+			, size(0)
+		{}
+	};
 
-	BufferType getType() const { return type; }
+	struct Settings
+	{
+		TypeFlags typeFlags;
+		MapFlags mapFlags;
+		BufferUsage usage;
+
+		Settings(uint32 typeflags, uint32 mapflags, BufferUsage usage)
+			: typeFlags((TypeFlags)typeflags)
+			, mapFlags((MapFlags)mapflags)
+			, usage(usage)
+		{}
+	};
 
-	vertex::Usage getUsage() const { return usage; }
+	Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &format, size_t size, size_t arraylength);
+	virtual ~Buffer();
 
-	bool isMapped() const { return is_mapped; }
+	size_t getSize() const { return size; }
+	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; }
+	const std::vector<DataMember> &getDataMembers() const { return dataMembers; }
+	const DataMember &getDataMember(int index) const { return dataMembers[index]; }
+	size_t getMemberOffset(int index) const { return dataMembers[index].offset; }
+	int getDataMemberIndex(const std::string &name) const;
 
 	/**
 	 * Map the Buffer to client memory.
@@ -92,58 +153,48 @@ public:
 	 **/
 	virtual void copyTo(size_t offset, size_t size, Buffer *other, size_t otheroffset) = 0;
 
-	uint32 getMapFlags() const { return map_flags; }
+	static std::vector<DataDeclaration> getCommonFormatDeclaration(CommonFormat format);
 
 	class Mapper
 	{
 	public:
 
-		/**
-		 * Memory-maps a Buffer.
-		 */
 		Mapper(Buffer &buffer)
-			: buf(buffer)
+			: buffer(buffer)
 		{
-			elems = buf.map();
+			data = buffer.map();
 		}
 
-		/**
-		 * unmaps the buffer
-		 */
 		~Mapper()
 		{
-			buf.unmap();
+			if (buffer.getMapFlags() & MAP_EXPLICIT_RANGE_MODIFY)
+				buffer.setMappedRangeModified(0, buffer.getSize());
+			buffer.unmap();
 		}
 
-		/**
-		 * Get pointer to memory mapped region
-		 */
-		void *get()
-		{
-			return elems;
-		}
-
-	private:
-
-		Buffer &buf;
-		void *elems;
+		Buffer &buffer;
+		void *data;
 
 	}; // Mapper
 
 protected:
 
+	std::vector<DataMember> dataMembers;
+	size_t arrayLength;
+	size_t arrayStride;
+
 	// The size of the buffer, in bytes.
 	size_t size;
 
 	// The type of the buffer object.
-	BufferType type;
+	TypeFlags typeFlags;
 
 	// Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW.
-	vertex::Usage usage;
+	BufferUsage usage;
 	
-	uint32 map_flags;
+	uint32 mapFlags;
 
-	bool is_mapped;
+	bool mapped;
 	
 }; // Buffer
 

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

@@ -45,7 +45,7 @@ static inline uint16 normToUint16(double n)
 love::Type Font::type("Font", &Object::type);
 int Font::fontCount = 0;
 
-const vertex::CommonFormat Font::vertexFormat = vertex::CommonFormat::XYf_STus_RGBAub;
+const CommonFormat Font::vertexFormat = CommonFormat::XYf_STus_RGBAub;
 
 Font::Font(love::font::Rasterizer *r, const SamplerState &s)
 	: rasterizers({r})
@@ -647,7 +647,7 @@ void Font::printv(graphics::Graphics *gfx, const Matrix4 &t, const std::vector<D
 	{
 		Graphics::BatchedDrawCommand streamcmd;
 		streamcmd.formats[0] = vertexFormat;
-		streamcmd.indexMode = vertex::TriangleIndexMode::QUADS;
+		streamcmd.indexMode = TRIANGLEINDEX_QUADS;
 		streamcmd.vertexCount = cmd.vertexcount;
 		streamcmd.texture = cmd.texture;
 

+ 2 - 2
src/modules/graphics/Font.h

@@ -51,9 +51,9 @@ public:
 	static love::Type type;
 
 	typedef std::vector<uint32> Codepoints;
-	typedef vertex::XYf_STus_RGBAub GlyphVertex;
+	typedef XYf_STus_RGBAub GlyphVertex;
 
-	static const vertex::CommonFormat vertexFormat;
+	static const CommonFormat vertexFormat;
 
 	enum AlignMode
 	{

+ 27 - 28
src/modules/graphics/Graphics.cpp

@@ -171,10 +171,12 @@ void Graphics::createQuadIndexBuffer()
 		return;
 
 	size_t size = sizeof(uint16) * (LOVE_UINT16_MAX / 4) * 6;
-	quadIndexBuffer = newBuffer(size, nullptr, BUFFER_INDEX, vertex::USAGE_STATIC, 0);
+
+	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, 0, BUFFERUSAGE_STATIC);
+	quadIndexBuffer = newBuffer(settings, DATAFORMAT_UINT16, nullptr, size, 0);
 
 	Buffer::Mapper map(*quadIndexBuffer);
-	vertex::fillIndices(vertex::TriangleIndexMode::QUADS, 0, LOVE_UINT16_MAX, (uint16 *) map.get());
+	fillIndices(TRIANGLEINDEX_QUADS, 0, LOVE_UINT16_MAX, (uint16 *) map.data);
 }
 
 Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
@@ -202,7 +204,7 @@ Video *Graphics::newVideo(love::video::VideoStream *stream, float dpiscale)
 	return new Video(this, stream, dpiscale);
 }
 
-love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, vertex::Usage usage)
+love::graphics::SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, BufferUsage usage)
 {
 	return new SpriteBatch(this, texture, size, usage);
 }
@@ -287,24 +289,25 @@ Shader *Graphics::newShader(const std::vector<std::string> &stagessource)
 	return newShaderInternal(stages[ShaderStage::STAGE_VERTEX], stages[ShaderStage::STAGE_PIXEL]);
 }
 
-Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, PrimitiveType drawmode, vertex::Usage usage)
+Buffer *Graphics::newBuffer(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength)
 {
-	return newMesh(Mesh::getDefaultVertexFormat(), &vertices[0], vertices.size() * sizeof(Vertex), drawmode, usage);
+	std::vector<Buffer::DataDeclaration> dataformat = {{"", format, 0}};
+	return newBuffer(settings, dataformat, data, size, arraylength);
 }
 
-Mesh *Graphics::newMesh(int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
+Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
 {
-	return newMesh(Mesh::getDefaultVertexFormat(), vertexcount, drawmode, usage);
+	return new Mesh(this, vertexformat, vertexcount, drawmode, usage);
 }
 
-love::graphics::Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
+Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
 {
-	return new Mesh(this, vertexformat, vertexcount, drawmode, usage);
+	return new Mesh(this, vertexformat, data, datasize, drawmode, usage);
 }
 
-love::graphics::Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage)
+Mesh *Graphics::newMesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
 {
-	return new Mesh(this, vertexformat, data, datasize, drawmode, usage);
+	return new Mesh(attributes, drawmode);
 }
 
 love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector<Font::ColoredString> &text)
@@ -944,7 +947,7 @@ CullMode Graphics::getMeshCullMode() const
 	return states.back().meshCullMode;
 }
 
-vertex::Winding Graphics::getFrontFaceWinding() const
+Winding Graphics::getFrontFaceWinding() const
 {
 	return states.back().winding;
 }
@@ -1033,8 +1036,6 @@ void Graphics::captureScreenshot(const ScreenshotInfo &info)
 
 Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawCommand &cmd)
 {
-	using namespace vertex;
-
 	BatchedDrawState &state = batchedDrawState;
 
 	bool shouldflush = false;
@@ -1042,7 +1043,7 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 
 	if (cmd.primitiveMode != state.primitiveMode
 		|| cmd.formats[0] != state.formats[0] || cmd.formats[1] != state.formats[1]
-		|| ((cmd.indexMode != TriangleIndexMode::NONE) != (state.indexCount > 0))
+		|| ((cmd.indexMode != TRIANGLEINDEX_NONE) != (state.indexCount > 0))
 		|| cmd.texture != state.texture
 		|| cmd.standardShaderType != state.standardShaderType)
 	{
@@ -1052,7 +1053,7 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 	int totalvertices = state.vertexCount + cmd.vertexCount;
 
 	// We only support uint16 index buffers for now.
-	if (totalvertices > LOVE_UINT16_MAX && cmd.indexMode != TriangleIndexMode::NONE)
+	if (totalvertices > LOVE_UINT16_MAX && cmd.indexMode != TRIANGLEINDEX_NONE)
 		shouldflush = true;
 
 	int reqIndexCount = getIndexCount(cmd.indexMode, cmd.vertexCount);
@@ -1081,7 +1082,7 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 		newdatasizes[i] = stride * cmd.vertexCount;
 	}
 
-	if (cmd.indexMode != TriangleIndexMode::NONE)
+	if (cmd.indexMode != TRIANGLEINDEX_NONE)
 	{
 		size_t datasize = (state.indexCount + reqIndexCount) * sizeof(uint16);
 
@@ -1119,18 +1120,18 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 			if (state.vb[i]->getSize() < buffersizes[i])
 			{
 				delete state.vb[i];
-				state.vb[i] = newStreamBuffer(BUFFER_VERTEX, buffersizes[i]);
+				state.vb[i] = newStreamBuffer(BUFFERTYPE_VERTEX, buffersizes[i]);
 			}
 		}
 
 		if (state.indexBuffer->getSize() < buffersizes[2])
 		{
 			delete state.indexBuffer;
-			state.indexBuffer = newStreamBuffer(BUFFER_INDEX, buffersizes[2]);
+			state.indexBuffer = newStreamBuffer(BUFFERTYPE_INDEX, buffersizes[2]);
 		}
 	}
 
-	if (cmd.indexMode != TriangleIndexMode::NONE)
+	if (cmd.indexMode != TRIANGLEINDEX_NONE)
 	{
 		if (state.indexBufferMap.data == nullptr)
 			state.indexBufferMap = state.indexBuffer->map(reqIndexSize);
@@ -1167,14 +1168,12 @@ Graphics::BatchedVertexData Graphics::requestBatchedDraw(const BatchedDrawComman
 
 void Graphics::flushBatchedDraws()
 {
-	using namespace vertex;
-
 	auto &sbstate = batchedDrawState;
 
 	if (sbstate.vertexCount == 0 && sbstate.indexCount == 0)
 		return;
 
-	Attributes attributes;
+	VertexAttributes attributes;
 	BufferBindings buffers;
 
 	size_t usedsizes[3] = {0, 0, 0};
@@ -1317,8 +1316,8 @@ void Graphics::points(const Vector2 *positions, const Colorf *colors, size_t num
 
 	BatchedDrawCommand cmd;
 	cmd.primitiveMode = PRIMITIVE_POINTS;
-	cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
-	cmd.formats[1] = vertex::CommonFormat::RGBAub;
+	cmd.formats[0] = getSinglePositionFormat(is2D);
+	cmd.formats[1] = CommonFormat::RGBAub;
 	cmd.vertexCount = (int) numpoints;
 
 	BatchedVertexData data = requestBatchedDraw(cmd);
@@ -1612,9 +1611,9 @@ void Graphics::polygon(DrawMode mode, const Vector2 *coords, size_t count, bool
 		bool is2D = t.isAffine2DTransform();
 
 		BatchedDrawCommand cmd;
-		cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
-		cmd.formats[1] = vertex::CommonFormat::RGBAub;
-		cmd.indexMode = vertex::TriangleIndexMode::FAN;
+		cmd.formats[0] = getSinglePositionFormat(is2D);
+		cmd.formats[1] = CommonFormat::RGBAub;
+		cmd.indexMode = TRIANGLEINDEX_FAN;
 		cmd.vertexCount = (int)count - (skipLastFilledVertex ? 1 : 0);
 
 		BatchedVertexData data = requestBatchedDraw(cmd);

+ 21 - 21
src/modules/graphics/Graphics.h

@@ -209,8 +209,8 @@ public:
 	{
 		PrimitiveType primitiveType = PRIMITIVE_TRIANGLES;
 
-		const vertex::Attributes *attributes;
-		const vertex::BufferBindings *buffers;
+		const VertexAttributes *attributes;
+		const BufferBindings *buffers;
 
 		int vertexStart = 0;
 		int vertexCount = 0;
@@ -221,7 +221,7 @@ public:
 		// TODO: This should be moved out to a state transition API?
 		CullMode cullMode = CULL_NONE;
 
-		DrawCommand(const vertex::Attributes *attribs, const vertex::BufferBindings *buffers)
+		DrawCommand(const VertexAttributes *attribs, const BufferBindings *buffers)
 			: attributes(attribs)
 			, buffers(buffers)
 		{}
@@ -231,8 +231,8 @@ public:
 	{
 		PrimitiveType primitiveType = PRIMITIVE_TRIANGLES;
 
-		const vertex::Attributes *attributes;
-		const vertex::BufferBindings *buffers;
+		const VertexAttributes *attributes;
+		const BufferBindings *buffers;
 
 		int indexCount = 0;
 		int instanceCount = 1;
@@ -246,7 +246,7 @@ public:
 		// TODO: This should be moved out to a state transition API?
 		CullMode cullMode = CULL_NONE;
 
-		DrawIndexedCommand(const vertex::Attributes *attribs, const vertex::BufferBindings *buffers, Resource *indexbuffer)
+		DrawIndexedCommand(const VertexAttributes *attribs, const BufferBindings *buffers, Resource *indexbuffer)
 			: attributes(attribs)
 			, buffers(buffers)
 			, indexBuffer(indexbuffer)
@@ -256,8 +256,8 @@ public:
 	struct BatchedDrawCommand
 	{
 		PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES;
-		vertex::CommonFormat formats[2];
-		vertex::TriangleIndexMode indexMode = vertex::TriangleIndexMode::NONE;
+		CommonFormat formats[2];
+		TriangleIndexMode indexMode = TRIANGLEINDEX_NONE;
 		int vertexCount = 0;
 		Texture *texture = nullptr;
 		Shader::StandardShader standardShaderType = Shader::STANDARD_DEFAULT;
@@ -265,7 +265,7 @@ public:
 		BatchedDrawCommand()
 		{
 			// VS2013 can't initialize arrays in the above manner...
-			formats[1] = formats[0] = vertex::CommonFormat::NONE;
+			formats[1] = formats[0] = CommonFormat::NONE;
 		}
 	};
 
@@ -428,17 +428,17 @@ public:
 	Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting);
 	Video *newVideo(love::video::VideoStream *stream, float dpiscale);
 
-	SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage);
+	SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferUsage usage);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
 	Shader *newShader(const std::vector<std::string> &stagessource);
 
-	virtual Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) = 0;
+	virtual Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) = 0;
+	virtual Buffer *newBuffer(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength);
 
-	Mesh *newMesh(const std::vector<Vertex> &vertices, PrimitiveType drawmode, vertex::Usage usage);
-	Mesh *newMesh(int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
-	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
-	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage);
+	Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage);
+	Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage);
+	Mesh *newMesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode);
 
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 
@@ -582,8 +582,8 @@ public:
 	void setMeshCullMode(CullMode cull);
 	CullMode getMeshCullMode() const;
 
-	virtual void setFrontFaceWinding(vertex::Winding winding) = 0;
-	vertex::Winding getFrontFaceWinding() const;
+	virtual void setFrontFaceWinding(Winding winding) = 0;
+	Winding getFrontFaceWinding() const;
 
 	/**
 	 * Sets the enabled color components when rendering.
@@ -814,7 +814,7 @@ public:
 
 	virtual void draw(const DrawCommand &cmd) = 0;
 	virtual void draw(const DrawIndexedCommand &cmd) = 0;
-	virtual void drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, Texture *texture) = 0;
+	virtual void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, Texture *texture) = 0;
 
 	void flushBatchedDraws();
 	BatchedVertexData requestBatchedDraw(const BatchedDrawCommand &command);
@@ -885,7 +885,7 @@ protected:
 		bool depthWrite = false;
 
 		CullMode meshCullMode = CULL_NONE;
-		vertex::Winding winding = vertex::WINDING_CCW;
+		Winding winding = WINDING_CCW;
 
 		StrongRef<Font> font;
 		StrongRef<Shader> shader;
@@ -905,7 +905,7 @@ protected:
 		StreamBuffer *indexBuffer = nullptr;
 
 		PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES;
-		vertex::CommonFormat formats[2];
+		CommonFormat formats[2];
 		StrongRef<Texture> texture;
 		Shader::StandardShader standardShaderType = Shader::STANDARD_DEFAULT;
 		int vertexCount = 0;
@@ -917,7 +917,7 @@ protected:
 		BatchedDrawState()
 		{
 			vb[0] = vb[1] = nullptr;
-			formats[0] = formats[1] = vertex::CommonFormat::NONE;
+			formats[0] = formats[1] = CommonFormat::NONE;
 			vbMap[0] = vbMap[1] = StreamBuffer::MapInfo();
 		}
 	};

+ 184 - 188
src/modules/graphics/Mesh.cpp

@@ -34,36 +34,22 @@ namespace love
 namespace graphics
 {
 
-static const char *getBuiltinAttribName(BuiltinVertexAttribute attribid)
-{
-	const char *name = "";
-	vertex::getConstant(attribid, name);
-	return name;
-}
-
 static_assert(offsetof(Vertex, x) == sizeof(float) * 0, "Incorrect position offset in Vertex struct");
 static_assert(offsetof(Vertex, s) == sizeof(float) * 2, "Incorrect texture coordinate offset in Vertex struct");
 static_assert(offsetof(Vertex, color.r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");
 
-std::vector<Mesh::AttribFormat> Mesh::getDefaultVertexFormat()
+std::vector<Buffer::DataDeclaration> Mesh::getDefaultVertexFormat()
 {
-	// Corresponds to the love::Vertex struct.
-	std::vector<Mesh::AttribFormat> vertexformat = {
-		{ getBuiltinAttribName(ATTRIB_POS),      vertex::DATA_FLOAT,  2 },
-		{ getBuiltinAttribName(ATTRIB_TEXCOORD), vertex::DATA_FLOAT,  2 },
-		{ getBuiltinAttribName(ATTRIB_COLOR),    vertex::DATA_UNORM8, 4 },
-	};
-
-	return vertexformat;
+	return Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
 }
 
 love::Type Mesh::type("Mesh", &Drawable::type);
 
-Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage)
-	: vertexFormat(vertexformat)
-	, vertexBuffer(nullptr)
+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)
@@ -72,29 +58,29 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexforma
 	, rangeStart(-1)
 	, rangeCount(-1)
 {
-	setupAttachedAttributes();
-	calculateAttributeSizes(gfx);
+	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);
 
-	vertexCount = datasize / vertexStride;
-	indexDataType = vertex::getIndexDataTypeFromMax(vertexCount);
+	vertexCount = vertexBuffer->getArrayLength();
+	vertexStride = vertexBuffer->getArrayStride();
+	vertexFormat = vertexBuffer->getDataMembers();
 
-	if (vertexCount == 0)
-		throw love::Exception("Data size is too small for specified vertex attribute formats.");
+	setupAttachedAttributes();
 
-	vertexBuffer = gfx->newBuffer(datasize, data, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ);
+	indexDataType = getIndexDataTypeFromMax(vertexCount);
 
 	vertexScratchBuffer = new char[vertexStride];
 }
 
-Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage)
-	: vertexFormat(vertexformat)
-	, vertexBuffer(nullptr)
+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)
-	, indexDataType(vertex::getIndexDataTypeFromMax(vertexcount))
+	, indexDataType(getIndexDataTypeFromMax(vertexcount))
 	, primitiveType(drawmode)
 	, rangeStart(-1)
 	, rangeCount(-1)
@@ -102,83 +88,82 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<AttribFormat> &vertexforma
 	if (vertexcount <= 0)
 		throw love::Exception("Invalid number of vertices (%d).", vertexcount);
 
-	setupAttachedAttributes();
-	calculateAttributeSizes(gfx);
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ, usage);
+	vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN);
 
-	size_t buffersize = vertexCount * vertexStride;
+	vertexStride = vertexBuffer->getArrayStride();
+	vertexFormat = vertexBuffer->getDataMembers();
 
-	vertexBuffer = gfx->newBuffer(buffersize, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ);
+	setupAttachedAttributes();
 
-	// Initialize the buffer's contents to 0.
-	memset(vertexBuffer->map(), 0, buffersize);
+	memset(vertexBuffer->map(), 0, vertexBuffer->getSize());
 	vertexBuffer->setMappedRangeModified(0, vertexBuffer->getSize());
 	vertexBuffer->unmap();
 
 	vertexScratchBuffer = new char[vertexStride];
 }
 
-Mesh::~Mesh()
+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)
 {
-	delete vertexBuffer;
-	delete indexBuffer;
-	delete vertexScratchBuffer;
+	if (attributes.size() == 0)
+		throw love::Exception("At least one buffer attribute must be specified in this constructor.");
+
+	attachedAttributes = attributes;
+
+	vertexCount = attachedAttributes.size() > 0 ? LOVE_UINT32_MAX : 0;
 
 	for (const auto &attrib : attachedAttributes)
 	{
-		if (attrib.second.mesh != this)
-			attrib.second.mesh->release();
+		if ((attrib.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.");
+
+		if (getAttachedAttributeIndex(attrib.name) != -1)
+			throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str());
+
+		vertexCount = std::min(vertexCount, attrib.buffer->getArrayLength());
 	}
+
+	indexDataType = getIndexDataTypeFromMax(vertexCount);
+}
+
+Mesh::~Mesh()
+{
+	delete vertexScratchBuffer;
 }
 
 void Mesh::setupAttachedAttributes()
 {
 	for (size_t i = 0; i < vertexFormat.size(); i++)
 	{
-		const std::string &name = vertexFormat[i].name;
+		const std::string &name = vertexFormat[i].decl.name;
 
-		if (attachedAttributes.find(name) != attachedAttributes.end())
+		if (getAttachedAttributeIndex(name) != -1)
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
-		attachedAttributes[name] = {this, (int) i, STEP_PER_VERTEX, true};
+		attachedAttributes.push_back({name, vertexBuffer, (int) i, STEP_PER_VERTEX, true});
 	}
 }
 
-void Mesh::calculateAttributeSizes(Graphics *gfx)
+int Mesh::getAttachedAttributeIndex(const std::string &name) const
 {
-	bool supportsGLSL3 = gfx->getCapabilities().features[Graphics::FEATURE_GLSL3];
-
-	size_t stride = 0;
-
-	for (const AttribFormat &format : vertexFormat)
+	for (int i = 0; i < (int) attachedAttributes.size(); i++)
 	{
-		size_t size = vertex::getDataTypeSize(format.type) * format.components;
-
-		if (format.components <= 0 || format.components > 4)
-			throw love::Exception("Vertex attributes must have between 1 and 4 components.");
-
-		// Hardware really doesn't like attributes that aren't 32 bit-aligned.
-		if (size % 4 != 0)
-			throw love::Exception("Vertex attributes must have enough components to be a multiple of 32 bits.");
-
-		if (vertex::isDataTypeInteger(format.type) && !supportsGLSL3)
-			throw love::Exception("Integer vertex attribute data types require GLSL 3 support.");
-
-		// Total size in bytes of each attribute in a single vertex.
-		attributeSizes.push_back(size);
-		stride += size;
+		if (attachedAttributes[i].name == name)
+			return i;
 	}
 
-	vertexStride = stride;
-}
-
-size_t Mesh::getAttributeOffset(size_t attribindex) const
-{
-	size_t offset = 0;
-
-	for (size_t i = 0; i < attribindex; i++)
-		offset += attributeSizes[i];
-
-	return offset;
+	return -1;
 }
 
 void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
@@ -186,6 +171,9 @@ 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);
 
@@ -200,6 +188,9 @@ size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize)
 	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.");
+
 	size_t offset = vertindex * vertexStride;
 	size_t size = std::min(datasize, vertexStride);
 
@@ -223,8 +214,13 @@ void Mesh::setVertexAttribute(size_t vertindex, int attribindex, const void *dat
 	if (attribindex >= (int) vertexFormat.size())
 		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
 
-	size_t offset = vertindex * vertexStride + getAttributeOffset(attribindex);
-	size_t size = std::min(datasize, attributeSizes[attribindex]);
+	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);
@@ -240,8 +236,13 @@ size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, s
 	if (attribindex >= (int) vertexFormat.size())
 		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
 
-	size_t offset = vertindex * vertexStride + getAttributeOffset(attribindex);
-	size_t size = std::min(datasize, attributeSizes[attribindex]);
+	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();
@@ -260,128 +261,106 @@ size_t Mesh::getVertexStride() const
 	return vertexStride;
 }
 
-const std::vector<Mesh::AttribFormat> &Mesh::getVertexFormat() const
+Buffer *Mesh::getVertexBuffer() const
 {
-	return vertexFormat;
-}
-
-vertex::DataType Mesh::getAttributeInfo(int attribindex, int &components) const
-{
-	if (attribindex < 0 || attribindex >= (int) vertexFormat.size())
-		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
-
-	components = vertexFormat[attribindex].components;
-	return vertexFormat[attribindex].type;
+	return vertexBuffer;
 }
 
-int Mesh::getAttributeIndex(const std::string &name) const
+const std::vector<Buffer::DataMember> &Mesh::getVertexFormat() const
 {
-	for (int i = 0; i < (int) vertexFormat.size(); i++)
-	{
-		if (vertexFormat[i].name == name)
-			return i;
-	}
-
-	return -1;
+	return vertexFormat;
 }
 
 void Mesh::setAttributeEnabled(const std::string &name, bool enable)
 {
-	auto it = attachedAttributes.find(name);
-
-	if (it == attachedAttributes.end())
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
 		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
 
-	it->second.enabled = enable;
+	attachedAttributes[index].enabled = enable;
 }
 
 bool Mesh::isAttributeEnabled(const std::string &name) const
 {
-	const auto it = attachedAttributes.find(name);
-
-	if (it == attachedAttributes.end())
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
 		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
 
-	return it->second.enabled;
+	return attachedAttributes[index].enabled;
 }
 
-void Mesh::attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname, AttributeStep step)
+void Mesh::attachAttribute(const std::string &name, Buffer *buffer, 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.");
+
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	if (step == STEP_PER_INSTANCE && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
 		throw love::Exception("Vertex attribute instancing is not supported on this system.");
 
-	if (mesh != this)
-	{
-		for (const auto &it : mesh->attachedAttributes)
-		{
-			// If the supplied Mesh has attached attributes of its own, then we
-			// prevent it from being attached to avoid reference cycles.
-			if (it.second.mesh != mesh)
-				throw love::Exception("Cannot attach a Mesh which has attached Meshes of its own.");
-		}
-	}
-
-	AttachedAttribute oldattrib = {};
-	AttachedAttribute newattrib = {};
+	BufferAttribute oldattrib = {};
+	BufferAttribute newattrib = {};
 
-	auto it = attachedAttributes.find(name);
-	if (it != attachedAttributes.end())
-		oldattrib = it->second;
-	else if (attachedAttributes.size() + 1 > vertex::Attributes::MAX)
-		throw love::Exception("A maximum of %d attributes can be attached at once.", vertex::Attributes::MAX);
+	int oldindex = getAttachedAttributeIndex(name);
+	if (oldindex != -1)
+		oldattrib = attachedAttributes[oldindex];
+	else if (attachedAttributes.size() + 1 > VertexAttributes::MAX)
+		throw love::Exception("A maximum of %d attributes can be attached at once.", VertexAttributes::MAX);
 
-	newattrib.mesh = mesh;
-	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
-	newattrib.index = mesh->getAttributeIndex(attachname);
+	newattrib.name = name;
+	newattrib.buffer = buffer;
+	newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true;
+	newattrib.indexInBuffer = buffer->getDataMemberIndex(attachname);
 	newattrib.step = step;
 
-	if (newattrib.index < 0)
-		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", attachname.c_str());
-
-	if (newattrib.mesh != this)
-		newattrib.mesh->retain();
+	if (newattrib.indexInBuffer < 0)
+		throw love::Exception("The specified vertex buffer does not have a vertex attribute named '%s'", attachname.c_str());
 
-	attachedAttributes[name] = newattrib;
-
-	if (oldattrib.mesh && oldattrib.mesh != this)
-		oldattrib.mesh->release();
+	if (oldindex != -1)
+		attachedAttributes[oldindex] = newattrib;
+	else
+		attachedAttributes.push_back(newattrib);
 }
 
 bool Mesh::detachAttribute(const std::string &name)
 {
-	auto it = attachedAttributes.find(name);
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
+		return false;
 
-	if (it != attachedAttributes.end() && it->second.mesh != this)
-	{
-		it->second.mesh->release();
-		attachedAttributes.erase(it);
+	attachedAttributes.erase(attachedAttributes.begin() + index);
 
-		if (getAttributeIndex(name) != -1)
-			attachAttribute(name, this, name);
+	if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(name) != -1)
+		attachAttribute(name, vertexBuffer, name);
 
-		return true;
-	}
+	return true;
+}
 
-	return false;
+const std::vector<Mesh::BufferAttribute> &Mesh::getAttachedAttributes() const
+{
+	return attachedAttributes;
 }
 
 void *Mesh::mapVertexData()
 {
-	return vertexBuffer->map();
+	return vertexBuffer.get() != nullptr ? vertexBuffer->map() : nullptr;
 }
 
 void Mesh::unmapVertexData(size_t modifiedoffset, size_t modifiedsize)
 {
+	if (!vertexBuffer.get())
+		return;
+
 	vertexBuffer->setMappedRangeModified(modifiedoffset, modifiedsize);
 	vertexBuffer->unmap();
 }
 
 void Mesh::flush()
 {
-	vertexBuffer->unmap();
+	if (vertexBuffer.get())
+		vertexBuffer->unmap();
 
-	if (indexBuffer != nullptr)
+	if (indexBuffer.get())
 		indexBuffer->unmap();
 }
 
@@ -391,7 +370,7 @@ void Mesh::flush()
 template <typename T>
 static void copyToIndexBuffer(const std::vector<uint32> &indices, Buffer::Mapper &buffermap, size_t maxval)
 {
-	T *elems = (T *) buffermap.get();
+	T *elems = (T *) buffermap.data;
 
 	for (size_t i = 0; i < indices.size(); i++)
 	{
@@ -406,21 +385,18 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 {
 	size_t maxval = getVertexCount();
 
-	IndexDataType datatype = vertex::getIndexDataTypeFromMax(maxval);
+	IndexDataType datatype = getIndexDataTypeFromMax(maxval);
+	DataFormat dataformat = getIndexDataFormat(datatype);
 
 	// Calculate the size in bytes of the index buffer data.
-	size_t size = map.size() * vertex::getIndexDataSize(datatype);
-
-	if (indexBuffer && size > indexBuffer->getSize())
-	{
-		delete indexBuffer;
-		indexBuffer = nullptr;
-	}
+	size_t size = map.size() * getIndexDataSize(datatype);
 
-	if (!indexBuffer && size > 0)
+	if (indexBuffer.get() == nullptr || size > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		indexBuffer = gfx->newBuffer(size, nullptr, BUFFER_INDEX, vertexBuffer->getUsage(), Buffer::MAP_READ);
+		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
+		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage);
+		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
 	}
 
 	useIndexBuffer = true;
@@ -448,25 +424,23 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 
 void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasize)
 {
-	if (indexBuffer && datasize > indexBuffer->getSize())
-	{
-		delete indexBuffer;
-		indexBuffer = nullptr;
-	}
+	DataFormat dataformat = getIndexDataFormat(datatype);
 
-	if (!indexBuffer && datasize > 0)
+	if (indexBuffer.get() == nullptr || datasize > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		indexBuffer = gfx->newBuffer(datasize, nullptr, BUFFER_INDEX, vertexBuffer->getUsage(), Buffer::MAP_READ);
+		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
+		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage);
+		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
 	}
 
-	indexCount = datasize / vertex::getIndexDataSize(datatype);
+	indexCount = datasize / getIndexDataSize(datatype);
 
 	if (!indexBuffer || indexCount == 0)
 		return;
 
 	Buffer::Mapper ibomap(*indexBuffer);
-	memcpy(ibomap.get(), data, datasize);
+	memcpy(ibomap.data, data, datasize);
 
 	useIndexBuffer = true;
 	indexDataType = datatype;
@@ -499,6 +473,9 @@ bool Mesh::getVertexMap(std::vector<uint32> &map) const
 	if (!indexBuffer || 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();
 
@@ -517,7 +494,27 @@ bool Mesh::getVertexMap(std::vector<uint32> &map) const
 	return true;
 }
 
-size_t Mesh::getVertexMapCount() const
+void Mesh::setIndexBuffer(Buffer *buffer)
+{
+	// Buffer constructor does the rest of the validation for index buffers
+	// (data member formats, etc.)
+	if (buffer != nullptr && (buffer->getTypeFlags() & Buffer::TYPEFLAG_INDEX) == 0)
+		throw love::Exception("setIndexBuffer requires a Buffer created as an index buffer.");
+
+	indexBuffer.set(buffer);
+	useIndexBuffer = buffer != nullptr;
+	indexCount = buffer != nullptr ? buffer->getArrayLength() : 0;
+
+	if (buffer != nullptr)
+		indexDataType = getIndexDataType(buffer->getDataMember(0).decl.format);
+}
+
+Buffer *Mesh::getIndexBuffer() const
+{
+	return indexBuffer;
+}
+
+size_t Mesh::getIndexCount() const
 {
 	return indexCount;
 }
@@ -592,43 +589,42 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 	if (Shader::current && texture.get())
 		Shader::current->checkMainTexture(texture);
 
-	vertex::Attributes attributes;
-	vertex::BufferBindings buffers;
+	VertexAttributes attributes;
+	BufferBindings buffers;
 
 	int activebuffers = 0;
 
 	for (const auto &attrib : attachedAttributes)
 	{
-		if (!attrib.second.enabled)
+		if (!attrib.enabled)
 			continue;
 
-		Mesh *mesh = attrib.second.mesh;
+		Buffer *buffer = attrib.buffer.get();
 		int attributeindex = -1;
 
 		// If the attribute is one of the LOVE-defined ones, use the constant
 		// attribute index for it, otherwise query the index from the shader.
 		BuiltinVertexAttribute builtinattrib;
-		if (vertex::getConstant(attrib.first.c_str(), builtinattrib))
+		if (getConstant(attrib.name.c_str(), builtinattrib))
 			attributeindex = (int) builtinattrib;
 		else if (Shader::current)
-			attributeindex = Shader::current->getVertexAttributeIndex(attrib.first);
+			attributeindex = Shader::current->getVertexAttributeIndex(attrib.name);
 
 		if (attributeindex >= 0)
 		{
 			// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-			mesh->vertexBuffer->unmap();
+			buffer->unmap();
 
-			const auto &formats = mesh->getVertexFormat();
-			const auto &format = formats[attrib.second.index];
+			const auto &member = buffer->getDataMember(attrib.indexInBuffer);
 
-			uint16 offset = (uint16) mesh->getAttributeOffset(attrib.second.index);
-			uint16 stride = (uint16) mesh->getVertexStride();
+			uint16 offset = (uint16) member.offset;
+			uint16 stride = (uint16) buffer->getArrayStride();
 
-			attributes.set(attributeindex, format.type, (uint8) format.components, offset, activebuffers);
-			attributes.setBufferLayout(activebuffers, stride, attrib.second.step);
+			attributes.set(attributeindex, member.decl.format, offset, activebuffers);
+			attributes.setBufferLayout(activebuffers, stride, attrib.step);
 
 			// TODO: Ideally we want to reuse buffers with the same stride+step.
-			buffers.set(activebuffers, mesh->vertexBuffer, 0);
+			buffers.set(activebuffers, buffer, 0);
 			activebuffers++;
 		}
 	}
@@ -653,7 +649,7 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 		cmd.cullMode = gfx->getMeshCullMode();
 
 		int start = std::min(std::max(0, rangeStart), (int) indexCount - 1);
-		cmd.indexBufferOffset = start * vertex::getIndexDataSize(indexDataType);
+		cmd.indexBufferOffset = start * getIndexDataSize(indexDataType);
 
 		cmd.indexCount = (int) indexCount;
 		if (rangeCount > 0)

+ 30 - 30
src/modules/graphics/Mesh.h

@@ -50,17 +50,20 @@ class Mesh : public Drawable
 {
 public:
 
-	static love::Type type;
-
-	struct AttribFormat
+	struct BufferAttribute
 	{
 		std::string name;
-		vertex::DataType type;
-		int components; // max 4
+		StrongRef<Buffer> buffer;
+		int indexInBuffer;
+		AttributeStep step;
+		bool enabled;
 	};
 
-	Mesh(Graphics *gfx, const std::vector<AttribFormat> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, vertex::Usage usage);
-	Mesh(Graphics *gfx, const std::vector<AttribFormat> &vertexformat, int vertexcount, PrimitiveType drawmode, vertex::Usage usage);
+	static love::Type type;
+
+	Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage);
+	Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage);
+	Mesh(const std::vector<BufferAttribute> &attributes, PrimitiveType drawmode);
 
 	virtual ~Mesh();
 
@@ -92,12 +95,15 @@ public:
 	 **/
 	size_t getVertexStride() const;
 
+	/**
+	 * Gets the Buffer that holds the Mesh's vertices.
+	 **/
+	Buffer *getVertexBuffer() const;
+
 	/**
 	 * Gets the format of each vertex attribute stored in the Mesh.
 	 **/
-	const std::vector<AttribFormat> &getVertexFormat() const;
-	vertex::DataType getAttributeInfo(int attribindex, int &components) const;
-	int getAttributeIndex(const std::string &name) const;
+	const std::vector<Buffer::DataMember> &getVertexFormat() const;
 
 	/**
 	 * Sets whether a specific vertex attribute is used when drawing the Mesh.
@@ -106,11 +112,12 @@ public:
 	bool isAttributeEnabled(const std::string &name) const;
 
 	/**
-	 * Attaches a vertex attribute from another Mesh to this one. The attribute
-	 * will be used when drawing this Mesh.
+	 * Attaches a vertex attribute from another vertex buffer to this Mesh. The
+	 * attribute will be used when drawing this Mesh.
 	 **/
-	void attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX);
+	void attachAttribute(const std::string &name, Buffer *buffer, 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);
@@ -136,10 +143,13 @@ public:
 	 **/
 	bool getVertexMap(std::vector<uint32> &map) const;
 
+	void setIndexBuffer(Buffer *buffer);
+	Buffer *getIndexBuffer() const;
+
 	/**
 	 * Gets the total number of elements in the vertex map array.
 	 **/
-	size_t getVertexMapCount() const;
+	size_t getIndexCount() const;
 
 	/**
 	 * Sets the texture used when drawing the Mesh.
@@ -172,31 +182,21 @@ public:
 
 	void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount);
 
-	static std::vector<AttribFormat> getDefaultVertexFormat();
+	static std::vector<Buffer::DataDeclaration> getDefaultVertexFormat();
 
 private:
 
 	friend class SpriteBatch;
 
-	struct AttachedAttribute
-	{
-		Mesh *mesh;
-		int index;
-		AttributeStep step;
-		bool enabled;
-	};
-
 	void setupAttachedAttributes();
-	void calculateAttributeSizes(Graphics *gfx);
-	size_t getAttributeOffset(size_t attribindex) const;
+	int getAttachedAttributeIndex(const std::string &name) const;
 
-	std::vector<AttribFormat> vertexFormat;
-	std::vector<size_t> attributeSizes;
+	std::vector<Buffer::DataMember> vertexFormat;
 
-	std::unordered_map<std::string, AttachedAttribute> attachedAttributes;
+	std::vector<BufferAttribute> attachedAttributes;
 
 	// Vertex buffer, for the vertex data.
-	Buffer *vertexBuffer;
+	StrongRef<Buffer> vertexBuffer;
 	size_t vertexCount;
 	size_t vertexStride;
 
@@ -205,7 +205,7 @@ private:
 	char *vertexScratchBuffer;
 
 	// Index buffer, for the vertex map.
-	Buffer *indexBuffer;
+	StrongRef<Buffer> indexBuffer;
 	bool useIndexBuffer;
 	size_t indexCount;
 	IndexDataType indexDataType;

+ 7 - 4
src/modules/graphics/ParticleSystem.cpp

@@ -93,7 +93,7 @@ ParticleSystem::ParticleSystem(Texture *texture, uint32 size)
 	, offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f)
 	, defaultOffset(true)
 	, relativeRotation(false)
-	, vertexAttributes(vertex::CommonFormat::XYf_STf_RGBAub, 0)
+	, vertexAttributes(CommonFormat::XYf_STf_RGBAub, 0)
 	, buffer(nullptr)
 {
 	if (size == 0 || size > MAX_PARTICLES)
@@ -191,7 +191,9 @@ void ParticleSystem::createBuffers(size_t size)
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 
 		size_t bytes = sizeof(Vertex) * size * 4;
-		buffer = gfx->newBuffer(bytes, nullptr, BUFFER_VERTEX, vertex::USAGE_STREAM, 0);
+		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, 0, BUFFERUSAGE_STREAM);
+		auto decl = Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
+		buffer = gfx->newBuffer(settings, decl, nullptr, bytes, 0);
 	}
 	catch (std::bad_alloc &)
 	{
@@ -203,7 +205,8 @@ void ParticleSystem::createBuffers(size_t size)
 void ParticleSystem::deleteBuffers()
 {
 	delete[] pMem;
-	delete buffer;
+	if (buffer)
+		buffer->release();
 
 	pMem = nullptr;
 	buffer = nullptr;
@@ -1080,7 +1083,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 
 	Graphics::TempTransform transform(gfx, m);
 
-	vertex::BufferBindings vertexbuffers;
+	BufferBindings vertexbuffers;
 	vertexbuffers.set(0, buffer, 0);
 
 	gfx->drawQuads(0, pCount, vertexAttributes, vertexbuffers, texture);

+ 1 - 1
src/modules/graphics/ParticleSystem.h

@@ -673,7 +673,7 @@ private:
 
 	bool relativeRotation;
 
-	const vertex::Attributes vertexAttributes;
+	const VertexAttributes vertexAttributes;
 	Buffer *buffer;
 
 	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];

+ 4 - 4
src/modules/graphics/Polyline.cpp

@@ -82,7 +82,7 @@ void Polyline::render(const Vector2 *coords, size_t count, size_t size_hint, flo
 		// extra degenerate triangle in between the core line and the overdraw
 		// line in order to break up the strip into two. This will let us draw
 		// everything in one draw call.
-		if (triangle_mode == vertex::TriangleIndexMode::STRIP)
+		if (triangle_mode == TRIANGLEINDEX_STRIP)
 			extra_vertices = 2;
 	}
 
@@ -383,7 +383,7 @@ void Polyline::draw(love::graphics::Graphics *gfx)
 	int maxvertices = LOVE_UINT16_MAX - 3;
 
 	int advance = maxvertices;
-	if (triangle_mode == vertex::TriangleIndexMode::STRIP)
+	if (triangle_mode == TRIANGLEINDEX_STRIP)
 		advance -= 2;
 
 	for (int vertex_start = 0; vertex_start < total_vertex_count; vertex_start += advance)
@@ -391,8 +391,8 @@ void Polyline::draw(love::graphics::Graphics *gfx)
 		const Vector2 *verts = vertices + vertex_start;
 
 		Graphics::BatchedDrawCommand cmd;
-		cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
-		cmd.formats[1] = vertex::CommonFormat::RGBAub;
+		cmd.formats[0] = getSinglePositionFormat(is2D);
+		cmd.formats[1] = CommonFormat::RGBAub;
 		cmd.indexMode = triangle_mode;
 		cmd.vertexCount = std::min(maxvertices, total_vertex_count - vertex_start);
 

+ 3 - 3
src/modules/graphics/Polyline.h

@@ -44,7 +44,7 @@ class Polyline
 {
 public:
 
-	Polyline(vertex::TriangleIndexMode mode = vertex::TriangleIndexMode::STRIP)
+	Polyline(TriangleIndexMode mode = TRIANGLEINDEX_STRIP)
 		: vertices(nullptr)
 		, overdraw(nullptr)
 		, vertex_count(0)
@@ -94,7 +94,7 @@ protected:
 	Vector2 *overdraw;
 	size_t vertex_count;
 	size_t overdraw_vertex_count;
-	vertex::TriangleIndexMode triangle_mode;
+	TriangleIndexMode triangle_mode;
 	size_t overdraw_vertex_start;
 
 }; // Polyline
@@ -109,7 +109,7 @@ class NoneJoinPolyline : public Polyline
 public:
 
 	NoneJoinPolyline()
-		: Polyline(vertex::TriangleIndexMode::QUADS)
+		: Polyline(TRIANGLEINDEX_QUADS)
 	{}
 
 	void render(const Vector2 *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)

+ 0 - 5
src/modules/graphics/Shader.h

@@ -33,11 +33,6 @@
 #include <vector>
 #include <stddef.h>
 
-namespace glslang
-{
-class TShader;
-}
-
 namespace love
 {
 namespace graphics

+ 30 - 30
src/modules/graphics/SpriteBatch.cpp

@@ -40,7 +40,7 @@ namespace graphics
 
 love::Type SpriteBatch::type("SpriteBatch", &Drawable::type);
 
-SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usage usage)
+SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage usage)
 	: texture(texture)
 	, size(size)
 	, next(0)
@@ -57,14 +57,16 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usag
 		throw love::Exception("A texture must be used when creating a SpriteBatch.");
 
 	if (texture->getTextureType() == TEXTURE_2D_ARRAY)
-		vertex_format = vertex::CommonFormat::XYf_STPf_RGBAub;
+		vertex_format = CommonFormat::XYf_STPf_RGBAub;
 	else
-		vertex_format = vertex::CommonFormat::XYf_STf_RGBAub;
+		vertex_format = CommonFormat::XYf_STf_RGBAub;
 
-	vertex_stride = vertex::getFormatStride(vertex_format);
+	vertex_stride = getFormatStride(vertex_format);
 
 	size_t vertex_size = vertex_stride * 4 * size;
-	array_buf = gfx->newBuffer(vertex_size, nullptr, BUFFER_VERTEX, usage, Buffer::MAP_EXPLICIT_RANGE_MODIFY);
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, usage);
+	auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
+	array_buf = gfx->newBuffer(settings, decl, nullptr, vertex_size, 0);
 }
 
 SpriteBatch::~SpriteBatch()
@@ -79,8 +81,6 @@ int SpriteBatch::add(const Matrix4 &m, int index /*= -1*/)
 
 int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/)
 {
-	using namespace vertex;
-
 	if (vertex_format == CommonFormat::XYf_STPf_RGBAub)
 		return addLayer(quad->getLayer(), quad, m, index);
 
@@ -122,8 +122,6 @@ int SpriteBatch::addLayer(int layer, const Matrix4 &m, int index)
 
 int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index)
 {
-	using namespace vertex;
-
 	if (vertex_format != CommonFormat::XYf_STPf_RGBAub)
 		throw love::Exception("addLayer can only be called on a SpriteBatch that uses an Array Texture!");
 
@@ -222,7 +220,9 @@ void SpriteBatch::setBufferSize(int newsize)
 	try
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		new_array_buf = gfx->newBuffer(vertex_size, nullptr, array_buf->getType(), array_buf->getUsage(), array_buf->getMapFlags());
+		Buffer::Settings settings(array_buf->getTypeFlags(), array_buf->getMapFlags(), array_buf->getUsage());
+		auto decl = Buffer::getCommonFormatDeclaration(vertex_format);
+		new_array_buf = gfx->newBuffer(settings, decl, nullptr, vertex_size, 0);
 
 		// Copy as much of the old data into the new GLBuffer as can fit.
 		size_t copy_size = vertex_stride * 4 * new_next;
@@ -248,24 +248,27 @@ int SpriteBatch::getBufferSize() const
 	return size;
 }
 
-void SpriteBatch::attachAttribute(const std::string &name, Mesh *mesh)
+void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer)
 {
+	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.");
+
 	AttachedAttribute oldattrib = {};
 	AttachedAttribute newattrib = {};
 
-	if (mesh->getVertexCount() < (size_t) next * 4)
-		throw love::Exception("Mesh has too few vertices to be attached to this SpriteBatch (at least %d vertices are required)", next*4);
+	if (buffer->getArrayLength() < (size_t) next * 4)
+		throw love::Exception("Buffer has too few vertices to be attached to this SpriteBatch (at least %d vertices are required)", next*4);
 
 	auto it = attached_attributes.find(name);
 	if (it != attached_attributes.end())
 		oldattrib = it->second;
 
-	newattrib.index = mesh->getAttributeIndex(name);
+	newattrib.index = buffer->getDataMemberIndex(name);
 
 	if (newattrib.index < 0)
-		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
+		throw love::Exception("The specified Buffer does not have a vertex attribute named '%s'", name.c_str());
 
-	newattrib.mesh = mesh;
+	newattrib.buffer = buffer;
 
 	attached_attributes[name] = newattrib;
 }
@@ -296,8 +299,6 @@ bool SpriteBatch::getDrawRange(int &start, int &count) const
 
 void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 {
-	using namespace vertex;
-
 	if (next == 0)
 		return;
 
@@ -321,7 +322,7 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 	// Make sure the buffer isn't mapped when we draw (sends data to GPU if needed.)
 	array_buf->unmap();
 
-	Attributes attributes;
+	VertexAttributes attributes;
 	BufferBindings buffers;
 
 	{
@@ -333,19 +334,19 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 
 	for (const auto &it : attached_attributes)
 	{
-		Mesh *mesh = it.second.mesh.get();
+		Buffer *buffer = it.second.buffer.get();
 
 		// We have to do this check here as wll because setBufferSize can be
 		// called after attachAttribute.
-		if (mesh->getVertexCount() < (size_t) next * 4)
-			throw love::Exception("Mesh with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str());
+		if (buffer->getArrayLength() < (size_t) next * 4)
+			throw love::Exception("Buffer with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str());
 
 		int attributeindex = -1;
 
 		// If the attribute is one of the LOVE-defined ones, use the constant
 		// attribute index for it, otherwise query the index from the shader.
 		BuiltinVertexAttribute builtinattrib;
-		if (vertex::getConstant(it.first.c_str(), builtinattrib))
+		if (getConstant(it.first.c_str(), builtinattrib))
 			attributeindex = (int) builtinattrib;
 		else if (Shader::current)
 			attributeindex = Shader::current->getVertexAttributeIndex(it.first);
@@ -353,19 +354,18 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 		if (attributeindex >= 0)
 		{
 			// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-			mesh->vertexBuffer->unmap();
+			buffer->unmap();
 
-			const auto &formats = mesh->getVertexFormat();
-			const auto &format = formats[it.second.index];
+			const auto &member = buffer->getDataMember(it.second.index);
 
-			uint16 offset = (uint16) mesh->getAttributeOffset(it.second.index);
-			uint16 stride = (uint16) mesh->getVertexStride();
+			uint16 offset = (uint16) buffer->getMemberOffset(it.second.index);
+			uint16 stride = (uint16) buffer->getArrayStride();
 
-			attributes.set(attributeindex, format.type, (uint8) format.components, offset, activebuffers);
+			attributes.set(attributeindex, member.decl.format, offset, activebuffers);
 			attributes.setBufferLayout(activebuffers, stride);
 
 			// TODO: We should reuse buffer bindings with the same buffer+stride+step.
-			buffers.set(activebuffers, mesh->vertexBuffer, 0);
+			buffers.set(activebuffers, buffer, 0);
 			activebuffers++;
 		}
 	}

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

@@ -51,7 +51,7 @@ public:
 
 	static love::Type type;
 
-	SpriteBatch(Graphics *gfx, Texture *texture, int size, vertex::Usage usage);
+	SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage usage);
 	virtual ~SpriteBatch();
 
 	int add(const Matrix4 &m, int index = -1);
@@ -93,7 +93,7 @@ public:
 	 * Attaches a specific vertex attribute from a Mesh to this SpriteBatch.
 	 * The vertex attribute will be used when drawing the SpriteBatch.
 	 **/
-	void attachAttribute(const std::string &name, Mesh *mesh);
+	void attachAttribute(const std::string &name, Buffer *buffer);
 
 	void setDrawRange(int start, int count);
 	void setDrawRange();
@@ -106,7 +106,7 @@ private:
 
 	struct AttachedAttribute
 	{
-		StrongRef<Mesh> mesh;
+		StrongRef<Buffer> buffer;
 		int index;
 	};
 
@@ -128,7 +128,7 @@ private:
 	Color32 color;
 	Colorf colorf;
 
-	vertex::CommonFormat vertex_format;
+	CommonFormat vertex_format;
 	size_t vertex_stride;
 	
 	love::graphics::Buffer *array_buf;

+ 6 - 3
src/modules/graphics/Text.cpp

@@ -42,7 +42,8 @@ Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
 
 Text::~Text()
 {
-	delete vertex_buffer;
+	if (vertex_buffer)
+		vertex_buffer->release();
 }
 
 void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset)
@@ -60,12 +61,14 @@ void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t
 			newsize = std::max(size_t(vertex_buffer->getSize() * 1.5), newsize);
 
 		auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-		Buffer *new_buffer = gfx->newBuffer(newsize, nullptr, BUFFER_VERTEX, vertex::USAGE_DYNAMIC, 0);
+		Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, 0, BUFFERUSAGE_DYNAMIC);
+		auto decl = Buffer::getCommonFormatDeclaration(Font::vertexFormat);
+		Buffer *new_buffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0);
 
 		if (vertex_buffer != nullptr)
 			vertex_buffer->copyTo(0, vertex_buffer->getSize(), new_buffer, 0);
 
-		delete vertex_buffer;
+		vertex_buffer->release();
 		vertex_buffer = new_buffer;
 
 		vertexBuffers.set(0, vertex_buffer, 0);

+ 2 - 2
src/modules/graphics/Text.h

@@ -85,8 +85,8 @@ private:
 
 	StrongRef<Font> font;
 
-	vertex::Attributes vertexAttributes;
-	vertex::BufferBindings vertexBuffers;
+	VertexAttributes vertexAttributes;
+	BufferBindings vertexBuffers;
 
 	Buffer *vertex_buffer;
 

+ 6 - 10
src/modules/graphics/Texture.cpp

@@ -309,8 +309,6 @@ void Texture::draw(Graphics *gfx, const Matrix4 &m)
 
 void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
 {
-	using namespace vertex;
-
 	if (!readable)
 		throw love::Exception("Textures with non-readable formats cannot be drawn.");
 
@@ -327,9 +325,9 @@ void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
 	bool is2D = tm.isAffine2DTransform();
 
 	Graphics::BatchedDrawCommand cmd;
-	cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
+	cmd.formats[0] = getSinglePositionFormat(is2D);
 	cmd.formats[1] = CommonFormat::STf_RGBAub;
-	cmd.indexMode = TriangleIndexMode::QUADS;
+	cmd.indexMode = TRIANGLEINDEX_QUADS;
 	cmd.vertexCount = 4;
 	cmd.texture = this;
 
@@ -343,7 +341,7 @@ void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
 		t.transformXY0((Vector3 *) data.stream[0], q->getVertexPositions(), 4);
 
 	const Vector2 *texcoords = q->getVertexTexCoords();
-	vertex::STf_RGBAub *vertexdata = (vertex::STf_RGBAub *) data.stream[1];
+	STf_RGBAub *vertexdata = (STf_RGBAub *) data.stream[1];
 
 	Color32 c = toColor32(gfx->getColor());
 
@@ -362,8 +360,6 @@ void Texture::drawLayer(Graphics *gfx, int layer, const Matrix4 &m)
 
 void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
 {
-	using namespace vertex;
-
 	if (!readable)
 		throw love::Exception("Textures with non-readable formats cannot be drawn.");
 
@@ -384,9 +380,9 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
 	Matrix4 t(tm, m);
 
 	Graphics::BatchedDrawCommand cmd;
-	cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
+	cmd.formats[0] = getSinglePositionFormat(is2D);
 	cmd.formats[1] = CommonFormat::STPf_RGBAub;
-	cmd.indexMode = TriangleIndexMode::QUADS;
+	cmd.indexMode = TRIANGLEINDEX_QUADS;
 	cmd.vertexCount = 4;
 	cmd.texture = this;
 	cmd.standardShaderType = Shader::STANDARD_ARRAY;
@@ -399,7 +395,7 @@ void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
 		t.transformXY0((Vector3 *) data.stream[0], q->getVertexPositions(), 4);
 
 	const Vector2 *texcoords = q->getVertexTexCoords();
-	vertex::STPf_RGBAub *vertexdata = (vertex::STPf_RGBAub *) data.stream[1];
+	STPf_RGBAub *vertexdata = (STPf_RGBAub *) data.stream[1];
 
 	for (int i = 0; i < 4; i++)
 	{

+ 4 - 4
src/modules/graphics/Video.cpp

@@ -121,9 +121,9 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
 	Matrix4 t(tm, m);
 
 	Graphics::BatchedDrawCommand cmd;
-	cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
-	cmd.formats[1] = vertex::CommonFormat::STf_RGBAub;
-	cmd.indexMode = vertex::TriangleIndexMode::QUADS;
+	cmd.formats[0] = getSinglePositionFormat(is2D);
+	cmd.formats[1] = CommonFormat::STf_RGBAub;
+	cmd.indexMode = TRIANGLEINDEX_QUADS;
 	cmd.vertexCount = 4;
 	cmd.standardShaderType = Shader::STANDARD_VIDEO;
 
@@ -134,7 +134,7 @@ void Video::draw(Graphics *gfx, const Matrix4 &m)
 	else
 		t.transformXY0((Vector3 *) data.stream[0], vertices, 4);
 
-	vertex::STf_RGBAub *verts = (vertex::STf_RGBAub *) data.stream[1];
+	STf_RGBAub *verts = (STf_RGBAub *) data.stream[1];
 
 	Color32 c = toColor32(gfx->getColor());
 

+ 93 - 83
src/modules/graphics/opengl/Buffer.cpp

@@ -35,18 +35,22 @@ namespace graphics
 namespace opengl
 {
 
-Buffer::Buffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags)
-	: love::graphics::Buffer(size, type, usage, mapflags)
-	, vbo(0)
-	, memory_map(nullptr)
-	, modified_offset(0)
-	, modified_size(0)
+Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &format, const void *data, size_t size, size_t arraylength)
+	: love::graphics::Buffer(gfx, settings, format, size, arraylength)
 {
-	target = OpenGL::getGLBufferType(type);
+	size = getSize();
+	arraylength = getArrayLength();
+
+	if (typeFlags & TYPEFLAG_VERTEX)
+		mapType = BUFFERTYPE_VERTEX;
+	else if (typeFlags & TYPEFLAG_INDEX)
+		mapType = BUFFERTYPE_INDEX;
+
+	target = OpenGL::getGLBufferType(mapType);
 
 	try
 	{
-		memory_map = new char[size];
+		memoryMap = new char[size];
 	}
 	catch (std::bad_alloc &)
 	{
@@ -54,34 +58,63 @@ Buffer::Buffer(size_t size, const void *data, BufferType type, vertex::Usage usa
 	}
 
 	if (data != nullptr)
-		memcpy(memory_map, data, size);
+		memcpy(memoryMap, data, size);
 
 	if (!load(data != nullptr))
 	{
-		delete[] memory_map;
+		delete[] memoryMap;
 		throw love::Exception("Could not load vertex buffer (out of VRAM?)");
 	}
 }
 
 Buffer::~Buffer()
 {
+	unloadVolatile();
+	delete[] memoryMap;
+}
+
+bool Buffer::loadVolatile()
+{
+	return load(true);
+}
+
+void Buffer::unloadVolatile()
+{
+	mapped = false;
 	if (vbo != 0)
-		unload();
+		gl.deleteBuffer(vbo);
+	vbo = 0;
+}
 
-	delete[] memory_map;
+bool Buffer::load(bool restore)
+{
+	glGenBuffers(1, &vbo);
+	gl.bindBuffer(mapType, vbo);
+
+	while (glGetError() != GL_NO_ERROR)
+		/* Clear the error 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()));
+
+	return (glGetError() == GL_NO_ERROR);
 }
 
 void *Buffer::map()
 {
-	if (is_mapped)
-		return memory_map;
+	if (mapped)
+		return memoryMap;
 
-	is_mapped = true;
+	mapped = true;
 
-	modified_offset = 0;
-	modified_size = 0;
+	modifiedOffset = 0;
+	modifiedSize = 0;
+	isMappedDataModified = false;
 
-	return memory_map;
+	return memoryMap;
 }
 
 void Buffer::unmapStatic(size_t offset, size_t size)
@@ -90,8 +123,8 @@ void Buffer::unmapStatic(size_t offset, size_t size)
 		return;
 
 	// Upload the mapped data to the buffer.
-	gl.bindBuffer(type, vbo);
-	glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memory_map + offset);
+	gl.bindBuffer(mapType, vbo);
+	glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memoryMap + offset);
 }
 
 void Buffer::unmapStream()
@@ -100,87 +133,98 @@ void Buffer::unmapStream()
 
 	// "orphan" current buffer to avoid implicit synchronisation on the GPU:
 	// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
-	gl.bindBuffer(type, vbo);
+	gl.bindBuffer(mapType, vbo);
 	glBufferData(target, (GLsizeiptr) getSize(), nullptr, glusage);
 
 #if LOVE_WINDOWS
 	// TODO: Verify that this codepath is a useful optimization.
 	if (gl.getVendor() == OpenGL::VENDOR_INTEL)
-		glBufferData(target, (GLsizeiptr) getSize(), memory_map, glusage);
+		glBufferData(target, (GLsizeiptr) getSize(), memoryMap, glusage);
 	else
 #endif
-		glBufferSubData(target, 0, (GLsizeiptr) getSize(), memory_map);
+		glBufferSubData(target, 0, (GLsizeiptr) getSize(), memoryMap);
 }
 
 void Buffer::unmap()
 {
-	if (!is_mapped)
+	if (!mapped)
 		return;
 
-	if ((map_flags & MAP_EXPLICIT_RANGE_MODIFY) != 0)
+	mapped = false;
+
+	if ((mapFlags & MAP_EXPLICIT_RANGE_MODIFY) != 0)
 	{
-		modified_offset = std::min(modified_offset, getSize() - 1);
-		modified_size = std::min(modified_size, getSize() - modified_offset);
+		if (!isMappedDataModified)
+			return;
+
+		modifiedOffset = std::min(modifiedOffset, getSize() - 1);
+		modifiedSize = std::min(modifiedSize, getSize() - modifiedOffset);
 	}
 	else
 	{
-		modified_offset = 0;
-		modified_size = getSize();
+		modifiedOffset = 0;
+		modifiedSize = getSize();
 	}
 
-	if (modified_size > 0)
+	if (modifiedSize > 0)
 	{
 		switch (getUsage())
 		{
-		case vertex::USAGE_STATIC:
-			unmapStatic(modified_offset, modified_size);
+		case BUFFERUSAGE_STATIC:
+			unmapStatic(modifiedOffset, modifiedSize);
 			break;
-		case vertex::USAGE_STREAM:
+		case BUFFERUSAGE_STREAM:
 			unmapStream();
 			break;
-		case vertex::USAGE_DYNAMIC:
+		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 (modified_size >= getSize() / 3)
+			if (modifiedSize >= getSize() / 3)
 				unmapStream();
 			else
-				unmapStatic(modified_offset, modified_size);
+				unmapStatic(modifiedOffset, modifiedSize);
 			break;
 		}
 	}
 
-	modified_offset = 0;
-	modified_size = 0;
-
-	is_mapped = false;
+	modifiedOffset = 0;
+	modifiedSize = 0;
 }
 
 void Buffer::setMappedRangeModified(size_t offset, size_t modifiedsize)
 {
-	if (!is_mapped || !(map_flags & MAP_EXPLICIT_RANGE_MODIFY))
+	if (!mapped || !(mapFlags & MAP_EXPLICIT_RANGE_MODIFY))
 		return;
 
+	if (!isMappedDataModified)
+	{
+		modifiedOffset = offset;
+		modifiedSize = modifiedsize;
+		isMappedDataModified = true;
+		return;
+	}
+
 	// 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.
 
-	size_t old_range_end = modified_offset + modified_size;
-	modified_offset = std::min(modified_offset, offset);
+	size_t oldrangeend = modifiedOffset + modifiedSize;
+	modifiedOffset = std::min(modifiedOffset, offset);
 
-	size_t new_range_end = std::max(offset + modifiedsize, old_range_end);
-	modified_size = new_range_end - modified_offset;
+	size_t newrangeend = std::max(offset + modifiedsize, oldrangeend);
+	modifiedSize = newrangeend - modifiedOffset;
 }
 
 void Buffer::fill(size_t offset, size_t size, const void *data)
 {
-	memcpy(memory_map + offset, data, size);
+	memcpy(memoryMap + offset, data, size);
 
-	if (is_mapped)
+	if (mapped)
 		setMappedRangeModified(offset, size);
 	else
 	{
-		gl.bindBuffer(type, vbo);
+		gl.bindBuffer(mapType, vbo);
 		glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data);
 	}
 }
@@ -192,41 +236,7 @@ ptrdiff_t Buffer::getHandle() const
 
 void Buffer::copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset)
 {
-	other->fill(otheroffset, size, memory_map + offset);
-}
-
-bool Buffer::loadVolatile()
-{
-	return load(true);
-}
-
-void Buffer::unloadVolatile()
-{
-	unload();
-}
-
-bool Buffer::load(bool restore)
-{
-	glGenBuffers(1, &vbo);
-	gl.bindBuffer(type, vbo);
-
-	while (glGetError() != GL_NO_ERROR)
-		/* Clear the error buffer. */;
-
-	// Copy the old buffer only if 'restore' was requested.
-	const GLvoid *src = restore ? memory_map : nullptr;
-
-	// Note that if 'src' is '0', no data will be copied.
-	glBufferData(target, (GLsizeiptr) getSize(), src, OpenGL::getGLBufferUsage(getUsage()));
-
-	return (glGetError() == GL_NO_ERROR);
-}
-
-void Buffer::unload()
-{
-	is_mapped = false;
-	gl.deleteBuffer(vbo);
-	vbo = 0;
+	other->fill(otheroffset, size, memoryMap + offset);
 }
 
 } // opengl

+ 15 - 11
src/modules/graphics/opengl/Buffer.h

@@ -32,6 +32,9 @@ namespace love
 {
 namespace graphics
 {
+
+class Graphics;
+
 namespace opengl
 {
 
@@ -39,9 +42,13 @@ class Buffer final : public love::graphics::Buffer, public Volatile
 {
 public:
 
-	Buffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags);
+	Buffer(love::graphics::Graphics *gfx, const Settings &settings, const std::vector<DataDeclaration> &format, const void *data, size_t size, size_t arraylength);
 	virtual ~Buffer();
 
+	// Implements Volatile.
+	bool loadVolatile() override;
+	void unloadVolatile() override;
+
 	void *map() override;
 	void unmap() override;
 	void setMappedRangeModified(size_t offset, size_t size) override;
@@ -50,28 +57,25 @@ public:
 
 	void copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) override;
 
-	// Implements Volatile.
-	bool loadVolatile() override;
-	void unloadVolatile() override;
-
 private:
 
 	bool load(bool restore);
-	void unload();
 
 	void unmapStatic(size_t offset, size_t size);
 	void unmapStream();
 
-	GLenum target;
+	BufferType mapType = BUFFERTYPE_VERTEX;
+	GLenum target = 0;
 
 	// The VBO identifier. Assigned by OpenGL.
-	GLuint vbo;
+	GLuint vbo = 0;
 
 	// A pointer to mapped memory.
-	char *memory_map;
+	char *memoryMap = nullptr;
 
-	size_t modified_offset;
-	size_t modified_size;
+	size_t modifiedOffset = 0;
+	size_t modifiedSize = 0;
+	bool isMappedDataModified = false;
 
 }; // Buffer
 

+ 17 - 17
src/modules/graphics/opengl/Graphics.cpp

@@ -145,9 +145,9 @@ love::graphics::Shader *Graphics::newShaderInternal(love::graphics::ShaderStage
 	return new Shader(vertex, pixel);
 }
 
-love::graphics::Buffer *Graphics::newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags)
+love::graphics::Buffer *Graphics::newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength)
 {
-	return new Buffer(size, data, type, usage, mapflags);
+	return new Buffer(this, settings, format, data, size, arraylength);
 }
 
 void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight)
@@ -238,9 +238,9 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b
 	{
 		// Initial sizes that should be good enough for most cases. It will
 		// resize to fit if needed, later.
-		batchedDrawState.vb[0] = CreateStreamBuffer(BUFFER_VERTEX, 1024 * 1024 * 1);
-		batchedDrawState.vb[1] = CreateStreamBuffer(BUFFER_VERTEX, 256  * 1024 * 1);
-		batchedDrawState.indexBuffer = CreateStreamBuffer(BUFFER_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+		batchedDrawState.vb[0] = CreateStreamBuffer(BUFFERTYPE_VERTEX, 1024 * 1024 * 1);
+		batchedDrawState.vb[1] = CreateStreamBuffer(BUFFERTYPE_VERTEX, 256  * 1024 * 1);
+		batchedDrawState.indexBuffer = CreateStreamBuffer(BUFFERTYPE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
 	}
 
 	// Reload all volatile objects.
@@ -360,7 +360,7 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 	GLenum glprimitivetype = OpenGL::getGLPrimitiveType(cmd.primitiveType);
 	GLenum gldatatype = OpenGL::getGLIndexDataType(cmd.indexType);
 
-	gl.bindBuffer(BUFFER_INDEX, cmd.indexBuffer->getHandle());
+	gl.bindBuffer(BUFFERTYPE_INDEX, cmd.indexBuffer->getHandle());
 
 	if (cmd.instanceCount > 1)
 		glDrawElementsInstanced(glprimitivetype, cmd.indexCount, gldatatype, gloffset, cmd.instanceCount);
@@ -370,13 +370,13 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 	++drawCalls;
 }
 
-static inline void advanceVertexOffsets(const vertex::Attributes &attributes, vertex::BufferBindings &buffers, int vertexcount)
+static inline void advanceVertexOffsets(const VertexAttributes &attributes, BufferBindings &buffers, int vertexcount)
 {
 	// TODO: Figure out a better way to avoid touching the same buffer multiple
 	// times, if multiple attributes share the buffer.
 	uint32 touchedbuffers = 0;
 
-	for (unsigned int i = 0; i < vertex::Attributes::MAX; i++)
+	for (unsigned int i = 0; i < VertexAttributes::MAX; i++)
 	{
 		if (!attributes.isEnabled(i))
 			continue;
@@ -393,7 +393,7 @@ static inline void advanceVertexOffsets(const vertex::Attributes &attributes, ve
 	}
 }
 
-void Graphics::drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, love::graphics::Texture *texture)
+void Graphics::drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture)
 {
 	const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX;
 	const int MAX_QUADS_PER_DRAW    = MAX_VERTICES_PER_DRAW / 4;
@@ -402,7 +402,7 @@ void Graphics::drawQuads(int start, int count, const vertex::Attributes &attribu
 	gl.bindTextureToUnit(texture, 0, false);
 	gl.setCullMode(CULL_NONE);
 
-	gl.bindBuffer(BUFFER_INDEX, quadIndexBuffer->getHandle());
+	gl.bindBuffer(BUFFERTYPE_INDEX, quadIndexBuffer->getHandle());
 
 	if (gl.isBaseVertexSupported())
 	{
@@ -422,7 +422,7 @@ void Graphics::drawQuads(int start, int count, const vertex::Attributes &attribu
 	}
 	else
 	{
-		vertex::BufferBindings bufferscopy = buffers;
+		BufferBindings bufferscopy = buffers;
 		if (start > 0)
 			advanceVertexOffsets(attributes, bufferscopy, start * 4);
 
@@ -510,7 +510,7 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h,
 	endPass();
 
 	bool iswindow = rts.getFirstTarget().texture == nullptr;
-	vertex::Winding vertexwinding = state.winding;
+	Winding vertexwinding = state.winding;
 
 	if (iswindow)
 	{
@@ -528,10 +528,10 @@ void Graphics::setRenderTargetsInternal(const RenderTargets &rts, int w, int h,
 
 		// Flip front face winding when rendering to a texture, since our
 		// projection matrix is flipped.
-		vertexwinding = vertexwinding == vertex::WINDING_CW ? vertex::WINDING_CCW : vertex::WINDING_CW;
+		vertexwinding = vertexwinding == WINDING_CW ? WINDING_CCW : WINDING_CW;
 	}
 
-	glFrontFace(vertexwinding == vertex::WINDING_CW ? GL_CW : GL_CCW);
+	glFrontFace(vertexwinding == WINDING_CW ? GL_CW : GL_CCW);
 
 	gl.setViewport({0, 0, pixelw, pixelh});
 
@@ -1229,7 +1229,7 @@ void Graphics::setDepthMode(CompareMode compare, bool write)
 	}
 }
 
-void Graphics::setFrontFaceWinding(vertex::Winding winding)
+void Graphics::setFrontFaceWinding(Winding winding)
 {
 	DisplayState &state = states.back();
 
@@ -1239,9 +1239,9 @@ void Graphics::setFrontFaceWinding(vertex::Winding winding)
 	state.winding = winding;
 
 	if (isRenderTargetActive())
-		winding = winding == vertex::WINDING_CW ? vertex::WINDING_CCW : vertex::WINDING_CW;
+		winding = winding == WINDING_CW ? WINDING_CCW : WINDING_CW;
 
-	glFrontFace(winding == vertex::WINDING_CW ? GL_CW : GL_CCW);
+	glFrontFace(winding == WINDING_CW ? GL_CW : GL_CCW);
 }
 
 void Graphics::setColor(Colorf c)

+ 3 - 3
src/modules/graphics/opengl/Graphics.h

@@ -60,7 +60,7 @@ public:
 	const char *getName() const override;
 
 	love::graphics::Texture *newTexture(const Texture::Settings &settings, const Texture::Slices *data = nullptr) override;
-	love::graphics::Buffer *newBuffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) override;
+	love::graphics::Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) override;
 
 	void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
 	bool setMode(int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil) override;
@@ -70,7 +70,7 @@ public:
 
 	void draw(const DrawCommand &cmd) override;
 	void draw(const DrawIndexedCommand &cmd) override;
-	void drawQuads(int start, int count, const vertex::Attributes &attributes, const vertex::BufferBindings &buffers, love::graphics::Texture *texture) override;
+	void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture) override;
 
 	void clear(OptionalColorf color, OptionalInt stencil, OptionalDouble depth) override;
 	void clear(const std::vector<OptionalColorf> &colors, OptionalInt stencil, OptionalDouble depth) override;
@@ -91,7 +91,7 @@ public:
 
 	void setDepthMode(CompareMode compare, bool write) override;
 
-	void setFrontFaceWinding(vertex::Winding winding) override;
+	void setFrontFaceWinding(Winding winding) override;
 
 	void setColorMask(ColorChannelMask mask) override;
 

+ 150 - 86
src/modules/graphics/opengl/OpenGL.cpp

@@ -185,7 +185,7 @@ void OpenGL::setupContext()
 	state.enabledAttribArrays = (uint32) ((1ull << uint32(maxvertexattribs)) - 1);
 	state.instancedAttribArrays = 0;
 
-	setVertexAttributes(vertex::Attributes(), vertex::BufferBindings());
+	setVertexAttributes(VertexAttributes(), BufferBindings());
 
 	// Get the current viewport.
 	glGetIntegerv(GL_VIEWPORT, (GLint *) &state.viewport.x);
@@ -222,7 +222,7 @@ void OpenGL::setupContext()
 	glGetIntegerv(GL_CULL_FACE_MODE, &faceCull);
 	state.faceCullMode = faceCull;
 
-	for (int i = 0; i < (int) BUFFER_MAX_ENUM; i++)
+	for (int i = 0; i < (int) BUFFERTYPE_MAX_ENUM; i++)
 	{
 		state.boundBuffers[i] = 0;
 		glBindBuffer(getGLBufferType((BufferType) i), 0);
@@ -562,16 +562,11 @@ GLenum OpenGL::getGLPrimitiveType(PrimitiveType type)
 {
 	switch (type)
 	{
-	case PRIMITIVE_TRIANGLES:
-		return GL_TRIANGLES;
-	case PRIMITIVE_TRIANGLE_STRIP:
-		return GL_TRIANGLE_STRIP;
-	case PRIMITIVE_TRIANGLE_FAN:
-		return GL_TRIANGLE_FAN;
-	case PRIMITIVE_POINTS:
-		return GL_POINTS;
-	case PRIMITIVE_MAX_ENUM:
-		return GL_ZERO;
+		case PRIMITIVE_TRIANGLES: return GL_TRIANGLES;
+		case PRIMITIVE_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP;
+		case PRIMITIVE_TRIANGLE_FAN: return GL_TRIANGLE_FAN;
+		case PRIMITIVE_POINTS: return GL_POINTS;
+		case PRIMITIVE_MAX_ENUM: return GL_ZERO;
 	}
 
 	return GL_ZERO;
@@ -581,12 +576,9 @@ GLenum OpenGL::getGLBufferType(BufferType type)
 {
 	switch (type)
 	{
-	case BUFFER_VERTEX:
-		return GL_ARRAY_BUFFER;
-	case BUFFER_INDEX:
-		return GL_ELEMENT_ARRAY_BUFFER;
-	case BUFFER_MAX_ENUM:
-		return GL_ZERO;
+		case BUFFERTYPE_VERTEX: return GL_ARRAY_BUFFER;
+		case BUFFERTYPE_INDEX: return GL_ELEMENT_ARRAY_BUFFER;
+		case BUFFERTYPE_MAX_ENUM: return GL_ZERO;
 	}
 
 	return GL_ZERO;
@@ -596,16 +588,11 @@ GLenum OpenGL::getGLTextureType(TextureType type)
 {
 	switch (type)
 	{
-	case TEXTURE_2D:
-		return GL_TEXTURE_2D;
-	case TEXTURE_VOLUME:
-		return GL_TEXTURE_3D;
-	case TEXTURE_2D_ARRAY:
-		return GL_TEXTURE_2D_ARRAY;
-	case TEXTURE_CUBE:
-		return GL_TEXTURE_CUBE_MAP;
-	case TEXTURE_MAX_ENUM:
-		return GL_ZERO;
+		case TEXTURE_2D: return GL_TEXTURE_2D;
+		case TEXTURE_VOLUME: return GL_TEXTURE_3D;
+		case TEXTURE_2D_ARRAY: return GL_TEXTURE_2D_ARRAY;
+		case TEXTURE_CUBE: return GL_TEXTURE_CUBE_MAP;
+		case TEXTURE_MAX_ENUM: return GL_ZERO;
 	}
 
 	return GL_ZERO;
@@ -615,74 +602,159 @@ GLenum OpenGL::getGLIndexDataType(IndexDataType type)
 {
 	switch (type)
 	{
-	case INDEX_UINT16:
-		return GL_UNSIGNED_SHORT;
-	case INDEX_UINT32:
-		return GL_UNSIGNED_INT;
-	default:
-		return GL_ZERO;
+		case INDEX_UINT16: return GL_UNSIGNED_SHORT;
+		case INDEX_UINT32: return GL_UNSIGNED_INT;
+		default: return GL_ZERO;
 	}
 }
 
-GLenum OpenGL::getGLVertexDataType(vertex::DataType type, GLboolean &normalized, bool &intformat)
+GLenum OpenGL::getGLVertexDataType(DataFormat format, int &components, GLboolean &normalized, bool &intformat)
 {
 	normalized = GL_FALSE;
 	intformat = false;
+	components = 1;
 
-	switch (type)
+	switch (format)
 	{
-	case vertex::DATA_SNORM8:
+	case DATAFORMAT_FLOAT:
+		components = 1;
+		return GL_FLOAT;
+	case DATAFORMAT_FLOAT_VEC2:
+		components = 2;
+		return GL_FLOAT;
+	case DATAFORMAT_FLOAT_VEC3:
+		components = 3;
+		return GL_FLOAT;
+	case DATAFORMAT_FLOAT_VEC4:
+		components = 4;
+		return GL_FLOAT;
+
+	case DATAFORMAT_FLOAT_MAT2X2:
+	case DATAFORMAT_FLOAT_MAT2X3:
+	case DATAFORMAT_FLOAT_MAT2X4:
+	case DATAFORMAT_FLOAT_MAT3X2:
+	case DATAFORMAT_FLOAT_MAT3X3:
+	case DATAFORMAT_FLOAT_MAT3X4:
+	case DATAFORMAT_FLOAT_MAT4X2:
+	case DATAFORMAT_FLOAT_MAT4X3:
+	case DATAFORMAT_FLOAT_MAT4X4:
+		return GL_ZERO;
+
+	case DATAFORMAT_INT32:
+		components = 1;
+		intformat = true;
+		return GL_INT;
+	case DATAFORMAT_INT32_VEC2:
+		components = 2;
+		intformat = true;
+		return GL_INT;
+	case DATAFORMAT_INT32_VEC3:
+		components = 3;
+		intformat = true;
+		return GL_INT;
+	case DATAFORMAT_INT32_VEC4:
+		components = 4;
+		intformat = true;
+		return GL_INT;
+
+	case DATAFORMAT_UINT32:
+		components = 1;
+		intformat = true;
+		return GL_UNSIGNED_INT;
+	case DATAFORMAT_UINT32_VEC2:
+		components = 2;
+		intformat = true;
+		return GL_UNSIGNED_INT;
+	case DATAFORMAT_UINT32_VEC3:
+		components = 3;
+		intformat = true;
+		return GL_UNSIGNED_INT;
+	case DATAFORMAT_UINT32_VEC4:
+		components = 4;
+		intformat = true;
+		return GL_UNSIGNED_INT;
+
+	case DATAFORMAT_SNORM8_VEC4:
+		components = 4;
 		normalized = GL_TRUE;
 		return GL_BYTE;
-	case vertex::DATA_UNORM8:
+
+	case DATAFORMAT_UNORM8_VEC4:
+		components = 4;
 		normalized = GL_TRUE;
 		return GL_UNSIGNED_BYTE;
-	case vertex::DATA_INT8:
+
+	case DATAFORMAT_INT8_VEC4:
+		components = 4;
 		intformat = true;
 		return GL_BYTE;
-	case vertex::DATA_UINT8:
+
+	case DATAFORMAT_UINT8_VEC4:
+		components = 4;
 		intformat = true;
 		return GL_UNSIGNED_BYTE;
-	case vertex::DATA_SNORM16:
+
+	case DATAFORMAT_SNORM16_VEC2:
+		components = 2;
 		normalized = GL_TRUE;
-		return GL_SHORT;
-	case vertex::DATA_UNORM16:
+		return GL_BYTE;
+	case DATAFORMAT_SNORM16_VEC4:
+		components = 4;
+		normalized = GL_TRUE;
+		return GL_BYTE;
+
+	case DATAFORMAT_UNORM16_VEC2:
+		components = 2;
+		normalized = GL_TRUE;
+		return GL_UNSIGNED_SHORT;
+	case DATAFORMAT_UNORM16_VEC4:
+		components = 4;
 		normalized = GL_TRUE;
 		return GL_UNSIGNED_SHORT;
-	case vertex::DATA_INT16:
+
+	case DATAFORMAT_INT16_VEC2:
+		components = 2;
+		intformat = true;
+		return GL_SHORT;
+	case DATAFORMAT_INT16_VEC4:
+		components = 4;
 		intformat = true;
 		return GL_SHORT;
-	case vertex::DATA_UINT16:
+
+	case DATAFORMAT_UINT16:
+		components = 1;
 		intformat = true;
 		return GL_UNSIGNED_SHORT;
-	case vertex::DATA_INT32:
+	case DATAFORMAT_UINT16_VEC2:
+		components = 2;
 		intformat = true;
-		return GL_INT;
-	case vertex::DATA_UINT32:
+		return GL_UNSIGNED_SHORT;
+	case DATAFORMAT_UINT16_VEC4:
+		components = 4;
 		intformat = true;
-		return GL_UNSIGNED_INT;
-	case vertex::DATA_FLOAT:
-		normalized = GL_FALSE;
-		return GL_FLOAT;
-	case vertex::DATA_MAX_ENUM:
+		return GL_UNSIGNED_SHORT;
+
+	case DATAFORMAT_BOOL:
+	case DATAFORMAT_BOOL_VEC2:
+	case DATAFORMAT_BOOL_VEC3:
+	case DATAFORMAT_BOOL_VEC4:
+		return GL_ZERO;
+
+	case DATAFORMAT_MAX_ENUM:
 		return GL_ZERO;
 	}
 
 	return GL_ZERO;
 }
 
-GLenum OpenGL::getGLBufferUsage(vertex::Usage usage)
+GLenum OpenGL::getGLBufferUsage(BufferUsage usage)
 {
 	switch (usage)
 	{
-	case vertex::USAGE_STREAM:
-		return GL_STREAM_DRAW;
-	case vertex::USAGE_DYNAMIC:
-		return GL_DYNAMIC_DRAW;
-	case vertex::USAGE_STATIC:
-		return GL_STATIC_DRAW;
-	default:
-		return 0;
+		case BUFFERUSAGE_STREAM: return GL_STREAM_DRAW;
+		case BUFFERUSAGE_DYNAMIC: return GL_DYNAMIC_DRAW;
+		case BUFFERUSAGE_STATIC: return GL_STATIC_DRAW;
+		default: return 0;
 	}
 }
 
@@ -699,14 +771,14 @@ void OpenGL::deleteBuffer(GLuint buffer)
 {
 	glDeleteBuffers(1, &buffer);
 
-	for (int i = 0; i < (int) BUFFER_MAX_ENUM; i++)
+	for (int i = 0; i < (int) BUFFERTYPE_MAX_ENUM; i++)
 	{
 		if (state.boundBuffers[i] == buffer)
 			state.boundBuffers[i] = 0;
 	}
 }
 
-void OpenGL::setVertexAttributes(const vertex::Attributes &attributes, const vertex::BufferBindings &buffers)
+void OpenGL::setVertexAttributes(const VertexAttributes &attributes, const BufferBindings &buffers)
 {
 	uint32 enablediff = attributes.enableBits ^ state.enabledAttribArrays;
 	uint32 instanceattribbits = 0;
@@ -739,18 +811,19 @@ void OpenGL::setVertexAttributes(const vertex::Attributes &attributes, const ver
 			if ((state.instancedAttribArrays & bit) ^ divisorbit)
 				glVertexAttribDivisor(i, divisor);
 
+			int components = 0;
 			GLboolean normalized = GL_FALSE;
 			bool intformat = false;
-			GLenum gltype = getGLVertexDataType(attrib.type, normalized, intformat);
+			GLenum gltype = getGLVertexDataType(attrib.format, components, normalized, intformat);
 
 			const void *offsetpointer = reinterpret_cast<void*>(bufferinfo.offset + attrib.offsetFromVertex);
 
-			bindBuffer(BUFFER_VERTEX, (GLuint) bufferinfo.buffer->getHandle());
+			bindBuffer(BUFFERTYPE_VERTEX, (GLuint) bufferinfo.buffer->getHandle());
 
 			if (intformat)
-				glVertexAttribIPointer(i, attrib.components, gltype, layout.stride, offsetpointer);
+				glVertexAttribIPointer(i, components, gltype, layout.stride, offsetpointer);
 			else
-				glVertexAttribPointer(i, attrib.components, gltype, normalized, layout.stride, offsetpointer);
+				glVertexAttribPointer(i, components, gltype, normalized, layout.stride, offsetpointer);
 		}
 
 		i++;
@@ -1073,24 +1146,15 @@ GLint OpenGL::getGLCompareMode(CompareMode mode)
 {
 	switch (mode)
 	{
-	case COMPARE_LESS:
-		return GL_LESS;
-	case COMPARE_LEQUAL:
-		return GL_LEQUAL;
-	case COMPARE_EQUAL:
-		return GL_EQUAL;
-	case COMPARE_GEQUAL:
-		return GL_GEQUAL;
-	case COMPARE_GREATER:
-		return GL_GREATER;
-	case COMPARE_NOTEQUAL:
-		return GL_NOTEQUAL;
-	case COMPARE_ALWAYS:
-		return GL_ALWAYS;
-	case COMPARE_NEVER:
-		return GL_NEVER;
-	default:
-		return GL_NEVER;
+		case COMPARE_LESS: return GL_LESS;
+		case COMPARE_LEQUAL: return GL_LEQUAL;
+		case COMPARE_EQUAL: return GL_EQUAL;
+		case COMPARE_GEQUAL: return GL_GEQUAL;
+		case COMPARE_GREATER: return GL_GREATER;
+		case COMPARE_NOTEQUAL: return GL_NOTEQUAL;
+		case COMPARE_ALWAYS: return GL_ALWAYS;
+		case COMPARE_NEVER: return GL_NEVER;
+		default: return GL_NEVER;
 	}
 }
 

+ 4 - 4
src/modules/graphics/opengl/OpenGL.h

@@ -238,7 +238,7 @@ public:
 	/**
 	 * Set all vertex attribute state.
 	 **/
-	void setVertexAttributes(const vertex::Attributes &attributes, const vertex::BufferBindings &buffers);
+	void setVertexAttributes(const VertexAttributes &attributes, const BufferBindings &buffers);
 
 	/**
 	 * Wrapper for glCullFace which eliminates redundant state setting.
@@ -399,8 +399,8 @@ public:
 	static GLenum getGLPrimitiveType(PrimitiveType type);
 	static GLenum getGLBufferType(BufferType type);
 	static GLenum getGLIndexDataType(IndexDataType type);
-	static GLenum getGLVertexDataType(vertex::DataType type, GLboolean &normalized, bool &intformat);
-	static GLenum getGLBufferUsage(vertex::Usage usage);
+	static GLenum getGLVertexDataType(DataFormat format, int &components, GLboolean &normalized, bool &intformat);
+	static GLenum getGLBufferUsage(BufferUsage usage);
 	static GLenum getGLTextureType(TextureType type);
 	static GLint getGLWrapMode(SamplerState::WrapMode wmode);
 	static GLint getGLCompareMode(CompareMode mode);
@@ -448,7 +448,7 @@ private:
 	// Tracked OpenGL state.
 	struct
 	{
-		GLuint boundBuffers[BUFFER_MAX_ENUM];
+		GLuint boundBuffers[BUFFERTYPE_MAX_ENUM];
 
 		// Texture unit state (currently bound texture for each texture unit.)
 		std::vector<GLuint> boundTextures[TEXTURE_MAX_ENUM];

+ 3 - 2
src/modules/graphics/opengl/Shader.cpp

@@ -23,6 +23,7 @@
 
 #include "Shader.h"
 #include "Graphics.h"
+#include "graphics/vertex.h"
 
 // C++
 #include <algorithm>
@@ -322,7 +323,7 @@ bool Shader::loadVolatile()
 	for (int i = 0; i < int(ATTRIB_MAX_ENUM); i++)
 	{
 		const char *name = nullptr;
-		if (vertex::getConstant((BuiltinVertexAttribute) i, name))
+		if (graphics::getConstant((BuiltinVertexAttribute) i, name))
 			glBindAttribLocation(program, i, (const GLchar *) name);
 	}
 
@@ -345,7 +346,7 @@ bool Shader::loadVolatile()
 	for (int i = 0; i < int(ATTRIB_MAX_ENUM); i++)
 	{
 		const char *name = nullptr;
-		if (vertex::getConstant(BuiltinVertexAttribute(i), name))
+		if (graphics::getConstant(BuiltinVertexAttribute(i), name))
 			builtinAttributes[i] = glGetAttribLocation(program, name);
 		else
 			builtinAttributes[i] = -1;

+ 188 - 216
src/modules/graphics/vertex.cpp

@@ -25,8 +25,6 @@ namespace love
 {
 namespace graphics
 {
-namespace vertex
-{
 
 static_assert(sizeof(Color32) == 4, "sizeof(Color32) incorrect!");
 static_assert(sizeof(STf_RGBAub) == sizeof(float)*2 + sizeof(Color32), "sizeof(STf_RGBAub) incorrect!");
@@ -103,6 +101,68 @@ int getFormatPositionComponents(CommonFormat format)
 	return 0;
 }
 
+// Order here relies on order of DataFormat enum.
+static const DataFormatInfo dataFormatInfo[]
+{
+	// baseType, isMatrix, components, rows, columns, componentSize, size
+	{ DATA_BASETYPE_FLOAT, false, 1, 0, 0, 4, 4  }, // DATAFORMAT_FLOAT
+	{ DATA_BASETYPE_FLOAT, false, 2, 0, 0, 4, 8  }, // DATAFORMAT_FLOAT_VEC2
+	{ DATA_BASETYPE_FLOAT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_FLOAT_VEC3
+	{ DATA_BASETYPE_FLOAT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_FLOAT_VEC4
+
+	{ DATA_BASETYPE_FLOAT, true, 0, 2, 2, 4, 16 }, // DATAFORMAT_FLOAT_MAT2X2
+	{ DATA_BASETYPE_FLOAT, true, 0, 2, 3, 4, 24 }, // DATAFORMAT_FLOAT_MAT2X3
+	{ DATA_BASETYPE_FLOAT, true, 0, 2, 4, 4, 32 }, // DATAFORMAT_FLOAT_MAT2X4
+
+	{ DATA_BASETYPE_FLOAT, true, 0, 3, 2, 4, 24 }, // DATAFORMAT_FLOAT_MAT3X2
+	{ DATA_BASETYPE_FLOAT, true, 0, 3, 3, 4, 36 }, // DATAFORMAT_FLOAT_MAT3X3
+	{ DATA_BASETYPE_FLOAT, true, 0, 3, 4, 4, 48 }, // DATAFORMAT_FLOAT_MAT3X4
+
+	{ DATA_BASETYPE_FLOAT, true, 0, 4, 2, 4, 32 }, // DATAFORMAT_FLOAT_MAT4X2
+	{ DATA_BASETYPE_FLOAT, true, 0, 4, 3, 4, 48 }, // DATAFORMAT_FLOAT_MAT4X3
+	{ DATA_BASETYPE_FLOAT, true, 0, 4, 4, 4, 64 }, // DATAFORMAT_FLOAT_MAT4X4
+
+	{ DATA_BASETYPE_INT, false, 1, 0, 0, 4, 4  }, // DATAFORMAT_INT32
+	{ DATA_BASETYPE_INT, false, 2, 0, 0, 4, 8  }, // DATAFORMAT_INT32_VEC2
+	{ DATA_BASETYPE_INT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_INT32_VEC3
+	{ DATA_BASETYPE_INT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_INT32_VEC4
+
+	{ DATA_BASETYPE_UINT, false, 1, 0, 0, 4, 4  }, // DATAFORMAT_UINT32
+	{ DATA_BASETYPE_UINT, false, 2, 0, 0, 4, 8  }, // DATAFORMAT_UINT32_VEC2
+	{ DATA_BASETYPE_UINT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_UINT32_VEC3
+	{ DATA_BASETYPE_UINT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_UINT32_VEC4
+
+	{ DATA_BASETYPE_SNORM, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_SNORM8_VEC4
+	{ DATA_BASETYPE_UNORM, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_UNORM8_VEC4
+	{ DATA_BASETYPE_INT,   false, 4, 0, 0, 1, 4 }, // DATAFORMAT_INT8_VEC4
+	{ DATA_BASETYPE_UINT,  false, 4, 0, 0, 1, 4 }, // DATAFORMAT_UINT8_VEC4
+
+	{ DATA_BASETYPE_SNORM, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_SNORM16_VEC2
+	{ DATA_BASETYPE_SNORM, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_SNORM16_VEC4
+
+	{ DATA_BASETYPE_UNORM, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_UNORM16_VEC2
+	{ DATA_BASETYPE_UNORM, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_UNORM16_VEC4
+
+	{ DATA_BASETYPE_INT, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_INT16_VEC2
+	{ DATA_BASETYPE_INT, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_INT16_VEC4
+
+	{ DATA_BASETYPE_UINT, false, 1, 0, 0, 2, 2 }, // DATAFORMAT_UINT16
+	{ DATA_BASETYPE_UINT, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_UINT16_VEC2
+	{ DATA_BASETYPE_UINT, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_UINT16_VEC4
+
+	{ DATA_BASETYPE_BOOL, false, 1, 0, 0, 4, 4  }, // DATAFORMAT_BOOL
+	{ DATA_BASETYPE_BOOL, false, 2, 0, 0, 4, 8  }, // DATAFORMAT_BOOL_VEC2
+	{ DATA_BASETYPE_BOOL, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_BOOL_VEC3
+	{ DATA_BASETYPE_BOOL, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_BOOL_VEC4
+};
+
+static_assert((sizeof(dataFormatInfo) / sizeof(DataFormatInfo)) == DATAFORMAT_MAX_ENUM, "dataFormatInfo array size must match number of DataFormat enum values.");
+
+const DataFormatInfo &getDataFormatInfo(DataFormat format)
+{
+	return dataFormatInfo[format];
+}
+
 size_t getIndexDataSize(IndexDataType type)
 {
 	switch (type)
@@ -113,63 +173,36 @@ size_t getIndexDataSize(IndexDataType type)
 	}
 }
 
-size_t getDataTypeSize(DataType datatype)
+IndexDataType getIndexDataTypeFromMax(size_t maxvalue)
 {
-	switch (datatype)
-	{
-	case DATA_SNORM8:
-	case DATA_UNORM8:
-	case DATA_INT8:
-	case DATA_UINT8:
-		return sizeof(uint8);
-	case DATA_SNORM16:
-	case DATA_UNORM16:
-	case DATA_INT16:
-	case DATA_UINT16:
-		return sizeof(uint16);
-	case DATA_INT32:
-	case DATA_UINT32:
-		return sizeof(uint32);
-	case DATA_FLOAT:
-		return sizeof(float);
-	case DATA_MAX_ENUM:
-		return 0;
-	}
-	return 0;
+	return maxvalue > LOVE_UINT16_MAX ? INDEX_UINT32 : INDEX_UINT16;
 }
 
-bool isDataTypeInteger(DataType datatype)
+DataFormat getIndexDataFormat(IndexDataType type)
 {
-	switch (datatype)
-	{
-	case DATA_INT8:
-	case DATA_UINT8:
-	case DATA_INT16:
-	case DATA_UINT16:
-	case DATA_INT32:
-	case DATA_UINT32:
-		return true;
-	default:
-		return false;
-	}
+	return type == INDEX_UINT32 ? DATAFORMAT_UINT32 : DATAFORMAT_UINT16;
 }
 
-IndexDataType getIndexDataTypeFromMax(size_t maxvalue)
+IndexDataType getIndexDataType(DataFormat format)
 {
-	IndexDataType types[] = {INDEX_UINT16, INDEX_UINT32};
-	return types[maxvalue > LOVE_UINT16_MAX ? 1 : 0];
+	switch (format)
+	{
+		case DATAFORMAT_UINT16: return INDEX_UINT16;
+		case DATAFORMAT_UINT32: return INDEX_UINT32;
+		default: return INDEX_MAX_ENUM;
+	}
 }
 
 int getIndexCount(TriangleIndexMode mode, int vertexCount)
 {
 	switch (mode)
 	{
-	case TriangleIndexMode::NONE:
+	case TRIANGLEINDEX_NONE:
 		return 0;
-	case TriangleIndexMode::STRIP:
-	case TriangleIndexMode::FAN:
+	case TRIANGLEINDEX_STRIP:
+	case TRIANGLEINDEX_FAN:
 		return 3 * (vertexCount - 2);
-	case TriangleIndexMode::QUADS:
+	case TRIANGLEINDEX_QUADS:
 		return vertexCount * 6 / 4;
 	}
 	return 0;
@@ -180,9 +213,9 @@ static void fillIndicesT(TriangleIndexMode mode, T vertexStart, T vertexCount, T
 {
 	switch (mode)
 	{
-	case TriangleIndexMode::NONE:
+	case TRIANGLEINDEX_NONE:
 		break;
-	case TriangleIndexMode::STRIP:
+	case TRIANGLEINDEX_STRIP:
 		{
 			int i = 0;
 			for (T index = 0; index < vertexCount - 2; index++)
@@ -193,7 +226,7 @@ static void fillIndicesT(TriangleIndexMode mode, T vertexStart, T vertexCount, T
 			}
 		}
 		break;
-	case TriangleIndexMode::FAN:
+	case TRIANGLEINDEX_FAN:
 		{
 			int i = 0;
 			for (T index = 2; index < vertexCount; index++)
@@ -204,7 +237,7 @@ static void fillIndicesT(TriangleIndexMode mode, T vertexStart, T vertexCount, T
 			}
 		}
 		break;
-	case TriangleIndexMode::QUADS:
+	case TRIANGLEINDEX_QUADS:
 		{
 			// 0---2
 			// | / |
@@ -238,7 +271,7 @@ void fillIndices(TriangleIndexMode mode, uint32 vertexStart, uint32 vertexCount,
 	fillIndicesT(mode, vertexStart, vertexCount, indices);
 }
 
-void Attributes::setCommonFormat(CommonFormat format, uint8 bufferindex)
+void VertexAttributes::setCommonFormat(CommonFormat format, uint8 bufferindex)
 {
 	setBufferLayout(bufferindex, (uint16) getFormatStride(format));
 
@@ -247,241 +280,180 @@ void Attributes::setCommonFormat(CommonFormat format, uint8 bufferindex)
 	case CommonFormat::NONE:
 		break;
 	case CommonFormat::XYf:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
 		break;
 	case CommonFormat::XYZf:
-		set(ATTRIB_POS, DATA_FLOAT, 3, 0, bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC3, 0, bufferindex);
 		break;
 	case CommonFormat::RGBAub:
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, 0, bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, 0, bufferindex);
 		break;
 	case CommonFormat::STf_RGBAub:
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 2), bufferindex);
 		break;
 	case CommonFormat::STPf_RGBAub:
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 3, 0, bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 3), bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, 0, bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 3), bufferindex);
 		break;
 	case CommonFormat::XYf_STf:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 2, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, uint16(sizeof(float) * 2), bufferindex);
 		break;
 	case CommonFormat::XYf_STPf:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 3, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, uint16(sizeof(float) * 2), bufferindex);
 		break;
 	case CommonFormat::XYf_STf_RGBAub:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 2, uint16(sizeof(float) * 2), bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 4), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 4), bufferindex);
 		break;
 	case CommonFormat::XYf_STus_RGBAub:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_UNORM16, 2, uint16(sizeof(float) * 2), bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 2 + sizeof(uint16) * 2), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_UNORM16_VEC2, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 2 + sizeof(uint16) * 2), bufferindex);
 		break;
 	case CommonFormat::XYf_STPf_RGBAub:
-		set(ATTRIB_POS, DATA_FLOAT, 2, 0, bufferindex);
-		set(ATTRIB_TEXCOORD, DATA_FLOAT, 3, uint16(sizeof(float) * 2), bufferindex);
-		set(ATTRIB_COLOR, DATA_UNORM8, 4, uint16(sizeof(float) * 5), bufferindex);
+		set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferindex);
+		set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC3, uint16(sizeof(float) * 2), bufferindex);
+		set(ATTRIB_COLOR, DATAFORMAT_UNORM8_VEC4, uint16(sizeof(float) * 5), bufferindex);
 		break;
 	}
 }
 
-static StringMap<BuiltinVertexAttribute, ATTRIB_MAX_ENUM>::Entry attribNameEntries[] =
+STRINGMAP_BEGIN(BuiltinVertexAttribute, ATTRIB_MAX_ENUM, attribName)
 {
 	{ "VertexPosition", ATTRIB_POS           },
 	{ "VertexTexCoord", ATTRIB_TEXCOORD      },
 	{ "VertexColor",    ATTRIB_COLOR         },
-};
-
-static StringMap<BuiltinVertexAttribute, ATTRIB_MAX_ENUM> attribNames(attribNameEntries, sizeof(attribNameEntries));
-
-static StringMap<IndexDataType, INDEX_MAX_ENUM>::Entry indexTypeEntries[] =
-{
-	{ "uint16", INDEX_UINT16 },
-	{ "uint32", INDEX_UINT32 },
-};
-
-static StringMap<IndexDataType, INDEX_MAX_ENUM> indexTypes(indexTypeEntries, sizeof(indexTypeEntries));
-
-static StringMap<Usage, USAGE_MAX_ENUM>::Entry usageEntries[] =
-{
-	{ "stream",  USAGE_STREAM  },
-	{ "dynamic", USAGE_DYNAMIC },
-	{ "static",  USAGE_STATIC  },
-};
-
-static StringMap<Usage, USAGE_MAX_ENUM> usages(usageEntries, sizeof(usageEntries));
-
-static StringMap<PrimitiveType, PRIMITIVE_MAX_ENUM>::Entry primitiveTypeEntries[] =
-{
-	{ "fan",       PRIMITIVE_TRIANGLE_FAN   },
-	{ "strip",     PRIMITIVE_TRIANGLE_STRIP },
-	{ "triangles", PRIMITIVE_TRIANGLES      },
-	{ "points",    PRIMITIVE_POINTS         },
-};
-
-static StringMap<PrimitiveType, PRIMITIVE_MAX_ENUM> primitiveTypes(primitiveTypeEntries, sizeof(primitiveTypeEntries));
-
-static StringMap<AttributeStep, STEP_MAX_ENUM>::Entry attributeStepEntries[] =
-{
-	{ "pervertex",   STEP_PER_VERTEX   },
-	{ "perinstance", STEP_PER_INSTANCE },
-};
-
-static StringMap<AttributeStep, STEP_MAX_ENUM> attributeSteps(attributeStepEntries, sizeof(attributeStepEntries));
-
-static StringMap<DataType, DATA_MAX_ENUM>::Entry dataTypeEntries[] =
-{
-	{ "snorm8",  DATA_SNORM8  },
-	{ "unorm8",  DATA_UNORM8  },
-	{ "int8",    DATA_INT8    },
-	{ "uint8",   DATA_UINT8   },
-	{ "snorm16", DATA_SNORM16 },
-	{ "unorm16", DATA_UNORM16 },
-	{ "int16",   DATA_INT16   },
-	{ "uint16",  DATA_UINT16  },
-	{ "int32",   DATA_INT32   },
-	{ "uint32",  DATA_UINT32  },
-	{ "float",   DATA_FLOAT   },
-};
-
-static StringMap<DataType, DATA_MAX_ENUM> dataTypes(dataTypeEntries, sizeof(dataTypeEntries));
-
-static StringMap<CullMode, CULL_MAX_ENUM>::Entry cullModeEntries[] =
-{
-	{ "none",  CULL_NONE  },
-	{ "back",  CULL_BACK  },
-	{ "front", CULL_FRONT },
-};
-
-static StringMap<CullMode, CULL_MAX_ENUM> cullModes(cullModeEntries, sizeof(cullModeEntries));
-
-static StringMap<Winding, WINDING_MAX_ENUM>::Entry windingEntries[] =
-{
-	{ "cw",  WINDING_CW  },
-	{ "ccw", WINDING_CCW },
-};
-
-static StringMap<Winding, WINDING_MAX_ENUM> windings(windingEntries, sizeof(windingEntries));
-
-bool getConstant(const char *in, BuiltinVertexAttribute &out)
-{
-	return attribNames.find(in, out);
-}
-
-bool getConstant(BuiltinVertexAttribute in, const char *&out)
-{
-	return attribNames.find(in, out);
 }
+STRINGMAP_END(BuiltinVertexAttribute, ATTRIB_MAX_ENUM, attribName)
 
-bool getConstant(const char *in, IndexDataType &out)
+const char *getConstant(BuiltinVertexAttribute attrib)
 {
-	return indexTypes.find(in, out);
+	const char *name = nullptr;
+	getConstant(attrib, name);
+	return name;
 }
 
-bool getConstant(IndexDataType in, const char *&out)
+STRINGMAP_BEGIN(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName)
 {
-	return indexTypes.find(in, out);
+	{ "vertex", BUFFERTYPE_VERTEX },
+	{ "index",  BUFFERTYPE_INDEX  },
 }
+STRINGMAP_END(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName)
 
-std::vector<std::string> getConstants(IndexDataType)
+STRINGMAP_BEGIN(IndexDataType, INDEX_MAX_ENUM, indexType)
 {
-	return indexTypes.getNames();
+	{ "uint16", INDEX_UINT16 },
+	{ "uint32", INDEX_UINT32 },
 }
+STRINGMAP_END(IndexDataType, INDEX_MAX_ENUM, indexType)
 
-bool getConstant(const char *in, Usage &out)
+STRINGMAP_BEGIN(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsage)
 {
-	return usages.find(in, out);
+	{ "stream",  BUFFERUSAGE_STREAM  },
+	{ "dynamic", BUFFERUSAGE_DYNAMIC },
+	{ "static",  BUFFERUSAGE_STATIC  },
 }
+STRINGMAP_END(BufferUsage, BUFFERUSAGE_MAX_ENUM, bufferUsage)
 
-bool getConstant(Usage in, const char *&out)
+STRINGMAP_BEGIN(PrimitiveType, PRIMITIVE_MAX_ENUM, primitiveType)
 {
-	return usages.find(in, out);
+	{ "fan",       PRIMITIVE_TRIANGLE_FAN   },
+	{ "strip",     PRIMITIVE_TRIANGLE_STRIP },
+	{ "triangles", PRIMITIVE_TRIANGLES      },
+	{ "points",    PRIMITIVE_POINTS         },
 }
+STRINGMAP_END(PrimitiveType, PRIMITIVE_MAX_ENUM, primitiveType)
 
-std::vector<std::string> getConstants(Usage)
+STRINGMAP_BEGIN(AttributeStep, STEP_MAX_ENUM, attributeStep)
 {
-	return usages.getNames();
+	{ "pervertex",   STEP_PER_VERTEX   },
+	{ "perinstance", STEP_PER_INSTANCE },
 }
+STRINGMAP_END(AttributeStep, STEP_MAX_ENUM, attributeStep)
 
-bool getConstant(const char *in, PrimitiveType &out)
+STRINGMAP_BEGIN(DataFormat, DATAFORMAT_MAX_ENUM, dataFormat)
 {
-	return primitiveTypes.find(in, out);
-}
+	{ "float",     DATAFORMAT_FLOAT      },
+	{ "floatvec2", DATAFORMAT_FLOAT_VEC2 },
+	{ "floatvec3", DATAFORMAT_FLOAT_VEC3 },
+	{ "floatvec4", DATAFORMAT_FLOAT_VEC4 },
 
-bool getConstant(PrimitiveType in, const char *&out)
-{
-	return primitiveTypes.find(in, out);
-}
+	{ "floatmat2x2", DATAFORMAT_FLOAT_MAT2X2 },
+	{ "floatmat2x3", DATAFORMAT_FLOAT_MAT2X3 },
+	{ "floatmat2x4", DATAFORMAT_FLOAT_MAT2X4 },
 
-std::vector<std::string> getConstants(PrimitiveType)
-{
-	return primitiveTypes.getNames();
-}
+	{ "floatmat3x2", DATAFORMAT_FLOAT_MAT3X2 },
+	{ "floatmat3x3", DATAFORMAT_FLOAT_MAT3X3 },
+	{ "floatmat3x4", DATAFORMAT_FLOAT_MAT3X4 },
 
-bool getConstant(const char *in, AttributeStep &out)
-{
-	return attributeSteps.find(in, out);
-}
+	{ "floatmat4x2", DATAFORMAT_FLOAT_MAT4X2 },
+	{ "floatmat4x3", DATAFORMAT_FLOAT_MAT4X3 },
+	{ "floatmat4x4", DATAFORMAT_FLOAT_MAT4X4 },
 
-bool getConstant(AttributeStep in, const char *&out)
-{
-	return attributeSteps.find(in, out);
-}
+	{ "int32",     DATAFORMAT_INT32      },
+	{ "int32vec2", DATAFORMAT_INT32_VEC2 },
+	{ "int32vec3", DATAFORMAT_INT32_VEC3 },
+	{ "int32vec4", DATAFORMAT_INT32_VEC4 },
 
-std::vector<std::string> getConstants(AttributeStep)
-{
-	return attributeSteps.getNames();
-}
+	{ "uint32",     DATAFORMAT_UINT32      },
+	{ "uint32vec2", DATAFORMAT_UINT32_VEC2 },
+	{ "uint32vec3", DATAFORMAT_UINT32_VEC3 },
+	{ "uint32vec4", DATAFORMAT_UINT32_VEC4 },
 
-bool getConstant(const char *in, DataType &out)
-{
-	return dataTypes.find(in, out);
-}
+	{ "snorm8vec4", DATAFORMAT_SNORM8_VEC4 },
+	{ "unorm8vec4", DATAFORMAT_UNORM8_VEC4 },
+	{ "int8vec4",   DATAFORMAT_INT8_VEC4   },
+	{ "uint8vec4",  DATAFORMAT_UINT8_VEC4  },
 
-bool getConstant(DataType in, const char *&out)
-{
-	return dataTypes.find(in, out);
-}
+	{ "snorm16vec2", DATAFORMAT_SNORM16_VEC2 },
+	{ "snorm16vec4", DATAFORMAT_SNORM16_VEC4 },
 
-std::vector<std::string> getConstants(DataType)
-{
-	return dataTypes.getNames();
-}
+	{ "unorm16vec2", DATAFORMAT_UNORM16_VEC2 },
+	{ "unorm16vec4", DATAFORMAT_UNORM16_VEC4 },
 
-bool getConstant(const char *in, CullMode &out)
-{
-	return cullModes.find(in, out);
-}
+	{ "int16vec2", DATAFORMAT_INT16_VEC2 },
+	{ "int16vec4", DATAFORMAT_INT16_VEC4 },
 
-bool getConstant(CullMode in, const char *&out)
-{
-	return cullModes.find(in, out);
-}
+	{ "uint16",     DATAFORMAT_UINT16      },
+	{ "uint16vec2", DATAFORMAT_UINT16_VEC2 },
+	{ "uint16vec4", DATAFORMAT_UINT16_VEC4 },
 
-std::vector<std::string> getConstants(CullMode)
-{
-	return cullModes.getNames();
+	{ "bool",     DATAFORMAT_BOOL      },
+	{ "boolvec2", DATAFORMAT_BOOL_VEC2 },
+	{ "boolvec3", DATAFORMAT_BOOL_VEC3 },
+	{ "boolvec4", DATAFORMAT_BOOL_VEC4 },
 }
+STRINGMAP_END(DataFormat, DATAFORMAT_MAX_ENUM, dataFormat)
 
-bool getConstant(const char *in, Winding &out)
+STRINGMAP_BEGIN(DataBaseType, DATA_BASETYPE_MAX_ENUM, dataBaseType)
 {
-	return windings.find(in, out);
+	{ "float", DATA_BASETYPE_FLOAT },
+	{ "int",   DATA_BASETYPE_INT   },
+	{ "uint",  DATA_BASETYPE_UINT  },
+	{ "snorm", DATA_BASETYPE_SNORM },
+	{ "unorm", DATA_BASETYPE_UNORM },
+	{ "bool",  DATA_BASETYPE_BOOL  },
 }
+STRINGMAP_END(DataBaseType, DATA_BASETYPE_MAX_ENUM, dataBaseType)
 
-bool getConstant(Winding in, const char *&out)
+STRINGMAP_BEGIN(CullMode, CULL_MAX_ENUM, cullMode)
 {
-	return windings.find(in, out);
+	{ "none",  CULL_NONE  },
+	{ "back",  CULL_BACK  },
+	{ "front", CULL_FRONT },
 }
+STRINGMAP_END(CullMode, CULL_MAX_ENUM, cullMode)
 
-std::vector<std::string> getConstants(Winding)
+STRINGMAP_BEGIN(Winding, WINDING_MAX_ENUM, winding)
 {
-	return windings.getNames();
+	{ "cw",  WINDING_CW  },
+	{ "ccw", WINDING_CCW },
 }
+STRINGMAP_END(Winding, WINDING_MAX_ENUM, winding)
 
-} // vertex
 } // graphics
 } // love

+ 117 - 80
src/modules/graphics/vertex.h

@@ -23,6 +23,7 @@
 // LOVE
 #include "common/int.h"
 #include "common/Color.h"
+#include "common/StringMap.h"
 
 // C
 #include <stddef.h>
@@ -46,7 +47,7 @@ enum BuiltinVertexAttribute
 	ATTRIB_MAX_ENUM
 };
 
-enum BuiltinVertexAttributeFlag
+enum BuiltinVertexAttributeFlags
 {
 	ATTRIBFLAG_POS = 1 << ATTRIB_POS,
 	ATTRIBFLAG_TEXCOORD = 1 << ATTRIB_TEXCOORD,
@@ -55,9 +56,9 @@ enum BuiltinVertexAttributeFlag
 
 enum BufferType
 {
-	BUFFER_VERTEX = 0,
-	BUFFER_INDEX,
-	BUFFER_MAX_ENUM
+	BUFFERTYPE_VERTEX = 0,
+	BUFFERTYPE_INDEX,
+	BUFFERTYPE_MAX_ENUM
 };
 
 enum IndexDataType
@@ -92,36 +93,81 @@ enum CullMode
 	CULL_MAX_ENUM
 };
 
-namespace vertex
+// The expected usage pattern of buffer data.
+enum BufferUsage
 {
-
-// The expected usage pattern of vertex data.
-enum Usage
-{
-	USAGE_STREAM,
-	USAGE_DYNAMIC,
-	USAGE_STATIC,
-	USAGE_MAX_ENUM
+	BUFFERUSAGE_STREAM,
+	BUFFERUSAGE_DYNAMIC,
+	BUFFERUSAGE_STATIC,
+	BUFFERUSAGE_MAX_ENUM
 };
 
-enum DataType
+// Value types used when interfacing with the GPU (vertex and shader data).
+// The order of this enum affects the dataFormatInfo array.
+enum DataFormat
 {
-	DATA_SNORM8,
-	DATA_UNORM8,
-	DATA_INT8,
-	DATA_UINT8,
+	DATAFORMAT_FLOAT,
+	DATAFORMAT_FLOAT_VEC2,
+	DATAFORMAT_FLOAT_VEC3,
+	DATAFORMAT_FLOAT_VEC4,
+
+	DATAFORMAT_FLOAT_MAT2X2,
+	DATAFORMAT_FLOAT_MAT2X3,
+	DATAFORMAT_FLOAT_MAT2X4,
+
+	DATAFORMAT_FLOAT_MAT3X2,
+	DATAFORMAT_FLOAT_MAT3X3,
+	DATAFORMAT_FLOAT_MAT3X4,
+
+	DATAFORMAT_FLOAT_MAT4X2,
+	DATAFORMAT_FLOAT_MAT4X3,
+	DATAFORMAT_FLOAT_MAT4X4,
+
+	DATAFORMAT_INT32,
+	DATAFORMAT_INT32_VEC2,
+	DATAFORMAT_INT32_VEC3,
+	DATAFORMAT_INT32_VEC4,
+
+	DATAFORMAT_UINT32,
+	DATAFORMAT_UINT32_VEC2,
+	DATAFORMAT_UINT32_VEC3,
+	DATAFORMAT_UINT32_VEC4,
+
+	DATAFORMAT_SNORM8_VEC4,
+	DATAFORMAT_UNORM8_VEC4,
+	DATAFORMAT_INT8_VEC4,
+	DATAFORMAT_UINT8_VEC4,
 
-	DATA_SNORM16,
-	DATA_UNORM16,
-	DATA_INT16,
-	DATA_UINT16,
+	DATAFORMAT_SNORM16_VEC2,
+	DATAFORMAT_SNORM16_VEC4,
 
-	DATA_INT32,
-	DATA_UINT32,
+	DATAFORMAT_UNORM16_VEC2,
+	DATAFORMAT_UNORM16_VEC4,
 
-	DATA_FLOAT,
+	DATAFORMAT_INT16_VEC2,
+	DATAFORMAT_INT16_VEC4,
 
-	DATA_MAX_ENUM
+	DATAFORMAT_UINT16,
+	DATAFORMAT_UINT16_VEC2,
+	DATAFORMAT_UINT16_VEC4,
+
+	DATAFORMAT_BOOL,
+	DATAFORMAT_BOOL_VEC2,
+	DATAFORMAT_BOOL_VEC3,
+	DATAFORMAT_BOOL_VEC4,
+
+	DATAFORMAT_MAX_ENUM
+};
+
+enum DataBaseType
+{
+	DATA_BASETYPE_FLOAT,
+	DATA_BASETYPE_INT,
+	DATA_BASETYPE_UINT,
+	DATA_BASETYPE_SNORM,
+	DATA_BASETYPE_UNORM,
+	DATA_BASETYPE_BOOL,
+	DATA_BASETYPE_MAX_ENUM
 };
 
 enum Winding
@@ -131,12 +177,12 @@ enum Winding
 	WINDING_MAX_ENUM
 };
 
-enum class TriangleIndexMode
+enum TriangleIndexMode
 {
-	NONE,
-	STRIP,
-	FAN,
-	QUADS,
+	TRIANGLEINDEX_NONE,
+	TRIANGLEINDEX_STRIP,
+	TRIANGLEINDEX_FAN,
+	TRIANGLEINDEX_QUADS,
 };
 
 enum class CommonFormat
@@ -154,6 +200,17 @@ enum class CommonFormat
 	XYf_STPf_RGBAub,
 };
 
+struct DataFormatInfo
+{
+	DataBaseType baseType;
+	bool isMatrix;
+	int components;
+	int matrixRows;
+	int matrixColumns;
+	size_t componentSize;
+	size_t size;
+};
+
 struct STf_RGBAub
 {
 	float s, t;
@@ -185,6 +242,8 @@ struct XYf_STf_RGBAub
 	Color32 color;
 };
 
+typedef XYf_STf_RGBAub Vertex;
+
 struct XYf_STus_RGBAub
 {
 	float  x, y;
@@ -221,42 +280,41 @@ struct BufferBindings
 	void clear() { useBits = 0; }
 };
 
-struct AttributeInfo
+struct VertexAttributeInfo
 {
-	DataType type;
-	uint8 components;
 	uint8 bufferIndex;
+	DataFormat format : 8;
 	uint16 offsetFromVertex;
 };
 
-struct BufferLayout
+struct VertexBufferLayout
 {
+	// Attribute step rate is stored outside this struct as a bitmask.
 	uint16 stride;
 };
 
-struct Attributes
+struct VertexAttributes
 {
 	static const uint32 MAX = 32;
 
 	uint32 enableBits = 0; // indexed by attribute
 	uint32 instanceBits = 0; // indexed by buffer
 
-	AttributeInfo attribs[MAX];
-	BufferLayout bufferLayouts[BufferBindings::MAX];
+	VertexAttributeInfo attribs[MAX];
+	VertexBufferLayout bufferLayouts[BufferBindings::MAX];
 
-	Attributes() {}
-	Attributes(CommonFormat format, uint8 bufferindex)
+	VertexAttributes() {}
+	VertexAttributes(CommonFormat format, uint8 bufferindex)
 	{
 		setCommonFormat(format, bufferindex);
 	}
 
-	void set(uint32 index, DataType type, uint8 components, uint16 offsetfromvertex, uint8 bufferindex)
+	void set(uint32 index, DataFormat format, uint16 offsetfromvertex, uint8 bufferindex)
 	{
 		enableBits |= (1u << index);
 
 		attribs[index].bufferIndex = bufferindex;
-		attribs[index].type = type;
-		attribs[index].components = components;
+		attribs[index].format = format;
 		attribs[index].offsetFromVertex = offsetfromvertex;
 	}
 
@@ -306,51 +364,30 @@ inline CommonFormat getSinglePositionFormat(bool is2D)
 	return is2D ? CommonFormat::XYf : CommonFormat::XYZf;
 }
 
-size_t getIndexDataSize(IndexDataType type);
-size_t getDataTypeSize(DataType datatype);
-bool isDataTypeInteger(DataType datatype);
+const DataFormatInfo &getDataFormatInfo(DataFormat format);
 
+size_t getIndexDataSize(IndexDataType type);
 IndexDataType getIndexDataTypeFromMax(size_t maxvalue);
+DataFormat getIndexDataFormat(IndexDataType type);
+IndexDataType getIndexDataType(DataFormat format);
 
 int getIndexCount(TriangleIndexMode mode, int vertexCount);
 
 void fillIndices(TriangleIndexMode mode, uint16 vertexStart, uint16 vertexCount, uint16 *indices);
 void fillIndices(TriangleIndexMode mode, uint32 vertexStart, uint32 vertexCount, uint32 *indices);
 
-bool getConstant(const char *in, BuiltinVertexAttribute &out);
-bool getConstant(BuiltinVertexAttribute in, const char *&out);
-
-bool getConstant(const char *in, IndexDataType &out);
-bool getConstant(IndexDataType in, const char *&out);
-std::vector<std::string> getConstants(IndexDataType);
-
-bool getConstant(const char *in, Usage &out);
-bool getConstant(Usage in, const char *&out);
-std::vector<std::string> getConstants(Usage);
-
-bool getConstant(const char *in, PrimitiveType &out);
-bool getConstant(PrimitiveType in, const char *&out);
-std::vector<std::string> getConstants(PrimitiveType);
-
-bool getConstant(const char *in, AttributeStep &out);
-bool getConstant(AttributeStep in, const char *&out);
-std::vector<std::string> getConstants(AttributeStep);
-
-bool getConstant(const char *in, DataType &out);
-bool getConstant(DataType in, const char *&out);
-std::vector<std::string> getConstants(DataType);
-
-bool getConstant(const char *in, CullMode &out);
-bool getConstant(CullMode in, const char *&out);
-std::vector<std::string> getConstants(CullMode);
-
-bool getConstant(const char *in, Winding &out);
-bool getConstant(Winding in, const char *&out);
-std::vector<std::string> getConstants(Winding);
-
-} // vertex
-
-typedef vertex::XYf_STf_RGBAub Vertex;
+STRINGMAP_DECLARE(BuiltinVertexAttribute);
+STRINGMAP_DECLARE(BufferType);
+STRINGMAP_DECLARE(IndexDataType);
+STRINGMAP_DECLARE(BufferUsage);
+STRINGMAP_DECLARE(PrimitiveType);
+STRINGMAP_DECLARE(AttributeStep);
+STRINGMAP_DECLARE(DataFormat);
+STRINGMAP_DECLARE(DataBaseType);
+STRINGMAP_DECLARE(CullMode);
+STRINGMAP_DECLARE(Winding);
+
+const char *getConstant(BuiltinVertexAttribute attrib);
 
 } // graphics
 } // love

+ 474 - 0
src/modules/graphics/wrap_Buffer.cpp

@@ -0,0 +1,474 @@
+/**
+* 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.
+**/
+
+#include "wrap_Buffer.h"
+#include "Buffer.h"
+#include "common/Data.h"
+
+namespace love
+{
+namespace graphics
+{
+
+static const double defaultComponents[] = {0.0, 0.0, 0.0, 1.0};
+
+template <typename T>
+static inline size_t writeData(lua_State *L, int startidx, int components, char *data)
+{
+	auto componentdata = (T *) data;
+
+	for (int i = 0; i < components; i++)
+		componentdata[i] = (T) (luaL_optnumber(L, startidx + i, defaultComponents[i]));
+
+	return sizeof(T) * components;
+}
+
+template <typename T>
+static inline size_t writeSNormData(lua_State *L, int startidx, int components, char *data)
+{
+	auto componentdata = (T *) data;
+	const auto maxval = std::numeric_limits<T>::max();
+
+	for (int i = 0; i < components; i++)
+		componentdata[i] = (T) (luax_optnumberclamped(L, startidx + i, -1.0, 1.0, defaultComponents[i]) * maxval);
+
+	return sizeof(T) * components;
+}
+
+template <typename T>
+static inline size_t writeUNormData(lua_State *L, int startidx, int components, char *data)
+{
+	auto componentdata = (T *) data;
+	const auto maxval = std::numeric_limits<T>::max();
+
+	for (int i = 0; i < components; i++)
+		componentdata[i] = (T) (luax_optnumberclamped01(L, startidx + i, 1.0) * maxval);
+
+	return sizeof(T) * components;
+}
+
+void luax_writebufferdata(lua_State *L, int startidx, DataFormat format, char *data)
+{
+	switch (format)
+	{
+		case DATAFORMAT_FLOAT:      writeData<float>(L, startidx, 1, data); break;
+		case DATAFORMAT_FLOAT_VEC2: writeData<float>(L, startidx, 2, data); break;
+		case DATAFORMAT_FLOAT_VEC3: writeData<float>(L, startidx, 3, data); break;
+		case DATAFORMAT_FLOAT_VEC4: writeData<float>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_INT32:      writeData<int32>(L, startidx, 1, data); break;
+		case DATAFORMAT_INT32_VEC2: writeData<int32>(L, startidx, 2, data); break;
+		case DATAFORMAT_INT32_VEC3: writeData<int32>(L, startidx, 3, data); break;
+		case DATAFORMAT_INT32_VEC4: writeData<int32>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_UINT32:      writeData<uint32>(L, startidx, 1, data); break;
+		case DATAFORMAT_UINT32_VEC2: writeData<uint32>(L, startidx, 2, data); break;
+		case DATAFORMAT_UINT32_VEC3: writeData<uint32>(L, startidx, 3, data); break;
+		case DATAFORMAT_UINT32_VEC4: writeData<uint32>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_SNORM8_VEC4: writeSNormData<int8>(L, startidx, 4, data); break;
+		case DATAFORMAT_UNORM8_VEC4: writeUNormData<uint8>(L, startidx, 4, data); break;
+		case DATAFORMAT_INT8_VEC4:   writeData<int8>(L, startidx, 4, data); break;
+		case DATAFORMAT_UINT8_VEC4:  writeData<uint8>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_SNORM16_VEC2: writeSNormData<int16>(L, startidx, 2, data); break;
+		case DATAFORMAT_SNORM16_VEC4: writeSNormData<int16>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_UNORM16_VEC2: writeUNormData<uint16>(L, startidx, 2, data); break;
+		case DATAFORMAT_UNORM16_VEC4: writeUNormData<uint16>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_INT16_VEC2: writeData<int16>(L, startidx, 2, data); break;
+		case DATAFORMAT_INT16_VEC4: writeData<int16>(L, startidx, 4, data); break;
+
+		case DATAFORMAT_UINT16:      writeData<uint16>(L, startidx, 1, data); break;
+		case DATAFORMAT_UINT16_VEC2: writeData<uint16>(L, startidx, 2, data); break;
+		case DATAFORMAT_UINT16_VEC4: writeData<uint16>(L, startidx, 4, data); break;
+
+		default: break;
+	}
+}
+
+template <typename T>
+static inline size_t readData(lua_State *L, int components, const char *data)
+{
+	const auto componentdata = (const T *) data;
+
+	for (int i = 0; i < components; i++)
+		lua_pushnumber(L, (lua_Number) componentdata[i]);
+
+	return sizeof(T) * components;
+}
+
+template <typename T>
+static inline size_t readSNormData(lua_State *L, int components, const char *data)
+{
+	const auto componentdata = (const T *) data;
+	const auto maxval = std::numeric_limits<T>::max();
+
+	for (int i = 0; i < components; i++)
+		lua_pushnumber(L, std::max(-1.0, (lua_Number) componentdata[i] / (lua_Number)maxval));
+
+	return sizeof(T) * components;
+}
+
+template <typename T>
+static inline size_t readUNormData(lua_State *L, int components, const char *data)
+{
+	const auto componentdata = (const T *) data;
+	const auto maxval = std::numeric_limits<T>::max();
+
+	for (int i = 0; i < components; i++)
+		lua_pushnumber(L, (lua_Number) componentdata[i] / (lua_Number)maxval);
+
+	return sizeof(T) * components;
+}
+
+void luax_readbufferdata(lua_State *L, DataFormat format, const char *data)
+{
+	switch (format)
+	{
+		case DATAFORMAT_FLOAT:      readData<float>(L, 1, data); break;
+		case DATAFORMAT_FLOAT_VEC2: readData<float>(L, 2, data); break;
+		case DATAFORMAT_FLOAT_VEC3: readData<float>(L, 3, data); break;
+		case DATAFORMAT_FLOAT_VEC4: readData<float>(L, 4, data); break;
+
+		case DATAFORMAT_INT32:      readData<int32>(L, 1, data); break;
+		case DATAFORMAT_INT32_VEC2: readData<int32>(L, 2, data); break;
+		case DATAFORMAT_INT32_VEC3: readData<int32>(L, 3, data); break;
+		case DATAFORMAT_INT32_VEC4: readData<int32>(L, 4, data); break;
+
+		case DATAFORMAT_UINT32:      readData<uint32>(L, 1, data); break;
+		case DATAFORMAT_UINT32_VEC2: readData<uint32>(L, 2, data); break;
+		case DATAFORMAT_UINT32_VEC3: readData<uint32>(L, 3, data); break;
+		case DATAFORMAT_UINT32_VEC4: readData<uint32>(L, 4, data); break;
+
+		case DATAFORMAT_SNORM8_VEC4: readSNormData<int8>(L, 4, data); break;
+		case DATAFORMAT_UNORM8_VEC4: readUNormData<uint8>(L, 4, data); break;
+		case DATAFORMAT_INT8_VEC4:   readData<int8>(L, 4, data); break;
+		case DATAFORMAT_UINT8_VEC4:  readData<uint8>(L, 4, data); break;
+
+		case DATAFORMAT_SNORM16_VEC2: readSNormData<int16>(L, 2, data); break;
+		case DATAFORMAT_SNORM16_VEC4: readSNormData<int16>(L, 4, data); break;
+
+		case DATAFORMAT_UNORM16_VEC2: readUNormData<uint16>(L, 2, data); break;
+		case DATAFORMAT_UNORM16_VEC4: readUNormData<uint16>(L, 4, data); break;
+
+		case DATAFORMAT_INT16_VEC2: readData<int16>(L, 2, data); break;
+		case DATAFORMAT_INT16_VEC4: readData<int16>(L, 4, data); break;
+
+		case DATAFORMAT_UINT16:      readData<uint16>(L, 1, data); break;
+		case DATAFORMAT_UINT16_VEC2: readData<uint16>(L, 2, data); break;
+		case DATAFORMAT_UINT16_VEC4: readData<uint16>(L, 4, data); break;
+
+		default: break;
+	}
+}
+
+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);
+
+	int startindex = (int) luaL_optnumber(L, 3, 1) - 1;
+
+	int count = -1;
+	if (!lua_isnoneornil(L, 4))
+	{
+		count = (int) luaL_checknumber(L, 4);
+		if (count <= 0)
+			return luaL_error(L, "Element count must be greater than 0.");
+	}
+
+	size_t stride = t->getArrayStride();
+	size_t offset = startindex * stride;
+	int arraylength = (int) t->getArrayLength();
+
+	if (startindex >= arraylength || startindex < 0)
+		return luaL_error(L, "Invalid vertex start index (must be between 1 and %d)", arraylength);
+
+	if (luax_istype(L, 2, Data::type))
+	{
+		Data *d = luax_checktype<Data>(L, 2);
+
+		count = count >= 0 ? count : (arraylength - startindex);
+		if (startindex + count > arraylength)
+			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();
+		return 0;
+	}
+
+	const std::vector<Buffer::DataMember> &members = t->getDataMembers();
+
+	int ncomponents = 0;
+	for (const Buffer::DataMember &member : members)
+		ncomponents += member.info.components;
+
+	luaL_checktype(L, 2, LUA_TTABLE);
+	int tablelen = (int) luax_objlen(L, 2);
+
+	lua_rawgeti(L, 2, 1);
+	bool tableoftables = lua_istable(L, -1);
+	lua_pop(L, 1);
+
+	if (!tableoftables)
+	{
+		if (tablelen % ncomponents != 0)
+			return luaL_error(L, "Array length in flat array variant of Buffer:setArrayData must be a multiple of the total number of components (%d)", ncomponents);
+		tablelen /= ncomponents;
+	}
+
+	count = count >= 0 ? std::min(count, tablelen) : tablelen;
+	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;
+
+	if (tableoftables)
+	{
+		for (int i = 0; i < count; i++)
+		{
+			// get arraydata[index]
+			lua_rawgeti(L, 2, i + 1);
+			luaL_checktype(L, -1, LUA_TTABLE);
+
+			// get arraydata[index][j]
+			for (int j = 1; j <= ncomponents; j++)
+				lua_rawgeti(L, -j, j);
+
+			int idx = -ncomponents;
+
+			for (const Buffer::DataMember &member : members)
+			{
+				luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+				idx += member.info.components;
+			}
+
+			lua_pop(L, ncomponents + 1);
+			data += stride;
+		}
+	}
+	else // Flat array
+	{
+		for (int i = 0; i < count; i++)
+		{
+			// get arraydata[arrayindex * ncomponents + componentindex]
+			for (int componentindex = 1; componentindex <= ncomponents; componentindex++)
+				lua_rawgeti(L, 2, i * ncomponents + componentindex);
+
+			int idx = -ncomponents;
+
+			for (const Buffer::DataMember &member : members)
+			{
+				luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+				idx += member.info.components;
+			}
+
+			lua_pop(L, ncomponents);
+			data += stride;
+		}
+	}
+
+	t->setMappedRangeModified(offset, count * stride);
+	t->unmap();
+
+	return 0;
+}
+
+static int w_Buffer_setElement(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+
+	size_t index = (size_t) (luaL_checkinteger(L, 2) - 1);
+	if (index >= t->getArrayLength())
+		return luaL_error(L, "Invalid Buffer element index: %ld", index);
+
+	size_t stride = t->getArrayStride();
+	size_t offset = index * stride;
+	char *data = (char *) t->map() + offset;
+	const auto &members = t->getDataMembers();
+
+	bool istable = lua_istable(L, 3);
+	int idx = istable ? 1 : 3;
+
+	if (istable)
+	{
+		for (const Buffer::DataMember &member : members)
+		{
+			int components = member.info.components;
+
+			for (int i = idx; i < idx + components; i++)
+				lua_rawgeti(L, 3, i);
+
+			luax_writebufferdata(L, -components, member.decl.format, data + member.offset);
+
+			idx += components;
+			lua_pop(L, components);
+		}
+	}
+	else
+	{
+		for (const Buffer::DataMember &member : members)
+		{
+			luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+			idx += member.info.components;
+		}
+	}
+
+	t->setMappedRangeModified(offset, stride);
+	return 0;
+}
+
+static int w_Buffer_getElement(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	if ((t->getMapFlags() & Buffer::MAP_READ) == 0)
+		return luaL_error(L, "Buffer:getElement requires the buffer to be created with the 'cpureadable' setting set to true.");
+
+	size_t index = (size_t) (luaL_checkinteger(L, 2) - 1);
+	if (index >= t->getArrayLength())
+		return luaL_error(L, "Invalid Buffer element index: %ld", index);
+
+	size_t offset = index * t->getArrayStride();
+	const char *data = (const char *) t->map() + offset;
+	const auto &members = t->getDataMembers();
+
+	int n = 0;
+
+	for (const Buffer::DataMember &member : members)
+	{
+		luax_readbufferdata(L, member.decl.format, data + member.offset);
+		n += member.info.components;
+	}
+
+	return n;
+}
+
+static int w_Buffer_getElementCount(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	lua_pushinteger(L, t->getArrayLength());
+	return 1;
+}
+
+static int w_Buffer_getElementStride(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	lua_pushinteger(L, t->getArrayStride());
+	return 1;
+}
+
+static int w_Buffer_getSize(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	lua_pushinteger(L, t->getSize());
+	return 1;
+}
+
+static int w_Buffer_getFormat(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	const auto &members = t->getDataMembers();
+
+	lua_createtable(L, (int) members.size(), 0);
+
+	for (size_t i = 0; i < members.size(); i++)
+	{
+		const Buffer::DataMember &member = members[i];
+
+		lua_createtable(L, 0, 4);
+
+		lua_pushstring(L, member.decl.name.c_str());
+		lua_setfield(L, -2, "name");
+
+		const char *formatstr = "unknown";
+		getConstant(member.decl.format, formatstr);
+		lua_pushstring(L, formatstr);
+		lua_setfield(L, -2, "format");
+
+		lua_pushinteger(L, member.decl.arrayLength);
+		lua_setfield(L, -2, "arraylength");
+
+		lua_pushinteger(L, member.offset);
+		lua_setfield(L, -2, "offset");
+
+		lua_rawseti(L, -2, i + 1);
+	}
+
+	return 1;
+}
+
+static int w_Buffer_isBufferType(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	BufferType buffertype = BUFFERTYPE_MAX_ENUM;
+	const char *typestr = luaL_checkstring(L, 2);
+	if (!getConstant(typestr, buffertype))
+		return luax_enumerror(L, "buffer type", getConstants(buffertype), typestr);
+	luax_pushboolean(L, (t->getTypeFlags() & (1 << buffertype)) != 0);
+	return 1;
+}
+
+static int w_Buffer_isCPUReadable(lua_State *L)
+{
+	Buffer *t = luax_checkbuffer(L, 1);
+	luax_pushboolean(L, (t->getMapFlags() & Buffer::MAP_READ) != 0);
+	return 1;
+}
+
+static const luaL_Reg w_Buffer_functions[] =
+{
+	{ "flush", w_Buffer_flush },
+	{ "setArrayData", w_Buffer_setArrayData },
+	{ "setElement", w_Buffer_setElement },
+	{ "getElement", w_Buffer_getElement },
+	{ "getElementCount", w_Buffer_getElementCount },
+	{ "getElementStride", w_Buffer_getElementStride },
+	{ "getSize", w_Buffer_getSize },
+	{ "getFormat", w_Buffer_getFormat },
+	{ "isBufferType", w_Buffer_isBufferType },
+	{ "isCPUReadable", w_Buffer_isCPUReadable },
+	{ 0, 0 }
+};
+
+extern "C" int luaopen_graphicsbuffer(lua_State *L)
+{
+	return luax_register_type(L, &Buffer::type, w_Buffer_functions, nullptr);
+}
+
+} // graphics
+} // love

+ 39 - 0
src/modules/graphics/wrap_Buffer.h

@@ -0,0 +1,39 @@
+/**
+* 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
+
+// LOVE
+#include "common/runtime.h"
+#include "Buffer.h"
+
+namespace love
+{
+namespace graphics
+{
+
+void luax_writebufferdata(lua_State *L, int startidx, DataFormat format, char *data);
+void luax_readbufferdata(lua_State *L, DataFormat format, const char *data);
+
+Buffer *luax_checkbuffer(lua_State *L, int idx);
+extern "C" int luaopen_graphicsbuffer(lua_State *L);
+
+} // graphics
+} // love

+ 359 - 43
src/modules/graphics/wrap_Graphics.cpp

@@ -37,6 +37,7 @@
 #include <cassert>
 #include <cstring>
 #include <cstdlib>
+#include <sstream>
 
 #include <algorithm>
 
@@ -1324,12 +1325,12 @@ int w_newSpriteBatch(lua_State *L)
 
 	Texture *texture = luax_checktexture(L, 1);
 	int size = (int) luaL_optinteger(L, 2, 1000);
-	vertex::Usage usage = vertex::USAGE_DYNAMIC;
+	BufferUsage usage = BUFFERUSAGE_DYNAMIC;
 	if (lua_gettop(L) > 2)
 	{
 		const char *usagestr = luaL_checkstring(L, 3);
-		if (!vertex::getConstant(usagestr, usage))
-			return luax_enumerror(L, "usage hint", vertex::getConstants(usage), usagestr);
+		if (!getConstant(usagestr, usage))
+			return luax_enumerror(L, "usage hint", getConstants(usage), usagestr);
 	}
 
 	SpriteBatch *t = nullptr;
@@ -1487,22 +1488,309 @@ int w_validateShader(lua_State *L)
 	return 1;
 }
 
-static vertex::Usage luax_optmeshusage(lua_State *L, int idx, vertex::Usage def)
+static BufferUsage luax_optbufferusage(lua_State *L, int idx, BufferUsage def)
 {
 	const char *usagestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx);
 
-	if (usagestr && !vertex::getConstant(usagestr, def))
-		luax_enumerror(L, "usage hint", vertex::getConstants(def), usagestr);
+	if (usagestr && !getConstant(usagestr, def))
+		luax_enumerror(L, "usage hint", getConstants(def), usagestr);
 
 	return def;
 }
 
+static void luax_optbuffersettings(lua_State *L, int idx, Buffer::Settings &settings)
+{
+	if (lua_isnoneornil(L, idx))
+		return;
+
+	luaL_checktype(L, idx, LUA_TTABLE);
+
+	lua_getfield(L, idx, "usage");
+	settings.usage = luax_optbufferusage(L, -1, settings.usage);
+	lua_pop(L, 1);
+
+	if (luax_boolflag(L, idx, "cpureadable", settings.mapFlags & Buffer::MAP_READ))
+		settings.mapFlags = (Buffer::MapFlags)(settings.mapFlags | Buffer::MAP_READ);
+	else
+		settings.mapFlags = (Buffer::MapFlags)(settings.mapFlags & (~Buffer::MAP_READ));
+}
+
+static void luax_checkbufferformat(lua_State *L, int idx, std::vector<Buffer::DataDeclaration> &format)
+{
+	luaL_checktype(L, idx, LUA_TTABLE);
+	int tablelen = luax_objlen(L, idx);
+
+	for (int i = 1; i <= tablelen; i++)
+	{
+		lua_rawgeti(L, idx, i);
+		luaL_checktype(L, -1, LUA_TTABLE);
+
+		Buffer::DataDeclaration decl("", DATAFORMAT_MAX_ENUM);
+
+		lua_getfield(L, -1, "name");
+		if (lua_type(L, -1) != LUA_TSTRING)
+		{
+			std::ostringstream ss;
+			ss << "'name' field expected in array element #";
+			ss << i;
+			ss << " of format table";
+			std::string str = ss.str();
+			luaL_argerror(L, idx, str.c_str());
+		}
+		decl.name = luax_checkstring(L, -1);
+		lua_pop(L, 1);
+
+		lua_getfield(L, -1, "format");
+		if (lua_type(L, -1) != LUA_TSTRING)
+		{
+			std::ostringstream ss;
+			ss << "'format' field expected in array element #";
+			ss << i;
+			ss << " of format table";
+			std::string str = ss.str();
+			luaL_argerror(L, idx, str.c_str());
+		}
+		const char *formatstr = luaL_checkstring(L, -1);
+		if (!getConstant(formatstr, decl.format))
+			luax_enumerror(L, "data format", getConstants(decl.format), formatstr);
+		lua_pop(L, 1);
+
+		decl.arrayLength = luax_intflag(L, -1, "arraylength", 0);
+
+		format.push_back(decl);
+		lua_pop(L, 1);
+	}
+}
+
+static Buffer *luax_newbuffer(lua_State *L, int idx, const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format)
+{
+	size_t arraylength = 0;
+	size_t bytesize = 0;
+	Data *data = nullptr;
+	const void *initialdata = nullptr;
+
+	int ncomponents = 0;
+	for (const Buffer::DataDeclaration &decl : format)
+		ncomponents += getDataFormatInfo(decl.format).components;
+
+	if (luax_istype(L, idx, Data::type))
+	{
+		data = luax_checktype<Data>(L, idx);
+		initialdata = data->getData();
+		bytesize = data->getSize();
+	}
+
+	bool tableoftables = false;
+
+	if (lua_istable(L, idx))
+	{
+		arraylength = luax_objlen(L, idx);
+
+		lua_rawgeti(L, idx, 1);
+		tableoftables = lua_istable(L, -1);
+		lua_pop(L, 1);
+
+		if (!tableoftables)
+		{
+			if (arraylength % ncomponents != 0)
+				luaL_error(L, "Array length in flat array variant of newBuffer must be a multiple of the total number of components (%d)", ncomponents);
+			arraylength /= ncomponents;
+		}
+	}
+	else if (data == nullptr)
+	{
+		lua_Integer len = luaL_checkinteger(L, idx);
+		if (len <= 0)
+			luaL_argerror(L, idx, "number of elements must be greater than 0");
+		arraylength = (size_t) len;
+	}
+
+	Buffer *b = nullptr;
+	luax_catchexcept(L, [&] { b = instance()->newBuffer(settings, format, initialdata, bytesize, arraylength); });
+
+	if (lua_istable(L, idx))
+	{
+		Buffer::Mapper mapper(*b);
+		char *data = (char *) mapper.data;
+		const auto &members = b->getDataMembers();
+		size_t stride = b->getArrayStride();
+
+		if (tableoftables)
+		{
+			for (size_t i = 0; i < arraylength; i++)
+			{
+				// get arraydata[index]
+				lua_rawgeti(L, 2, i + 1);
+				luaL_checktype(L, -1, LUA_TTABLE);
+
+				// get arraydata[index][j]
+				for (int j = 1; j <= ncomponents; j++)
+					lua_rawgeti(L, -j, j);
+
+				int idx = -ncomponents;
+
+				for (const Buffer::DataMember &member : members)
+				{
+					luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+					idx += member.info.components;
+				}
+
+				lua_pop(L, ncomponents + 1);
+				data += stride;
+			}
+		}
+		else // Flat array
+		{
+			for (size_t i = 0; i < arraylength; i++)
+			{
+				// get arraydata[arrayindex * ncomponents + componentindex]
+				for (int componentindex = 1; componentindex <= ncomponents; componentindex++)
+					lua_rawgeti(L, 2, i * ncomponents + componentindex);
+
+				int idx = -ncomponents;
+
+				for (const Buffer::DataMember &member : members)
+				{
+					luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+					idx += member.info.components;
+				}
+
+				lua_pop(L, ncomponents);
+				data += stride;
+			}
+		}
+	}
+
+	return b;
+}
+
+int w_newBuffer(lua_State *L)
+{
+	Buffer::Settings settings(0, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+
+	luaL_checktype(L, 3, LUA_TTABLE);
+
+	for (int i = 0; i < BUFFERTYPE_MAX_ENUM; i++)
+	{
+		BufferType buffertype = (BufferType) i;
+		const char *tname = nullptr;
+		if (!getConstant(buffertype, tname))
+			continue;
+		if (luax_boolflag(L, 3, tname, false))
+			settings.typeFlags = (Buffer::TypeFlags)(settings.typeFlags | (1u << i));
+	}
+
+	luax_optbuffersettings(L, 3, settings);
+
+	std::vector<Buffer::DataDeclaration> format;
+	luax_checkbufferformat(L, 1, format);
+
+	Buffer *b = luax_newbuffer(L, 2, settings, format);
+
+	luax_pushtype(L, b);
+	b->release();
+	return 1;
+}
+
+int w_newVertexBuffer(lua_State *L)
+{
+	Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+	luax_optbuffersettings(L, 3, settings);
+
+	std::vector<Buffer::DataDeclaration> format;
+	luax_checkbufferformat(L, 1, format);
+
+	Buffer *b = luax_newbuffer(L, 2, settings, format);
+
+	luax_pushtype(L, b);
+	b->release();
+	return 1;
+}
+
+int w_newIndexBuffer(lua_State *L)
+{
+	Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC);
+	luax_optbuffersettings(L, 3, settings);
+
+	size_t arraylength = 0;
+	size_t bytesize = 0;
+	DataFormat format = DATAFORMAT_UINT16;
+	Data *data = nullptr;
+	const void *initialdata = nullptr;
+
+	if (luax_istype(L, 1, Data::type))
+	{
+		data = luax_checktype<Data>(L, 1);
+		initialdata = data->getData();
+		bytesize = data->getSize();
+	}
+
+	if (lua_istable(L, 1))
+	{
+		arraylength = luax_objlen(L, 1);
+
+		// Scan array for invalid types and the max value.
+		lua_Integer maxvalue = 0;
+		for (size_t i = 0; i < arraylength; i++)
+		{
+			lua_rawgeti(L, 1, i + 1);
+			lua_Integer v = luaL_checkinteger(L, -1) - 1;
+			lua_pop(L, 1);
+			if (v < 0)
+				return luaL_argerror(L, 1, "expected positive integer values in array");
+			else
+				maxvalue = std::max(maxvalue, v);
+		}
+
+		format = getIndexDataFormat(getIndexDataTypeFromMax(maxvalue));
+	}
+	else if (data == nullptr)
+	{
+		lua_Integer len = luaL_checkinteger(L, 1);
+		if (len <= 0)
+			return luaL_argerror(L, 1, "number of elements must be greater than 0");
+		arraylength = (size_t) len;
+	}
+
+	if (data != nullptr || !lua_isnoneornil(L, 2))
+	{
+		const char *formatstr = luaL_checkstring(L, 2);
+		if (!getConstant(formatstr, format))
+			return luax_enumerror(L, "index data format", getConstants(format), formatstr);
+	}
+
+	Buffer *b = nullptr;
+	luax_catchexcept(L, [&] { b = instance()->newBuffer(settings, format, initialdata, bytesize, arraylength); });
+
+	if (lua_istable(L, 1))
+	{
+		Buffer::Mapper mapper(*b);
+		uint16 *u16data = (uint16 *) mapper.data;
+		uint32 *u32data = (uint32 *) mapper.data;
+
+		for (size_t i = 0; i < arraylength; i++)
+		{
+			lua_rawgeti(L, 1, i + 1);
+			lua_Integer v = luaL_checkinteger(L, -1) - 1;
+			lua_pop(L, 1);
+			if (format == DATAFORMAT_UINT16)
+				u16data[i] = (uint16) v;
+			else
+				u32data[i] = (uint32) v;
+		}
+	}
+
+	luax_pushtype(L, b);
+	b->release();
+	return 1;
+}
+
 static PrimitiveType luax_optmeshdrawmode(lua_State *L, int idx, PrimitiveType def)
 {
 	const char *modestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx);
 
-	if (modestr && !vertex::getConstant(modestr, def))
-		luax_enumerror(L, "mesh draw mode", vertex::getConstants(def), modestr);
+	if (modestr && !getConstant(modestr, def))
+		luax_enumerror(L, "mesh draw mode", getConstants(def), modestr);
 
 	return def;
 }
@@ -1512,7 +1800,9 @@ static Mesh *newStandardMesh(lua_State *L)
 	Mesh *t = nullptr;
 
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 2, PRIMITIVE_TRIANGLE_FAN);
-	vertex::Usage usage = luax_optmeshusage(L, 3, vertex::USAGE_DYNAMIC);
+	BufferUsage usage = luax_optbufferusage(L, 3, BUFFERUSAGE_DYNAMIC);
+
+	std::vector<Buffer::DataDeclaration> format = Mesh::getDefaultVertexFormat();
 
 	// First argument is a table of standard vertices, or the number of
 	// standard vertices.
@@ -1552,12 +1842,12 @@ static Mesh *newStandardMesh(lua_State *L)
 			vertices.push_back(v);
 		}
 
-		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertices, drawmode, usage); });
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(format, vertices.data(), vertices.size() * sizeof(Vertex), drawmode, usage); });
 	}
 	else
 	{
 		int count = (int) luaL_checkinteger(L, 1);
-		luax_catchexcept(L, [&](){ t = instance()->newMesh(count, drawmode, usage); });
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(format, count, drawmode, usage); });
 	}
 
 	return t;
@@ -1569,10 +1859,10 @@ static Mesh *newCustomMesh(lua_State *L)
 
 	// First argument is the vertex format, second is a table of vertices or
 	// the number of vertices.
-	std::vector<Mesh::AttribFormat> vertexformat;
+	std::vector<Buffer::DataDeclaration> vertexformat;
 
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 3, PRIMITIVE_TRIANGLE_FAN);
-	vertex::Usage usage = luax_optmeshusage(L, 4, vertex::USAGE_DYNAMIC);
+	BufferUsage usage = luax_optbufferusage(L, 4, BUFFERUSAGE_DYNAMIC);
 
 	lua_rawgeti(L, 1, 1);
 	if (!lua_istable(L, -1))
@@ -1591,27 +1881,53 @@ static Mesh *newCustomMesh(lua_State *L)
 		for (int j = 1; j <= 3; j++)
 			lua_rawgeti(L, -j, j);
 
-		Mesh::AttribFormat format;
-		format.name = luaL_checkstring(L, -3);
+		const char *name = luaL_checkstring(L, -3);
 
+		DataFormat format = DATAFORMAT_MAX_ENUM;
 		const char *tname = luaL_checkstring(L, -2);
-		if (strcmp(tname, "byte") == 0) // Legacy name.
-			format.type = vertex::DATA_UNORM8;
-		else if (!vertex::getConstant(tname, format.type))
-		{
-			luax_enumerror(L, "Mesh vertex data type name", vertex::getConstants(format.type), tname);
-			return nullptr;
-		}
 
-		format.components = (int) luaL_checkinteger(L, -1);
-		if (format.components <= 0 || format.components > 4)
+		if (!lua_isnoneornil(L, -1))
 		{
-			luaL_error(L, "Number of vertex attribute components must be between 1 and 4 (got %d)", format.components);
-			return nullptr;
+			int components = (int) luaL_checkinteger(L, -1);
+
+			// Check deprecated format names.
+			if (strcmp(tname, "byte") == 0 || strcmp(tname, "unorm8") == 0)
+			{
+				if (components == 4)
+					format = DATAFORMAT_UNORM8_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
+			}
+			else if (strcmp(tname, "unorm16") == 0)
+			{
+				if (components == 2)
+					format = DATAFORMAT_UNORM16_VEC2;
+				else if (components == 4)
+					format = DATAFORMAT_UNORM16_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
+
+			}
+			else if (strcmp(tname, "float") == 0)
+			{
+				if (components == 1)
+					format = DATAFORMAT_FLOAT;
+				else if (components == 2)
+					format = DATAFORMAT_FLOAT_VEC2;
+				else if (components == 3)
+					format = DATAFORMAT_FLOAT_VEC3;
+				else if (components == 4)
+					format = DATAFORMAT_FLOAT_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
+			}
 		}
 
+		if (format == DATAFORMAT_MAX_ENUM && !getConstant(tname, format))
+			luax_enumerror(L, "vertex data format", getConstants(format), tname);
+
 		lua_pop(L, 4);
-		vertexformat.push_back(format);
+		vertexformat.emplace_back(name, format);
 	}
 
 	if (lua_isnumber(L, 2))
@@ -1636,10 +1952,6 @@ static Mesh *newCustomMesh(lua_State *L)
 		}
 		lua_pop(L, 1);
 
-		int vertexcomponents = 0;
-		for (const Mesh::AttribFormat &format : vertexformat)
-			vertexcomponents += format.components;
-
 		size_t numvertices = luax_objlen(L, 2);
 
 		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertexformat, numvertices, drawmode, usage); });
@@ -1656,19 +1968,19 @@ static Mesh *newCustomMesh(lua_State *L)
 			int n = 0;
 			for (size_t i = 0; i < vertexformat.size(); i++)
 			{
-				int components = vertexformat[i].components;
+				const auto &info = getDataFormatInfo(vertexformat[i].format);
 
 				// get vertices[vertindex][n]
-				for (int c = 0; c < components; c++)
+				for (int c = 0; c < info.components; c++)
 				{
 					n++;
 					lua_rawgeti(L, -(c + 1), n);
 				}
 
 				// Fetch the values from Lua and store them in data buffer.
-				luax_writeAttributeData(L, -components, vertexformat[i].type, components, data);
+				luax_writebufferdata(L, -info.components, vertexformat[i].format, data);
 
-				lua_pop(L, components);
+				lua_pop(L, info.components);
 
 				luax_catchexcept(L,
 					[&](){ t->setVertexAttribute(vertindex, i, data, sizeof(float) * 4); },
@@ -2171,8 +2483,8 @@ int w_setMeshCullMode(lua_State *L)
 	const char *str = luaL_checkstring(L, 1);
 	CullMode mode;
 
-	if (!vertex::getConstant(str, mode))
-		return luax_enumerror(L, "cull mode", vertex::getConstants(mode), str);
+	if (!getConstant(str, mode))
+		return luax_enumerror(L, "cull mode", getConstants(mode), str);
 
 	luax_catchexcept(L, [&]() { instance()->setMeshCullMode(mode); });
 	return 0;
@@ -2182,7 +2494,7 @@ int w_getMeshCullMode(lua_State *L)
 {
 	CullMode mode = instance()->getMeshCullMode();
 	const char *str;
-	if (!vertex::getConstant(mode, str))
+	if (!getConstant(mode, str))
 		return luaL_error(L, "Unknown cull mode");
 	lua_pushstring(L, str);
 	return 1;
@@ -2191,10 +2503,10 @@ int w_getMeshCullMode(lua_State *L)
 int w_setFrontFaceWinding(lua_State *L)
 {
 	const char *str = luaL_checkstring(L, 1);
-	vertex::Winding winding;
+	Winding winding;
 
-	if (!vertex::getConstant(str, winding))
-		return luax_enumerror(L, "vertex winding", vertex::getConstants(winding), str);
+	if (!getConstant(str, winding))
+		return luax_enumerror(L, "vertex winding", getConstants(winding), str);
 
 	luax_catchexcept(L, [&]() { instance()->setFrontFaceWinding(winding); });
 	return 0;
@@ -2202,9 +2514,9 @@ int w_setFrontFaceWinding(lua_State *L)
 
 int w_getFrontFaceWinding(lua_State *L)
 {
-	vertex::Winding winding = instance()->getFrontFaceWinding();
+	Winding winding = instance()->getFrontFaceWinding();
 	const char *str;
-	if (!vertex::getConstant(winding, str))
+	if (!getConstant(winding, str))
 		return luaL_error(L, "Unknown vertex winding");
 	lua_pushstring(L, str);
 	return 1;
@@ -3073,6 +3385,9 @@ static const luaL_Reg functions[] =
 	{ "newSpriteBatch", w_newSpriteBatch },
 	{ "newParticleSystem", w_newParticleSystem },
 	{ "newShader", w_newShader },
+	{ "newBuffer", w_newBuffer },
+	{ "newVertexBuffer", w_newVertexBuffer },
+	{ "newIndexBuffer", w_newIndexBuffer },
 	{ "newMesh", w_newMesh },
 	{ "newText", w_newText },
 	{ "_newVideo", w_newVideo },
@@ -3202,6 +3517,7 @@ static const lua_CFunction types[] =
 	luaopen_texture,
 	luaopen_font,
 	luaopen_quad,
+	luaopen_graphicsbuffer,
 	luaopen_spritebatch,
 	luaopen_particlesystem,
 	luaopen_shader,

+ 1 - 0
src/modules/graphics/wrap_Graphics.h

@@ -31,6 +31,7 @@
 #include "wrap_Mesh.h"
 #include "wrap_Text.h"
 #include "wrap_Video.h"
+#include "wrap_Buffer.h"
 #include "Graphics.h"
 
 namespace love

+ 133 - 184
src/modules/graphics/wrap_Mesh.cpp

@@ -20,6 +20,7 @@
 
 // LOVE
 #include "wrap_Mesh.h"
+#include "wrap_Buffer.h"
 #include "Texture.h"
 #include "wrap_Texture.h"
 
@@ -36,140 +37,6 @@ Mesh *luax_checkmesh(lua_State *L, int idx)
 	return luax_checktype<Mesh>(L, idx);
 }
 
-static const double defaultComponents[] = {0.0, 0.0, 0.0, 1.0};
-
-template <typename T>
-static inline size_t writeData(lua_State *L, int startidx, int components, char *data)
-{
-	auto componentdata = (T *) data;
-
-	for (int i = 0; i < components; i++)
-		componentdata[i] = (T) (luaL_optnumber(L, startidx + i, defaultComponents[i]));
-
-	return sizeof(T) * components;
-}
-
-template <typename T>
-static inline size_t writeSNormData(lua_State *L, int startidx, int components, char *data)
-{
-	auto componentdata = (T *) data;
-	auto maxval = std::numeric_limits<T>::max();
-
-	for (int i = 0; i < components; i++)
-		componentdata[i] = (T) (luax_optnumberclamped(L, startidx + i, -1.0, 1.0, defaultComponents[i]) * maxval);
-
-	return sizeof(T) * components;
-}
-
-template <typename T>
-static inline size_t writeUNormData(lua_State *L, int startidx, int components, char *data)
-{
-	auto componentdata = (T *) data;
-	auto maxval = std::numeric_limits<T>::max();
-
-	for (int i = 0; i < components; i++)
-		componentdata[i] = (T) (luax_optnumberclamped01(L, startidx + i, 1.0) * maxval);
-
-	return sizeof(T) * components;
-}
-
-char *luax_writeAttributeData(lua_State *L, int startidx, vertex::DataType type, int components, char *data)
-{
-	switch (type)
-	{
-	case vertex::DATA_SNORM8:
-		return data + writeSNormData<int8>(L, startidx, components, data);
-	case vertex::DATA_UNORM8:
-		return data + writeUNormData<uint8>(L, startidx, components, data);
-	case vertex::DATA_INT8:
-		return data + writeData<int8>(L, startidx, components, data);
-	case vertex::DATA_UINT8:
-		return data + writeData<uint8>(L, startidx, components, data);
-	case vertex::DATA_SNORM16:
-		return data + writeSNormData<int16>(L, startidx, components, data);
-	case vertex::DATA_UNORM16:
-		return data + writeUNormData<uint16>(L, startidx, components, data);
-	case vertex::DATA_INT16:
-		return data + writeData<int16>(L, startidx, components, data);
-	case vertex::DATA_UINT16:
-		return data + writeData<uint16>(L, startidx, components, data);
-	case vertex::DATA_INT32:
-		return data + writeData<int32>(L, startidx, components, data);
-	case vertex::DATA_UINT32:
-		return data + writeData<uint32>(L, startidx, components, data);
-	case vertex::DATA_FLOAT:
-		return data + writeData<float>(L, startidx, components, data);
-	default:
-		return data;
-	}
-}
-
-template <typename T>
-static inline size_t readData(lua_State *L, int components, const char *data)
-{
-	auto componentdata = (const T *) data;
-
-	for (int i = 0; i < components; i++)
-		lua_pushnumber(L, (lua_Number) componentdata[i]);
-
-	return sizeof(T) * components;
-}
-
-template <typename T>
-static inline size_t readSNormData(lua_State *L, int components, const char *data)
-{
-	auto componentdata = (const T *) data;
-	auto maxval = std::numeric_limits<T>::max();
-
-	for (int i = 0; i < components; i++)
-		lua_pushnumber(L, std::max(-1.0, (lua_Number) componentdata[i] / (lua_Number)maxval));
-
-	return sizeof(T) * components;
-}
-
-template <typename T>
-static inline size_t readUNormData(lua_State *L, int components, const char *data)
-{
-	auto componentdata = (const T *) data;
-	auto maxval = std::numeric_limits<T>::max();
-
-	for (int i = 0; i < components; i++)
-		lua_pushnumber(L, (lua_Number) componentdata[i] / (lua_Number)maxval);
-
-	return sizeof(T) * components;
-}
-
-const char *luax_readAttributeData(lua_State *L, vertex::DataType type, int components, const char *data)
-{
-	switch (type)
-	{
-	case vertex::DATA_SNORM8:
-		return data + readSNormData<int8>(L, components, data);
-	case vertex::DATA_UNORM8:
-		return data + readUNormData<uint8>(L, components, data);
-	case vertex::DATA_INT8:
-		return data + readData<int8>(L, components, data);
-	case vertex::DATA_UINT8:
-		return data + readData<uint8>(L, components, data);
-	case vertex::DATA_SNORM16:
-		return data + readSNormData<int16>(L, components, data);
-	case vertex::DATA_UNORM16:
-		return data + readUNormData<uint16>(L, components, data);
-	case vertex::DATA_INT16:
-		return data + readData<int16>(L, components, data);
-	case vertex::DATA_UINT16:
-		return data + readData<uint16>(L, components, data);
-	case vertex::DATA_INT32:
-		return data + readData<int32>(L, components, data);
-	case vertex::DATA_UINT32:
-		return data + readData<uint32>(L, components, data);
-	case vertex::DATA_FLOAT:
-		return data + readData<float>(L, components, data);
-	default:
-		return data;
-	}
-}
-
 int w_Mesh_setVertices(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
@@ -188,7 +55,7 @@ int w_Mesh_setVertices(lua_State *L)
 	size_t byteoffset = vertstart * stride;
 	int totalverts = (int) t->getVertexCount();
 
-	if (vertstart >= totalverts)
+	if (vertstart >= totalverts || vertstart < 0)
 		return luaL_error(L, "Invalid vertex start index (must be between 1 and %d)", totalverts);
 
 	if (luax_istype(L, 2, Data::type))
@@ -215,11 +82,11 @@ int w_Mesh_setVertices(lua_State *L)
 	if (vertstart + vertcount > totalverts)
 		return luaL_error(L, "Too many vertices (expected at most %d, got %d)", totalverts - vertstart, vertcount);
 
-	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
+	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 
 	int ncomponents = 0;
-	for (const Mesh::AttribFormat &format : vertexformat)
-		ncomponents += format.components;
+	for (const Buffer::DataMember &member : vertexformat)
+		ncomponents += member.info.components;
 
 	char *data = (char *) t->mapVertexData() + byteoffset;
 
@@ -235,14 +102,16 @@ int w_Mesh_setVertices(lua_State *L)
 
 		int idx = -ncomponents;
 
-		for (const Mesh::AttribFormat &format : vertexformat)
+		for (const Buffer::DataMember &member : vertexformat)
 		{
 			// Fetch the values from Lua and store them in data buffer.
-			data = luax_writeAttributeData(L, idx, format.type, format.components, data);
-			idx += format.components;
+			luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+			idx += member.info.components;
 		}
 
 		lua_pop(L, ncomponents + 1);
+
+		data += stride;
 	}
 
 	t->unmapVertexData(byteoffset, vertcount * stride);
@@ -256,34 +125,34 @@ int w_Mesh_setVertex(lua_State *L)
 
 	bool istable = lua_istable(L, 3);
 
-	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
-
+	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 	char *data = (char *) t->getVertexScratchBuffer();
-	char *writtendata = data;
 
 	int idx = istable ? 1 : 3;
 
 	if (istable)
 	{
-		for (const Mesh::AttribFormat &format : vertexformat)
+		for (const Buffer::DataMember &member : vertexformat)
 		{
-			for (int i = idx; i < idx + format.components; i++)
+			int components = member.info.components;
+
+			for (int i = idx; i < idx + components; i++)
 				lua_rawgeti(L, 3, i);
 
 			// Fetch the values from Lua and store them in data buffer.
-			writtendata = luax_writeAttributeData(L, -format.components, format.type, format.components, writtendata);
+			luax_writebufferdata(L, -components, member.decl.format, data + member.offset);
 
-			idx += format.components;
-			lua_pop(L, format.components);
+			idx += components;
+			lua_pop(L, components);
 		}
 	}
 	else
 	{
-		for (const Mesh::AttribFormat &format : vertexformat)
+		for (const Buffer::DataMember &member : vertexformat)
 		{
 			// Fetch the values from Lua and store them in data buffer.
-			writtendata = luax_writeAttributeData(L, idx, format.type, format.components, writtendata);
-			idx += format.components;
+			luax_writebufferdata(L, idx, member.decl.format, data + member.offset);
+			idx += member.info.components;
 		}
 	}
 
@@ -296,19 +165,18 @@ int w_Mesh_getVertex(lua_State *L)
 	Mesh *t = luax_checkmesh(L, 1);
 	size_t index = (size_t) luaL_checkinteger(L, 2) - 1;
 
-	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
+	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 
 	char *data = (char *) t->getVertexScratchBuffer();
-	const char *readdata = data;
 
 	luax_catchexcept(L, [&](){ t->getVertex(index, data, t->getVertexStride()); });
 
 	int n = 0;
 
-	for (const Mesh::AttribFormat &format : vertexformat)
+	for (const Buffer::DataMember &member : vertexformat)
 	{
-		readdata = luax_readAttributeData(L, format.type, format.components, readdata);
-		n += format.components;
+		luax_readbufferdata(L, member.decl.format, data + member.offset);
+		n += member.info.components;
 	}
 
 	return n;
@@ -320,15 +188,18 @@ int w_Mesh_setVertexAttribute(lua_State *L)
 	size_t vertindex = (size_t) luaL_checkinteger(L, 2) - 1;
 	int attribindex = (int) luaL_checkinteger(L, 3) - 1;
 
-	vertex::DataType type;
-	int components;
-	luax_catchexcept(L, [&](){ type = t->getAttributeInfo(attribindex, components); });
+	const auto &vertexformat = t->getVertexFormat();
+
+	if (attribindex < 0 || attribindex >= (int) vertexformat.size())
+		return luaL_error(L, "Invalid vertex attribute index: %d", attribindex + 1);
+
+	const Buffer::DataMember &member = vertexformat[attribindex];
 
 	// Maximum possible size for a single vertex attribute.
 	char data[sizeof(float) * 4];
 
 	// Fetch the values from Lua and store them in the data buffer.
-	luax_writeAttributeData(L, 4, type, components, data);
+	luax_writebufferdata(L, 4, member.decl.format, data);
 
 	luax_catchexcept(L, [&](){ t->setVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
 	return 0;
@@ -340,17 +211,20 @@ int w_Mesh_getVertexAttribute(lua_State *L)
 	size_t vertindex = (size_t) luaL_checkinteger(L, 2) - 1;
 	int attribindex = (int) luaL_checkinteger(L, 3) - 1;
 
-	vertex::DataType type;
-	int components;
-	luax_catchexcept(L, [&](){ type = t->getAttributeInfo(attribindex, components); });
+	const auto &vertexformat = t->getVertexFormat();
+
+	if (attribindex < 0 || attribindex >= (int) vertexformat.size())
+		return luaL_error(L, "Invalid vertex attribute index: %d", attribindex + 1);
+
+	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); });
 
-	luax_readAttributeData(L, type, components, data);
-	return components;
+	luax_readbufferdata(L, member.decl.format, data);
+	return member.info.components;
 }
 
 int w_Mesh_getVertexCount(lua_State *L)
@@ -364,28 +238,27 @@ int w_Mesh_getVertexFormat(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
 
-	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
+	const std::vector<Buffer::DataMember> &vertexformat = t->getVertexFormat();
 	lua_createtable(L, (int) vertexformat.size(), 0);
 
 	const char *tname = nullptr;
 
 	for (size_t i = 0; i < vertexformat.size(); i++)
 	{
-		if (!vertex::getConstant(vertexformat[i].type, tname))
-			return luax_enumerror(L, "vertex attribute data type", vertex::getConstants(vertexformat[i].type), tname);
+		const auto &decl = vertexformat[i].decl;
+
+		if (!getConstant(decl.format, tname))
+			return luax_enumerror(L, "vertex attribute data type", getConstants(decl.format), tname);
 
 		lua_createtable(L, 3, 0);
 
-		lua_pushstring(L, vertexformat[i].name.c_str());
+		lua_pushstring(L, decl.name.c_str());
 		lua_rawseti(L, -2, 1);
 
 		lua_pushstring(L, tname);
 		lua_rawseti(L, -2, 2);
 
-		lua_pushinteger(L, vertexformat[i].components);
-		lua_rawseti(L, -2, 3);
-
-		// format[i] = {name, type, components}
+		// format[i] = {name, type}
 		lua_rawseti(L, -2, (int) i + 1);
 	}
 
@@ -415,16 +288,29 @@ int w_Mesh_attachAttribute(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	const char *name = luaL_checkstring(L, 2);
-	Mesh *mesh = luax_checkmesh(L, 3);
+
+	Buffer *buffer = nullptr;
+	if (luax_istype(L, 3, Buffer::type))
+	{
+		buffer = luax_checktype<Buffer>(L, 3);
+	}
+	else
+	{
+		Mesh *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;
 	const char *stepstr = lua_isnoneornil(L, 4) ? nullptr : luaL_checkstring(L, 4);
-	if (stepstr != nullptr && !vertex::getConstant(stepstr, step))
-		return luax_enumerror(L, "vertex attribute step", vertex::getConstants(step), stepstr);
+	if (stepstr != nullptr && !getConstant(stepstr, step))
+		return luax_enumerror(L, "vertex attribute step", getConstants(step), stepstr);
 
 	const char *attachname = luaL_optstring(L, 5, name);
 
-	luax_catchexcept(L, [&](){ t->attachAttribute(name, mesh, attachname, step); });
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, attachname, step); });
 	return 0;
 }
 
@@ -438,6 +324,48 @@ int w_Mesh_detachAttribute(lua_State *L)
 	return 1;
 }
 
+int w_Mesh_getAttachedAttributes(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	const auto &attributes = t->getAttachedAttributes();
+
+	lua_createtable(L, (int) attributes.size(), 0);
+
+	for (int i = 0; i < (int) attributes.size(); i++)
+	{
+		const auto &attrib = attributes[i];
+
+		lua_createtable(L, 4, 0);
+
+		luax_pushstring(L, attrib.name);
+		lua_rawseti(L, -1, 1);
+
+		luax_pushtype(L, attrib.buffer.get());
+		lua_rawseti(L, -1, 2);
+
+		const char *stepstr = nullptr;
+		if (!getConstant(attrib.step, stepstr))
+			return luaL_error(L, "Invalid vertex attribute step.");
+		lua_pushstring(L, stepstr);
+		lua_rawseti(L, -1, 3);
+
+		const Buffer::DataMember &member = attrib.buffer->getDataMember(attrib.indexInBuffer);
+		luax_pushstring(L, member.decl.name);
+		lua_rawseti(L, -1, 4);
+
+		lua_rawseti(L, -1, i + 1);
+	}
+
+	return 1;
+}
+
+int w_Mesh_getVertexBuffer(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	luax_pushtype(L, t->getVertexBuffer());
+	return 1;
+}
+
 int w_Mesh_flush(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
@@ -462,10 +390,10 @@ int w_Mesh_setVertexMap(lua_State *L)
 
 		const char *indextypestr = luaL_checkstring(L, 3);
 		IndexDataType indextype;
-		if (!vertex::getConstant(indextypestr, indextype))
-			return luax_enumerror(L, "index data type", vertex::getConstants(indextype), indextypestr);
+		if (!getConstant(indextypestr, indextype))
+			return luax_enumerror(L, "index data type", getConstants(indextype), indextypestr);
 
-		size_t datatypesize = vertex::getIndexDataSize(indextype);
+		size_t datatypesize = getIndexDataSize(indextype);
 
 		int indexcount = (int) luaL_optinteger(L, 4, d->getSize() / datatypesize);
 
@@ -528,6 +456,23 @@ int w_Mesh_getVertexMap(lua_State *L)
 	return 1;
 }
 
+int w_Mesh_setIndexBuffer(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	Buffer *b = nullptr;
+	if (!lua_isnoneornil(L, 2))
+		b = luax_checkbuffer(L, 2);
+	luax_catchexcept(L, [&]() { t->setIndexBuffer(b); });
+	return 0;
+}
+
+int w_Mesh_getIndexBuffer(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	luax_pushtype(L, t->getIndexBuffer());
+	return 1;
+}
+
 int w_Mesh_setTexture(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
@@ -561,8 +506,8 @@ int w_Mesh_setDrawMode(lua_State *L)
 	const char *str = luaL_checkstring(L, 2);
 	PrimitiveType mode;
 
-	if (!vertex::getConstant(str, mode))
-		return luax_enumerror(L, "mesh draw mode", vertex::getConstants(mode), str);
+	if (!getConstant(str, mode))
+		return luax_enumerror(L, "mesh draw mode", getConstants(mode), str);
 
 	t->setDrawMode(mode);
 	return 0;
@@ -574,7 +519,7 @@ int w_Mesh_getDrawMode(lua_State *L)
 	PrimitiveType mode = t->getDrawMode();
 	const char *str;
 
-	if (!vertex::getConstant(mode, str))
+	if (!getConstant(mode, str))
 		return luaL_error(L, "Unknown mesh draw mode.");
 
 	lua_pushstring(L, str);
@@ -624,9 +569,13 @@ static const luaL_Reg w_Mesh_functions[] =
 	{ "isAttributeEnabled", w_Mesh_isAttributeEnabled },
 	{ "attachAttribute", w_Mesh_attachAttribute },
 	{ "detachAttribute", w_Mesh_detachAttribute },
+	{ "getAttachedAttributes", w_Mesh_getAttachedAttributes },
+	{ "getVertexBuffer", w_Mesh_getVertexBuffer },
 	{ "flush", w_Mesh_flush },
 	{ "setVertexMap", w_Mesh_setVertexMap },
 	{ "getVertexMap", w_Mesh_getVertexMap },
+	{ "setIndexBuffer", w_Mesh_setIndexBuffer },
+	{ "getIndexBuffer", w_Mesh_getIndexBuffer },
 	{ "setTexture", w_Mesh_setTexture },
 	{ "getTexture", w_Mesh_getTexture },
 	{ "setDrawMode", w_Mesh_setDrawMode },

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

@@ -30,9 +30,6 @@ namespace love
 namespace graphics
 {
 
-char *luax_writeAttributeData(lua_State *L, int startidx, vertex::DataType type, int components, char *data);
-const char *luax_readAttributeData(lua_State *L, vertex::DataType type, int components, const char *data);
-
 Mesh *luax_checkmesh(lua_State *L, int idx);
 extern "C" int luaopen_mesh(lua_State *L);
 

+ 15 - 2
src/modules/graphics/wrap_SpriteBatch.cpp

@@ -216,9 +216,22 @@ int w_SpriteBatch_attachAttribute(lua_State *L)
 {
 	SpriteBatch *t = luax_checkspritebatch(L, 1);
 	const char *name = luaL_checkstring(L, 2);
-	Mesh *m = luax_checktype<Mesh>(L, 3);
 
-	luax_catchexcept(L, [&](){ t->attachAttribute(name, m); });
+	Buffer *buffer = nullptr;
+	if (luax_istype(L, 3, Buffer::type))
+	{
+		buffer = luax_checktype<Buffer>(L, 3);
+	}
+	else
+	{
+		Mesh *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); });
 	return 0;
 }