Browse Source

Added the ability to have custom vertex attributes in Meshes (resolves issue #768.)

Added new love.graphics.newMesh variants: newMesh(vertexformat, vertices [, drawmode, meshusage]) and newMesh(vertexformat, numvertices [, drawmode, meshusage]).

Replaced the regular love.graphics.newMesh variants with newMesh(vertices [, drawmode, meshusage]) and newMesh(numvertices [, drawmode, meshusage]). To use an image or canvas with a mesh, use Mesh:setTexture.

vertexformat is a table with the following prototype:
{
  {attributename, datatype, components},
  {attributename, datatype, components},
  ...
}

Where attributename is the name of the vertex attribute (can be the built-in names 'VertexPosition', 'VertexTexCoord', or 'VertexColor', or a custom name for use in a vertex shader), datatype is the type of values used for the attribute ('float' or 'byte'), and components is the number of components in the vertex attribute (between 1 and 4.)

The vertex format is used to determine the layout of the vertices in the mesh, for example the 'regular' newMesh variants use this vertex format:
format = {
  {"VertexPosition", "float", 2},
  {"VertexTexCoord", "float", 2},
  {"VertexColor", "byte", 4},
}

The mesh usage parameter accepts the same constants as the spritebatch usage hint in love.graphics.newSpriteBatch - "dynamic", "static", and "stream".

Mesh:setVertex now sets *all* vertex attributes for a specific vertex in the Mesh.

Added Mesh:setVertexAttribute(vertexindex, attributeindex, attributevalue1, ...), which sets the values for a specific vertex attribute in a specific vertex in the Mesh (resolves issue #784.)

Added Mesh:getVertexFormat and Mesh:flush.

Added Mesh:setAttributeEnabled(attributename, enable) and Mesh:isAttributeEnabled(attributename), to enable or disable the use of a specific attribute when drawing the Mesh.

Added Mesh:attachAttribute(attributename, mesh), which makes the Mesh use a vertex attribute from another mesh when drawing the Mesh. This can be used to separate out vertex attributes which are updated at different rates into different meshes, and to share vertex data between multiple meshes.

Removed Mesh:setVertices, Mesh:getVertices, and Mesh:setVertexColors.
Alex Szpakowski 10 years ago
parent
commit
2844499ef7

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

@@ -3150,7 +3150,7 @@
 		08FB7793FE84155DC02AAC07 /* Project object */ = {
 		08FB7793FE84155DC02AAC07 /* Project object */ = {
 			isa = PBXProject;
 			isa = PBXProject;
 			attributes = {
 			attributes = {
-				LastUpgradeCheck = 0610;
+				LastUpgradeCheck = 0630;
 				TargetAttributes = {
 				TargetAttributes = {
 					FA0B78DC1A958B90000E1D17 = {
 					FA0B78DC1A958B90000E1D17 = {
 						CreatedOnToolsVersion = 6.1.1;
 						CreatedOnToolsVersion = 6.1.1;
@@ -3761,7 +3761,6 @@
 		10D5479E63C26BB35EB5482E /* Release */ = {
 		10D5479E63C26BB35EB5482E /* Release */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
 				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
@@ -3810,7 +3809,6 @@
 		64274E785071353E1A1D0D4B /* Debug */ = {
 		64274E785071353E1A1D0D4B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
 				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
@@ -3863,7 +3861,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD)";
 				GCC_PREPROCESSOR_DEFINITIONS = (
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					"$(inherited)",
 					LOVE_SUPPORT_IMAGEIO,
 					LOVE_SUPPORT_IMAGEIO,
@@ -3905,7 +3902,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD)";
 				COPY_PHASE_STRIP = YES;
 				COPY_PHASE_STRIP = YES;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_NS_ASSERTIONS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 				GCC_PREPROCESSOR_DEFINITIONS = (
@@ -3949,7 +3945,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				ARCHS = "$(ARCHS_STANDARD)";
 				COPY_PHASE_STRIP = YES;
 				COPY_PHASE_STRIP = YES;
 				ENABLE_NS_ASSERTIONS = NO;
 				ENABLE_NS_ASSERTIONS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 				GCC_PREPROCESSOR_DEFINITIONS = (
@@ -3992,7 +3987,6 @@
 		FA5326C4189719C700F7BBF4 /* Distribution */ = {
 		FA5326C4189719C700F7BBF4 /* Distribution */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
 				CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_MODULES = YES;
@@ -4043,7 +4037,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ENABLE_MODULES = NO;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DYLIB_COMPATIBILITY_VERSION = 9.0;
 				DYLIB_COMPATIBILITY_VERSION = 9.0;
@@ -4076,7 +4069,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ENABLE_MODULES = NO;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				DYLIB_COMPATIBILITY_VERSION = 9.0;
 				DYLIB_COMPATIBILITY_VERSION = 9.0;
 				DYLIB_CURRENT_VERSION = 9.0;
 				DYLIB_CURRENT_VERSION = 9.0;
@@ -4108,7 +4100,6 @@
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				ALWAYS_SEARCH_USER_PATHS = NO;
-				CLANG_ENABLE_MODULES = NO;
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DYLIB_COMPATIBILITY_VERSION = 9.0;
 				DYLIB_COMPATIBILITY_VERSION = 9.0;

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

@@ -8,17 +8,17 @@
 
 
 /* Begin PBXBuildFile section */
 /* Begin PBXBuildFile section */
 		8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
 		8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
-		A9255DD11043183600BA1496 /* FreeType.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A93E6E4810420B4A007D418B /* FreeType.framework */; };
-		A9255DD31043183600BA1496 /* Lua.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A93E6E5310420B57007D418B /* Lua.framework */; };
-		A9255E031043195A00BA1496 /* Vorbis.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9255E021043195A00BA1496 /* Vorbis.framework */; };
-		A9255F58104324E100BA1496 /* Ogg.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9255F51104324D700BA1496 /* Ogg.framework */; };
+		A9255DD11043183600BA1496 /* FreeType.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A93E6E4810420B4A007D418B /* FreeType.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+		A9255DD31043183600BA1496 /* Lua.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A93E6E5310420B57007D418B /* Lua.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+		A9255E031043195A00BA1496 /* Vorbis.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9255E021043195A00BA1496 /* Vorbis.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+		A9255F58104324E100BA1496 /* Ogg.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9255F51104324D700BA1496 /* Ogg.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		A93E6E5510420B57007D418B /* Lua.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A93E6E5310420B57007D418B /* Lua.framework */; };
 		A93E6E5510420B57007D418B /* Lua.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A93E6E5310420B57007D418B /* Lua.framework */; };
 		A93E6EED10420BA8007D418B /* love.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A93E6A3410420AC0007D418B /* love.cpp */; };
 		A93E6EED10420BA8007D418B /* love.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A93E6A3410420AC0007D418B /* love.cpp */; };
 		A9D307F2106635D3004FEDF8 /* physfs.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9D307E9106635C3004FEDF8 /* physfs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		A9D307F2106635D3004FEDF8 /* physfs.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9D307E9106635C3004FEDF8 /* physfs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		A9F169AC109E825000FC83D1 /* mpg123.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F169A6109E824900FC83D1 /* mpg123.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		A9F169AC109E825000FC83D1 /* mpg123.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F169A6109E824900FC83D1 /* mpg123.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F16926109E7BAD00FC83D1 /* libmodplug.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		A9F169AD109E825000FC83D1 /* libmodplug.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = A9F16926109E7BAD00FC83D1 /* libmodplug.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		FA08F69616C766E000F007B5 /* love.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; };
 		FA08F69616C766E000F007B5 /* love.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; };
-		FA08F69716C766E700F007B5 /* love.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; };
+		FA08F69716C766E700F007B5 /* love.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA08F69116C765A200F007B5 /* love.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		FA0B78D21A95814A000E1D17 /* love.icns in Resources */ = {isa = PBXBuildFile; fileRef = FA0B78D01A95814A000E1D17 /* love.icns */; };
 		FA0B78D21A95814A000E1D17 /* love.icns in Resources */ = {isa = PBXBuildFile; fileRef = FA0B78D01A95814A000E1D17 /* love.icns */; };
 		FA0B78D31A95814A000E1D17 /* lovedocument.icns in Resources */ = {isa = PBXBuildFile; fileRef = FA0B78D11A95814A000E1D17 /* lovedocument.icns */; };
 		FA0B78D31A95814A000E1D17 /* lovedocument.icns in Resources */ = {isa = PBXBuildFile; fileRef = FA0B78D11A95814A000E1D17 /* lovedocument.icns */; };
 		FA0B7F301A95AC7D000E1D17 /* love.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A93E6A3410420AC0007D418B /* love.cpp */; };
 		FA0B7F301A95AC7D000E1D17 /* love.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A93E6A3410420AC0007D418B /* love.cpp */; };
@@ -35,7 +35,7 @@
 		FA5D249C1A96CF4300C6FC8F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA5D249A1A96CF4300C6FC8F /* Images.xcassets */; };
 		FA5D249C1A96CF4300C6FC8F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA5D249A1A96CF4300C6FC8F /* Images.xcassets */; };
 		FA5D24C21A96D78000C6FC8F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5D24C11A96D78000C6FC8F /* Foundation.framework */; };
 		FA5D24C21A96D78000C6FC8F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA5D24C11A96D78000C6FC8F /* Foundation.framework */; };
 		FA5D24D11A96E73300C6FC8F /* liblove.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0B7EEF1A95924A000E1D17 /* liblove.a */; };
 		FA5D24D11A96E73300C6FC8F /* liblove.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FA0B7EEF1A95924A000E1D17 /* liblove.a */; };
-		FA77A36518F1172600D23AE9 /* jpeg-turbo.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA77A36418F1172600D23AE9 /* jpeg-turbo.framework */; };
+		FA77A36518F1172600D23AE9 /* jpeg-turbo.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA77A36418F1172600D23AE9 /* jpeg-turbo.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		FA7C636A1A9C49570000FD29 /* Launch Screen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA7C63691A9C49570000FD29 /* Launch Screen.xib */; };
 		FA7C636A1A9C49570000FD29 /* Launch Screen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA7C63691A9C49570000FD29 /* Launch Screen.xib */; };
 		FA9B4A0A16E1579F00074F42 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0916E1579F00074F42 /* SDL2.framework */; };
 		FA9B4A0A16E1579F00074F42 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0916E1579F00074F42 /* SDL2.framework */; };
 		FA9B4A0B16E157B500074F42 /* SDL2.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0916E1579F00074F42 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
 		FA9B4A0B16E157B500074F42 /* SDL2.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0916E1579F00074F42 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
@@ -317,7 +317,7 @@
 		29B97313FDCFA39411CA2CEA /* Project object */ = {
 		29B97313FDCFA39411CA2CEA /* Project object */ = {
 			isa = PBXProject;
 			isa = PBXProject;
 			attributes = {
 			attributes = {
-				LastUpgradeCheck = 0610;
+				LastUpgradeCheck = 0630;
 				TargetAttributes = {
 				TargetAttributes = {
 					FA0B7F051A95AAF3000E1D17 = {
 					FA0B7F051A95AAF3000E1D17 = {
 						CreatedOnToolsVersion = 6.1.1;
 						CreatedOnToolsVersion = 6.1.1;
@@ -421,7 +421,6 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
@@ -445,7 +444,6 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
@@ -510,6 +508,7 @@
 					100000000,
 					100000000,
 				);
 				);
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;
+				SDKROOT = macosx;
 				WARNING_CFLAGS = (
 				WARNING_CFLAGS = (
 					"-Wall",
 					"-Wall",
 					"-W",
 					"-W",
@@ -567,6 +566,7 @@
 				);
 				);
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;
 				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;
 				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;
+				SDKROOT = macosx;
 				WARNING_CFLAGS = (
 				WARNING_CFLAGS = (
 					"-Wall",
 					"-Wall",
 					"-W",
 					"-W",
@@ -748,6 +748,7 @@
 				);
 				);
 				PRODUCT_NAME = love;
 				PRODUCT_NAME = love;
 				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;
 				SCAN_ALL_SOURCE_FILES_FOR_INCLUDES = YES;
+				SDKROOT = macosx;
 				WARNING_CFLAGS = (
 				WARNING_CFLAGS = (
 					"-Wall",
 					"-Wall",
 					"-W",
 					"-W",
@@ -758,7 +759,6 @@
 		FA5326C718971A0900F7BBF4 /* Distribution */ = {
 		FA5326C718971A0900F7BBF4 /* Distribution */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
 			buildSettings = {
 			buildSettings = {
-				ARCHS = "$(ARCHS_STANDARD_64_BIT)";
 				COMBINE_HIDPI_IMAGES = YES;
 				COMBINE_HIDPI_IMAGES = YES;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (

+ 1 - 1
src/common/runtime.h

@@ -524,7 +524,7 @@ int luax_catchexcept(lua_State *L, const T& func, const F& finallyfunc)
 		lua_pushstring(L, e.what());
 		lua_pushstring(L, e.what());
 	}
 	}
 
 
-	finallyfunc();
+	finallyfunc(should_error);
 
 
 	if (should_error)
 	if (should_error)
 		return luaL_error(L, "%s", lua_tostring(L, -1));
 		return luaL_error(L, "%s", lua_tostring(L, -1));

+ 1 - 1
src/modules/filesystem/wrap_Filesystem.cpp

@@ -186,7 +186,7 @@ FileData *luax_getfiledata(lua_State *L, int idx)
 	{
 	{
 		luax_catchexcept(L,
 		luax_catchexcept(L,
 			[&]() { data = file->read(); },
 			[&]() { data = file->read(); },
-			[&]() { file->release(); }
+			[&](bool) { file->release(); }
 		);
 		);
 	}
 	}
 
 

+ 3 - 3
src/modules/font/wrap_Font.cpp

@@ -50,7 +50,7 @@ int w_newRasterizer(lua_State *L)
 
 
 		luax_catchexcept(L,
 		luax_catchexcept(L,
 			[&]() { t = instance()->newRasterizer(d); },
 			[&]() { t = instance()->newRasterizer(d); },
-			[&]() { d->release(); }
+			[&](bool) { d->release(); }
 		);
 		);
 
 
 		luax_pushtype(L, FONT_RASTERIZER_ID, t);
 		luax_pushtype(L, FONT_RASTERIZER_ID, t);
@@ -97,7 +97,7 @@ int w_newTrueTypeRasterizer(lua_State *L)
 
 
 		luax_catchexcept(L,
 		luax_catchexcept(L,
 			[&]() { t = instance()->newTrueTypeRasterizer(d, size, hinting); },
 			[&]() { t = instance()->newTrueTypeRasterizer(d, size, hinting); },
-			[&]() { d->release(); }
+			[&](bool) { d->release(); }
 		);
 		);
 	}
 	}
 
 
@@ -146,7 +146,7 @@ int w_newBMFontRasterizer(lua_State *L)
 
 
 	luax_catchexcept(L,
 	luax_catchexcept(L,
 		[&]() { t = instance()->newBMFontRasterizer(d, images); },
 		[&]() { t = instance()->newBMFontRasterizer(d, images); },
-		[&]() { d->release(); for (auto id : images) id->release(); }
+		[&](bool) { d->release(); for (auto id : images) id->release(); }
 	);
 	);
 
 
 	luax_pushtype(L, FONT_RASTERIZER_ID, t);
 	luax_pushtype(L, FONT_RASTERIZER_ID, t);

+ 6 - 10
src/modules/graphics/opengl/GLBuffer.cpp

@@ -34,14 +34,7 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-// GLBuffer
-
-GLBuffer *GLBuffer::Create(size_t size, GLenum target, GLenum usage)
-{
-	return new GLBuffer(size, target, usage);
-}
-
-GLBuffer::GLBuffer(size_t size, GLenum target, GLenum usage)
+GLBuffer::GLBuffer(size_t size, const void *data, GLenum target, GLenum usage)
 	: is_bound(false)
 	: is_bound(false)
 	, is_mapped(false)
 	, is_mapped(false)
 	, size(size)
 	, size(size)
@@ -59,7 +52,10 @@ GLBuffer::GLBuffer(size_t size, GLenum target, GLenum usage)
 		throw love::Exception("Out of memory.");
 		throw love::Exception("Out of memory.");
 	}
 	}
 
 
-	bool ok = load(false);
+	if (data != nullptr)
+		memcpy(memory_map, data, size);
+
+	bool ok = load(data != nullptr);
 
 
 	if (!ok)
 	if (!ok)
 	{
 	{
@@ -330,7 +326,7 @@ void VertexIndex::resize(size_t size)
 	// VertexIndex will propagate the exception and keep the old GLBuffer.
 	// VertexIndex will propagate the exception and keep the old GLBuffer.
 	try
 	try
 	{
 	{
-		new_element_array = GLBuffer::Create(array_size, GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW);
+		new_element_array = new GLBuffer(array_size, nullptr, GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW);
 	}
 	}
 	catch (std::bad_alloc &)
 	catch (std::bad_alloc &)
 	{
 	{

+ 9 - 20
src/modules/graphics/opengl/GLBuffer.h

@@ -47,17 +47,6 @@ class GLBuffer : public Volatile
 {
 {
 public:
 public:
 
 
-	/**
-	 * Create a new GLBuffer.
-	 *
-	 * @param size The size of the GLBuffer (in bytes).
-	 * @param target GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER.
-	 * @param usage GL_DYNAMIC_DRAW, etc.
-	 * @param backing Determines what guarantees are placed on the data.
-	 * @return A new GLBuffer.
-	 */
-	static GLBuffer *Create(size_t size, GLenum target, GLenum usage);
-
 	/**
 	/**
 	 * Constructor.
 	 * Constructor.
 	 *
 	 *
@@ -66,7 +55,7 @@ public:
 	 * @param usage Usage hint, e.g. GL_DYNAMIC_DRAW.
 	 * @param usage Usage hint, e.g. GL_DYNAMIC_DRAW.
 	 * @param backing Determines what guarantees are placed on the data.
 	 * @param backing Determines what guarantees are placed on the data.
 	 */
 	 */
-	GLBuffer(size_t size, GLenum target, GLenum usage);
+	GLBuffer(size_t size, const void *data, GLenum target, GLenum usage);
 
 
 	/**
 	/**
 	 * Destructor.
 	 * Destructor.
@@ -123,7 +112,7 @@ public:
 	 *
 	 *
 	 * @return A pointer to memory which represents the buffer.
 	 * @return A pointer to memory which represents the buffer.
 	 */
 	 */
-	virtual void *map();
+	void *map();
 
 
 	/**
 	/**
 	 * Unmap a previously mapped GLBuffer. The buffer must be unmapped
 	 * Unmap a previously mapped GLBuffer. The buffer must be unmapped
@@ -135,18 +124,18 @@ public:
 	 *                   sub-range of data modified. Optional.
 	 *                   sub-range of data modified. Optional.
 	 * @param usedSize   The size of the sub-range of modified data. Optional.
 	 * @param usedSize   The size of the sub-range of modified data. Optional.
 	 */
 	 */
-	virtual void unmap(size_t usedOffset = 0, size_t usedSize = -1);
+	void unmap(size_t usedOffset = 0, size_t usedSize = -1);
 
 
 	/**
 	/**
 	 * Bind the GLBuffer to its specified target.
 	 * Bind the GLBuffer to its specified target.
 	 * (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, etc).
 	 * (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, etc).
 	 */
 	 */
-	virtual void bind();
+	void bind();
 
 
 	/**
 	/**
 	 * Unbind a prevously bound GLBuffer.
 	 * Unbind a prevously bound GLBuffer.
 	 */
 	 */
-	virtual void unbind();
+	void unbind();
 
 
 	/**
 	/**
 	 * Fill a portion of the buffer with data.
 	 * Fill a portion of the buffer with data.
@@ -157,7 +146,7 @@ public:
 	 * @param size The size of the incoming data.
 	 * @param size The size of the incoming data.
 	 * @param data Pointer to memory to copy data from.
 	 * @param data Pointer to memory to copy data from.
 	 */
 	 */
-	virtual void fill(size_t offset, size_t size, const void *data);
+	void fill(size_t offset, size_t size, const void *data);
 
 
 	/**
 	/**
 	 * Get a pointer which represents the specified byte offset.
 	 * Get a pointer which represents the specified byte offset.
@@ -165,11 +154,11 @@ public:
 	 * @param offset The byte offset. (0 is first byte).
 	 * @param offset The byte offset. (0 is first byte).
 	 * @return A pointer which represents the offset.
 	 * @return A pointer which represents the offset.
 	 */
 	 */
-	virtual const void *getPointer(size_t offset) const;
+	const void *getPointer(size_t offset) const;
 
 
 	// Implements Volatile.
 	// Implements Volatile.
-	virtual bool loadVolatile();
-	virtual void unloadVolatile();
+	bool loadVolatile() override;
+	void unloadVolatile() override;
 
 
 	/**
 	/**
 	 * This helper class can bind a GLBuffer temporarily, and
 	 * This helper class can bind a GLBuffer temporarily, and

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

@@ -671,7 +671,7 @@ Font *Graphics::newFont(love::font::Rasterizer *r, const Texture::Filter &filter
 	return new Font(r, filter);
 	return new Font(r, filter);
 }
 }
 
 
-SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, int usage)
+SpriteBatch *Graphics::newSpriteBatch(Texture *texture, int size, Mesh::Usage usage)
 {
 {
 	return new SpriteBatch(texture, size, usage);
 	return new SpriteBatch(texture, size, usage);
 }
 }
@@ -747,14 +747,24 @@ Shader *Graphics::newShader(const Shader::ShaderSource &source)
 	return new Shader(source);
 	return new Shader(source);
 }
 }
 
 
-Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode mode)
+Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode drawmode, Mesh::Usage usage)
 {
 {
-	return new Mesh(vertices, mode);
+	return new Mesh(vertices, drawmode, usage);
 }
 }
 
 
-Mesh *Graphics::newMesh(int vertexcount, Mesh::DrawMode mode)
+Mesh *Graphics::newMesh(int vertexcount, Mesh::DrawMode drawmode, Mesh::Usage usage)
 {
 {
-	return new Mesh(vertexcount, mode);
+	return new Mesh(vertexcount, drawmode, usage);
+}
+
+Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, Mesh::DrawMode drawmode, Mesh::Usage usage)
+{
+	return new Mesh(vertexformat, vertexcount, drawmode, usage);
+}
+
+Mesh *Graphics::newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, Mesh::DrawMode drawmode, Mesh::Usage usage)
+{
+	return new Mesh(vertexformat, data, datasize, drawmode, usage);
 }
 }
 
 
 Text *Graphics::newText(Font *font, const std::string &text)
 Text *Graphics::newText(Font *font, const std::string &text)
@@ -762,7 +772,7 @@ Text *Graphics::newText(Font *font, const std::string &text)
 	return new Text(font, text);
 	return new Text(font, text);
 }
 }
 
 
-void Graphics::setColor(const Color &c)
+void Graphics::setColor(Color c)
 {
 {
 	gl.setColor(c);
 	gl.setColor(c);
 	states.back().color = c;
 	states.back().color = c;
@@ -773,7 +783,7 @@ Color Graphics::getColor() const
 	return states.back().color;
 	return states.back().color;
 }
 }
 
 
-void Graphics::setBackgroundColor(const Color &c)
+void Graphics::setBackgroundColor(Color c)
 {
 {
 	states.back().backgroundColor = c;
 	states.back().backgroundColor = c;
 }
 }

+ 8 - 5
src/modules/graphics/opengl/Graphics.h

@@ -167,7 +167,7 @@ public:
 	 **/
 	 **/
 	Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::getDefaultFilter());
 	Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::getDefaultFilter());
 
 
-	SpriteBatch *newSpriteBatch(Texture *texture, int size, int usage);
+	SpriteBatch *newSpriteBatch(Texture *texture, int size, Mesh::Usage usage);
 
 
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 	ParticleSystem *newParticleSystem(Texture *texture, int size);
 
 
@@ -175,8 +175,11 @@ public:
 
 
 	Shader *newShader(const Shader::ShaderSource &source);
 	Shader *newShader(const Shader::ShaderSource &source);
 
 
-	Mesh *newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN);
-	Mesh *newMesh(int vertexcount, Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN);
+	Mesh *newMesh(const std::vector<Vertex> &vertices, Mesh::DrawMode drawmode, Mesh::Usage usage);
+	Mesh *newMesh(int vertexcount, Mesh::DrawMode drawmode, Mesh::Usage usage);
+
+	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, int vertexcount, Mesh::DrawMode drawmode, Mesh::Usage usage);
+	Mesh *newMesh(const std::vector<Mesh::AttribFormat> &vertexformat, const void *data, size_t datasize, Mesh::DrawMode drawmode, Mesh::Usage usage);
 
 
 	Text *newText(Font *font, const std::string &text = "");
 	Text *newText(Font *font, const std::string &text = "");
 
 
@@ -184,7 +187,7 @@ public:
 	 * Sets the foreground color.
 	 * Sets the foreground color.
 	 * @param c The new foreground color.
 	 * @param c The new foreground color.
 	 **/
 	 **/
-	void setColor(const Color &c);
+	void setColor(Color c);
 
 
 	/**
 	/**
 	 * Gets current color.
 	 * Gets current color.
@@ -194,7 +197,7 @@ public:
 	/**
 	/**
 	 * Sets the background Color.
 	 * Sets the background Color.
 	 **/
 	 **/
-	void setBackgroundColor(const Color &c);
+	void setBackgroundColor(Color c);
 
 
 	/**
 	/**
 	 * Gets the current background color.
 	 * Gets the current background color.

+ 490 - 167
src/modules/graphics/opengl/Mesh.cpp

@@ -22,9 +22,11 @@
 #include "Mesh.h"
 #include "Mesh.h"
 #include "common/Matrix.h"
 #include "common/Matrix.h"
 #include "common/Exception.h"
 #include "common/Exception.h"
+#include "Shader.h"
 
 
 // C++
 // C++
 #include <algorithm>
 #include <algorithm>
+#include <limits>
 
 
 namespace love
 namespace love
 {
 {
@@ -33,120 +35,351 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-Mesh::Mesh(const std::vector<Vertex> &verts, Mesh::DrawMode mode)
-	: vbo(nullptr)
-	, vertex_count(0)
+static const char *getBuiltinAttribName(VertexAttribID attribid)
+{
+	const char *name = "";
+	Shader::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, r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");
+
+static std::vector<Mesh::AttribFormat> getDefaultVertexFormat()
+{
+	// Corresponds to the love::Vertex struct.
+	std::vector<Mesh::AttribFormat> vertexformat = {
+		{getBuiltinAttribName(ATTRIB_POS),      Mesh::DATA_FLOAT, 2},
+		{getBuiltinAttribName(ATTRIB_TEXCOORD), Mesh::DATA_FLOAT, 2},
+		{getBuiltinAttribName(ATTRIB_COLOR),    Mesh::DATA_BYTE,  4},
+	};
+
+	return vertexformat;
+}
+
+Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, const void *data, size_t datasize, DrawMode drawmode, Usage usage)
+	: vertexFormat(vertexformat)
+	, vbo(nullptr)
+	, vertexCount(0)
+	, vertexStride(0)
+	, vboUsedOffset(0)
+	, vboUsedSize(0)
 	, ibo(nullptr)
 	, ibo(nullptr)
-	, element_count(0)
-	, element_data_type(getGLDataTypeFromMax(verts.size()))
-	, draw_mode(mode)
-	, range_min(-1)
-	, range_max(-1)
-	, texture(nullptr)
-	, colors_enabled(false)
+	, elementCount(0)
+	, elementDataType(0)
+	, drawMode(drawmode)
+	, rangeMin(-1)
+	, rangeMax(-1)
 {
 {
-	setVertices(verts);
+	setupAttachedAttributes();
+	calculateAttributeSizes();
+
+	vertexCount = datasize / vertexStride;
+	elementDataType = getGLDataTypeFromMax(vertexCount);
+
+	if (vertexCount == 0)
+		throw love::Exception("Data size is too small for specified vertex attribute formats.");
+
+	vbo = new GLBuffer(datasize, data, GL_ARRAY_BUFFER, getGLBufferUsage(usage));
+
+	vertexScratchBuffer = new char[vertexStride];
 }
 }
 
 
-Mesh::Mesh(int vertexcount, Mesh::DrawMode mode)
-	: vbo(nullptr)
-	, vertex_count(0)
+Mesh::Mesh(const std::vector<AttribFormat> &vertexformat, int vertexcount, DrawMode drawmode, Usage usage)
+	: vertexFormat(vertexformat)
+	, vbo(nullptr)
+	, vertexCount((size_t) vertexcount)
+	, vertexStride(0)
+	, vboUsedOffset(0)
+	, vboUsedSize(0)
 	, ibo(nullptr)
 	, ibo(nullptr)
-	, element_count(0)
-	, element_data_type(getGLDataTypeFromMax(vertexcount))
-	, draw_mode(mode)
-	, range_min(-1)
-	, range_max(-1)
-	, texture(nullptr)
-	, colors_enabled(false)
+	, elementCount(0)
+	, elementDataType(getGLDataTypeFromMax(vertexcount))
+	, drawMode(drawmode)
+	, rangeMin(-1)
+	, rangeMax(-1)
 {
 {
-	if (vertexcount < 1)
-		throw love::Exception("Invalid number of vertices.");
+	if (vertexcount <= 0)
+		throw love::Exception("Invalid number of vertices (%d).", vertexcount);
 
 
-	std::vector<Vertex> verts(vertexcount);
+	setupAttachedAttributes();
+	calculateAttributeSizes();
 
 
-	// Default-initialized vertices should have a white opaque color.
-	for (size_t i = 0; i < verts.size(); i++)
-	{
-		verts[i].r = 255;
-		verts[i].g = 255;
-		verts[i].b = 255;
-		verts[i].a = 255;
-	}
+	size_t buffersize = vertexCount * vertexStride;
+
+	vbo = new GLBuffer(buffersize, nullptr, GL_ARRAY_BUFFER, getGLBufferUsage(usage));
 
 
-	setVertices(verts);
+	// Initialize the buffer's contents to 0.
+	GLBuffer::Bind bind(*vbo);
+	memset(vbo->map(), 0, buffersize);
+	vbo->unmap();
+
+	vertexScratchBuffer = new char[vertexStride];
+}
+
+Mesh::Mesh(const std::vector<Vertex> &vertices, DrawMode drawmode, Usage usage)
+	: Mesh(getDefaultVertexFormat(), &vertices[0], vertices.size() * sizeof(Vertex), drawmode, usage)
+{
+}
+
+Mesh::Mesh(int vertexcount, DrawMode drawmode, Usage usage)
+	: Mesh(getDefaultVertexFormat(), vertexcount, drawmode, usage)
+{
 }
 }
 
 
 Mesh::~Mesh()
 Mesh::~Mesh()
 {
 {
 	delete vbo;
 	delete vbo;
 	delete ibo;
 	delete ibo;
+	delete vertexScratchBuffer;
+
+	for (const auto attrib : attachedAttributes)
+	{
+		if (attrib.second.mesh != this)
+			attrib.second.mesh->release();
+	}
 }
 }
 
 
-void Mesh::setVertices(const std::vector<Vertex> &verts)
+void Mesh::setupAttachedAttributes()
 {
 {
-	if (verts.size() == 0)
-		throw love::Exception("At least one vertex is required.");
+	for (size_t i = 0; i < vertexFormat.size(); i++)
+	{
+		const std::string &name = vertexFormat[i].name;
 
 
-	size_t size = sizeof(Vertex) * verts.size();
+		if (attachedAttributes.find(name) != attachedAttributes.end())
+			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
 
-	if (vbo && size > vbo->getSize())
+		attachedAttributes[name] = {this, i, true};
+	}
+}
+
+void Mesh::calculateAttributeSizes()
+{
+	size_t stride = 0;
+
+	for (const AttribFormat &format : vertexFormat)
 	{
 	{
-		delete vbo;
-		vbo = nullptr;
+		// Hardware really doesn't like attributes that aren't 32 bit-aligned.
+		if (format.type == DATA_BYTE && format.components != 4)
+			throw love::Exception("byte vertex attributes must have 4 components.");
+
+		if (format.components <= 0 || format.components > 4)
+			throw love::Exception("Vertex attributes must have between 1 and 4 components.");
+
+		// Total size in bytes of each attribute in a single vertex.
+		attributeSizes.push_back(getAttribFormatSize(format));
+		stride += attributeSizes.back();
 	}
 	}
 
 
-	if (!vbo)
-		vbo = GLBuffer::Create(size, GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW);
+	vertexStride = stride;
+}
 
 
-	vertex_count = verts.size();
+size_t Mesh::getAttributeOffset(size_t attribindex) const
+{
+	size_t offset = 0;
 
 
-	GLBuffer::Bind vbo_bind(*vbo);
-	GLBuffer::Mapper vbo_mapper(*vbo);
+	for (size_t i = 0; i < attribindex; i++)
+		offset += attributeSizes[i];
 
 
-	// Fill the buffer with the vertices.
-	memcpy(vbo_mapper.get(), &verts[0], size);
+	return offset;
 }
 }
 
 
-const Vertex *Mesh::getVertices() const
+void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
 {
 {
-	if (vbo)
+	if (vertindex >= vertexCount)
+		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
+
+	size_t offset = vertindex * vertexStride;
+	size_t size = std::min(datasize, vertexStride);
+
+	GLBuffer::Bind bind(*vbo);
+	uint8 *bufferdata = (uint8 *) vbo->map();
+
+	memcpy(bufferdata + offset, data, size);
+
+	vboUsedOffset = std::min(vboUsedOffset, offset);
+	vboUsedSize = std::max(vboUsedSize, (offset + size) - vboUsedOffset);
+}
+
+size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize)
+{
+	if (vertindex >= vertexCount)
+		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
+
+	size_t offset = vertindex * vertexStride;
+	size_t size = std::min(datasize, vertexStride);
+
+	// We're relying on vbo->map() returning read/write data... ew.
+	GLBuffer::Bind bind(*vbo);
+	const uint8 *bufferdata = (const uint8 *) vbo->map();
+
+	memcpy(data, bufferdata + offset, size);
+
+	if (vboUsedSize == 0)
 	{
 	{
-		GLBuffer::Bind vbo_bind(*vbo);
-		return (Vertex *) vbo->map();
+		vboUsedOffset = std::min(vboUsedOffset, offset);
+		vboUsedSize = std::max(vboUsedSize, (offset + size) - vboUsedOffset);
 	}
 	}
 
 
-	return nullptr;
+	return size;
+}
+
+void *Mesh::getVertexScratchBuffer()
+{
+	return vertexScratchBuffer;
 }
 }
 
 
-void Mesh::setVertex(size_t index, const Vertex &v)
+void Mesh::setVertexAttribute(size_t vertindex, int attribindex, const void *data, size_t datasize)
 {
 {
-	if (index >= vertex_count)
-		throw love::Exception("Invalid vertex index: %ld", index + 1);
+	if (vertindex >= vertexCount)
+		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
+
+	if (attribindex >= (int) vertexFormat.size())
+		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
 
 
-	GLBuffer::Bind vbo_bind(*vbo);
+	size_t offset = vertindex * vertexStride + getAttributeOffset(attribindex);
+	size_t size = std::min(datasize, attributeSizes[attribindex]);
 
 
-	// We unmap the vertex buffer in Mesh::draw. This lets us coalesce the
-	// buffer transfer calls into just one.
-	Vertex *vertices = (Vertex *) vbo->map();
-	vertices[index] = v;
+	GLBuffer::Bind bind(*vbo);
+	uint8 *bufferdata = (uint8 *) vbo->map();
+
+	memcpy(bufferdata + offset, data, size);
+
+	vboUsedOffset = std::min(vboUsedOffset, offset);
+	vboUsedSize = std::max(vboUsedSize, (offset + size) - vboUsedOffset);
 }
 }
 
 
-Vertex Mesh::getVertex(size_t index) const
+size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize)
 {
 {
-	if (index >= vertex_count)
-		throw love::Exception("Invalid vertex index: %ld", index + 1);
+	if (vertindex >= vertexCount)
+		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
+
+	if (attribindex >= (int) vertexFormat.size())
+		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
+
+	size_t offset = vertindex * vertexStride + getAttributeOffset(attribindex);
+	size_t size = std::min(datasize, attributeSizes[attribindex]);
+
+	// We're relying on vbo->map() returning read/write data... ew.
+	GLBuffer::Bind bind(*vbo);
+	const uint8 *bufferdata = (const uint8 *) vbo->map();
 
 
-	GLBuffer::Bind vbo_bind(*vbo);
+	memcpy(data, bufferdata + offset, size);
 
 
-	// We unmap the vertex buffer in Mesh::draw.
-	Vertex *vertices = (Vertex *) vbo->map();
-	return vertices[index];
+	if (vboUsedSize == 0)
+	{
+		vboUsedOffset = std::min(vboUsedOffset, offset);
+		vboUsedSize = std::max(vboUsedSize, (offset + size) - vboUsedOffset);
+	}
+
+	return size;
 }
 }
 
 
 size_t Mesh::getVertexCount() const
 size_t Mesh::getVertexCount() const
 {
 {
-	return vertex_count;
+	return vertexCount;
+}
+
+size_t Mesh::getVertexStride() const
+{
+	return vertexStride;
+}
+
+const std::vector<Mesh::AttribFormat> &Mesh::getVertexFormat() const
+{
+	return vertexFormat;
+}
+
+Mesh::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);
+
+	DataType type = vertexFormat[attribindex].type;
+	components = vertexFormat[attribindex].components;
+	return type;
+}
+
+void Mesh::setAttributeEnabled(const std::string &name, bool enable)
+{
+	auto it = attachedAttributes.find(name);
+
+	if (it == attachedAttributes.end())
+		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
+
+	it->second.enabled = enable;
+}
+
+bool Mesh::isAttributeEnabled(const std::string &name) const
+{
+	const auto it = attachedAttributes.find(name);
+
+	if (it == attachedAttributes.end())
+		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
+
+	return it->second.enabled;
+}
+
+void Mesh::attachAttribute(const std::string &name, Mesh *mesh)
+{
+	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 = {};
+
+	auto it = attachedAttributes.find(name);
+	if (it != attachedAttributes.end())
+		oldattrib = it->second;
+
+	newattrib.mesh = mesh;
+	newattrib.index = std::numeric_limits<size_t>::max();
+	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
+
+	// Find the index of the attribute in the mesh.
+	for (size_t i = 0; i < mesh->vertexFormat.size(); i++)
+	{
+		if (mesh->vertexFormat[i].name == name)
+		{
+			newattrib.index = i;
+			break;
+		}
+	}
+
+	if (newattrib.index == std::numeric_limits<size_t>::max())
+		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
+
+	if (newattrib.mesh != this)
+		newattrib.mesh->retain();
+
+	attachedAttributes[name] = newattrib;
+
+	if (oldattrib.mesh && oldattrib.mesh != this)
+		oldattrib.mesh->release();
+}
+
+void Mesh::flush()
+{
+	{
+		GLBuffer::Bind vbobind(*vbo);
+		vbo->unmap(vboUsedOffset, vboUsedSize);
+		vboUsedOffset = vboUsedSize = 0;
+	}
+
+	if (ibo != nullptr)
+	{
+		GLBuffer::Bind ibobind(*ibo);
+		ibo->unmap();
+	}
 }
 }
 
 
 /**
 /**
@@ -168,7 +401,9 @@ static void copyToIndexBuffer(const std::vector<uint32> &indices, GLBuffer::Mapp
 
 
 void Mesh::setVertexMap(const std::vector<uint32> &map)
 void Mesh::setVertexMap(const std::vector<uint32> &map)
 {
 {
-	GLenum datatype = getGLDataTypeFromMax(vertex_count);
+	size_t maxval = getVertexCount();
+
+	GLenum datatype = getGLDataTypeFromMax(maxval);
 
 
 	// Calculate the size in bytes of the index buffer data.
 	// Calculate the size in bytes of the index buffer data.
 	size_t size = map.size() * getGLDataTypeSize(datatype);
 	size_t size = map.size() * getGLDataTypeSize(datatype);
@@ -180,32 +415,29 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	}
 	}
 
 
 	if (!ibo && size > 0)
 	if (!ibo && size > 0)
-		ibo = GLBuffer::Create(size, GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW);
+		ibo = new GLBuffer(size, nullptr, GL_ELEMENT_ARRAY_BUFFER, vbo->getUsage());
 
 
-	element_count = map.size();
+	elementCount = map.size();
 
 
-	if (!ibo || element_count == 0)
+	if (!ibo || elementCount == 0)
 		return;
 		return;
 
 
-	GLBuffer::Bind ibo_bind(*ibo);
-	GLBuffer::Mapper ibo_map(*ibo);
+	GLBuffer::Bind ibobind(*ibo);
+	GLBuffer::Mapper ibomap(*ibo);
 
 
 	// Fill the buffer with the index values from the vector.
 	// Fill the buffer with the index values from the vector.
 	switch (datatype)
 	switch (datatype)
 	{
 	{
-	case GL_UNSIGNED_BYTE:
-		copyToIndexBuffer<uint8>(map, ibo_map, vertex_count);
-		break;
 	case GL_UNSIGNED_SHORT:
 	case GL_UNSIGNED_SHORT:
-		copyToIndexBuffer<uint16>(map, ibo_map, vertex_count);
+		copyToIndexBuffer<uint16>(map, ibomap, maxval);
 		break;
 		break;
 	case GL_UNSIGNED_INT:
 	case GL_UNSIGNED_INT:
 	default:
 	default:
-		copyToIndexBuffer<uint32>(map, ibo_map, vertex_count);
+		copyToIndexBuffer<uint32>(map, ibomap, maxval);
 		break;
 		break;
 	}
 	}
 
 
-	element_data_type = datatype;
+	elementDataType = datatype;
 }
 }
 
 
 /**
 /**
@@ -221,36 +453,33 @@ static void copyFromIndexBuffer(void *buffer, size_t count, std::vector<uint32>
 
 
 void Mesh::getVertexMap(std::vector<uint32> &map) const
 void Mesh::getVertexMap(std::vector<uint32> &map) const
 {
 {
-	if (!ibo || element_count == 0)
+	if (!ibo || elementCount == 0)
 		return;
 		return;
 
 
 	map.clear();
 	map.clear();
-	map.reserve(element_count);
+	map.reserve(elementCount);
 
 
-	GLBuffer::Bind ibo_bind(*ibo);
+	GLBuffer::Bind ibobind(*ibo);
 
 
-	// We unmap the buffer in Mesh::draw and Mesh::setVertexMap.
+	// We unmap the buffer in Mesh::draw, Mesh::setVertexMap, and Mesh::flush.
 	void *buffer = ibo->map();
 	void *buffer = ibo->map();
 
 
 	// Fill the vector from the buffer.
 	// Fill the vector from the buffer.
-	switch (element_data_type)
+	switch (elementDataType)
 	{
 	{
-	case GL_UNSIGNED_BYTE:
-		copyFromIndexBuffer<uint8>(buffer, element_count, map);
-		break;
 	case GL_UNSIGNED_SHORT:
 	case GL_UNSIGNED_SHORT:
-		copyFromIndexBuffer<uint16>(buffer, element_count, map);
+		copyFromIndexBuffer<uint16>(buffer, elementCount, map);
 		break;
 		break;
 	case GL_UNSIGNED_INT:
 	case GL_UNSIGNED_INT:
 	default:
 	default:
-		copyFromIndexBuffer<uint32>(buffer, element_count, map);
+		copyFromIndexBuffer<uint32>(buffer, elementCount, map);
 		break;
 		break;
 	}
 	}
 }
 }
 
 
 size_t Mesh::getVertexMapCount() const
 size_t Mesh::getVertexMapCount() const
 {
 {
-	return element_count;
+	return elementCount;
 }
 }
 
 
 void Mesh::setTexture(Texture *tex)
 void Mesh::setTexture(Texture *tex)
@@ -268,14 +497,14 @@ Texture *Mesh::getTexture() const
 	return texture.get();
 	return texture.get();
 }
 }
 
 
-void Mesh::setDrawMode(Mesh::DrawMode mode)
+void Mesh::setDrawMode(DrawMode mode)
 {
 {
-	draw_mode = mode;
+	drawMode = mode;
 }
 }
 
 
 Mesh::DrawMode Mesh::getDrawMode() const
 Mesh::DrawMode Mesh::getDrawMode() const
 {
 {
-	return draw_mode;
+	return drawMode;
 }
 }
 
 
 void Mesh::setDrawRange(int min, int max)
 void Mesh::setDrawRange(int min, int max)
@@ -283,76 +512,95 @@ void Mesh::setDrawRange(int min, int max)
 	if (min < 0 || max < 0 || min > max)
 	if (min < 0 || max < 0 || min > max)
 		throw love::Exception("Invalid draw range.");
 		throw love::Exception("Invalid draw range.");
 
 
-	range_min = min;
-	range_max = max;
+	rangeMin = min;
+	rangeMax = max;
 }
 }
 
 
 void Mesh::setDrawRange()
 void Mesh::setDrawRange()
 {
 {
-	range_min = range_max = -1;
+	rangeMin = rangeMax = -1;
 }
 }
 
 
 void Mesh::getDrawRange(int &min, int &max) const
 void Mesh::getDrawRange(int &min, int &max) const
 {
 {
-	min = range_min;
-	max = range_max;
+	min = rangeMin;
+	max = rangeMax;
 }
 }
 
 
-void Mesh::setVertexColors(bool enable)
+void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
-	colors_enabled = enable;
-}
+	OpenGL::TempDebugGroup debuggroup("Mesh draw");
 
 
-bool Mesh::hasVertexColors() const
-{
-	return colors_enabled;
-}
+	std::vector<GLint> attriblocations;
+	attriblocations.reserve(attachedAttributes.size());
 
 
-void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
-{
-	const size_t pos_offset   = offsetof(Vertex, x);
-	const size_t tex_offset   = offsetof(Vertex, s);
-	const size_t color_offset = offsetof(Vertex, r);
+	bool hasposattrib = false;
 
 
-	if (vertex_count == 0)
-		return;
+	for (const auto &attrib : attachedAttributes)
+	{
+		if (!attrib.second.enabled)
+			continue;
 
 
-	OpenGL::TempDebugGroup debuggroup("Mesh draw");
+		Mesh *mesh = attrib.second.mesh;
+		const AttribFormat &format = mesh->vertexFormat[attrib.second.index];
 
 
-	if (texture.get())
-		gl.bindTexture(*(GLuint *) texture->getHandle());
-	else
-		gl.bindTexture(gl.getDefaultTexture());
+		GLint attriblocation = -1;
 
 
-	Matrix m;
-	m.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
+		// If the attribute is one of the LOVE-defined ones, use the constant
+		// attribute index for it, otherwise query the index from the shader.
+		VertexAttribID builtinattrib;
+		if (Shader::getConstant(format.name.c_str(), builtinattrib))
+			attriblocation = (GLint) builtinattrib;
+		else if (Shader::current)
+			attriblocation = Shader::current->getAttribLocation(format.name);
 
 
-	OpenGL::TempTransform transform(gl);
-	transform.get() *= m;
+		// The active shader might not use this vertex attribute name.
+		if (attriblocation < 0)
+			continue;
 
 
-	GLBuffer::Bind vbo_bind(*vbo);
+		// Needed for unmap and glVertexAttribPointer.
+		GLBuffer::Bind vbobind(*mesh->vbo);
 
 
-	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
-	vbo->unmap();
+		// Make sure the buffer isn't mapped (sends data to GPU if needed.)
+		mesh->vbo->unmap(mesh->vboUsedOffset, mesh->vboUsedSize);
+		mesh->vboUsedOffset = mesh->vboUsedSize = 0;
 
 
-	glEnableVertexAttribArray(ATTRIB_POS);
-	glEnableVertexAttribArray(ATTRIB_TEXCOORD);
+		size_t offset = mesh->getAttributeOffset(attrib.second.index);
+		const void *gloffset = mesh->vbo->getPointer(offset);
+		GLenum datatype = getGLDataType(format.type);
+		GLboolean normalized = (datatype == GL_UNSIGNED_BYTE);
 
 
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), vbo->getPointer(pos_offset));
-	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), vbo->getPointer(tex_offset));
+		glEnableVertexAttribArray(attriblocation);
+		glVertexAttribPointer(attriblocation, format.components, datatype, normalized, mesh->vertexStride, gloffset);
+
+		attriblocations.push_back(attriblocation);
+
+		if (attriblocation == ATTRIB_POS)
+			hasposattrib = true;
+	}
 
 
-	if (hasVertexColors())
+	if (!hasposattrib)
 	{
 	{
-		// Per-vertex colors.
-		glEnableVertexAttribArray(ATTRIB_COLOR);
-		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), vbo->getPointer(color_offset));
+		for (GLint attrib : attriblocations)
+			glDisableVertexAttribArray(attrib);
+
+		// Not supported on all platforms or GL versions at least, I believe.
+		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
 	}
 	}
 
 
-	GLenum mode = getGLDrawMode(draw_mode);
+	if (texture.get())
+		gl.bindTexture(*(GLuint *) texture->getHandle());
+	else
+		gl.bindTexture(gl.getDefaultTexture());
+
+	Matrix m(x, y, angle, sx, sy, ox, oy, kx, ky);
+
+	OpenGL::TempTransform transform(gl);
+	transform.get() *= m;
 
 
 	gl.prepareDraw();
 	gl.prepareDraw();
 
 
-	if (ibo && element_count > 0)
+	if (ibo && elementCount > 0)
 	{
 	{
 		// Use the custom vertex map (index buffer) to draw the vertices.
 		// Use the custom vertex map (index buffer) to draw the vertices.
 		GLBuffer::Bind ibo_bind(*ibo);
 		GLBuffer::Bind ibo_bind(*ibo);
@@ -360,61 +608,84 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
 		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
 		ibo->unmap();
 		ibo->unmap();
 
 
-		int max = (int) element_count - 1;
-		if (range_max >= 0)
-			max = std::min(range_max, max);
+		int max = (int) elementCount - 1;
+		if (rangeMax >= 0)
+			max = std::min(rangeMax, max);
 
 
 		int min = 0;
 		int min = 0;
-		if (range_min >= 0)
-			min = std::min(range_min, max);
+		if (rangeMin >= 0)
+			min = std::min(rangeMin, max);
 
 
-		GLenum type = element_data_type;
+		GLenum type = elementDataType;
 		const void *indices = ibo->getPointer(min * getGLDataTypeSize(type));
 		const void *indices = ibo->getPointer(min * getGLDataTypeSize(type));
 
 
-		gl.drawElements(mode, max - min + 1, type, indices);
+		gl.drawElements(getGLDrawMode(drawMode), max - min + 1, type, indices);
 	}
 	}
 	else
 	else
 	{
 	{
-		int max = (int) vertex_count - 1;
-		if (range_max >= 0)
-			max = std::min(range_max, max);
+		int max = (int) vertexCount - 1;
+		if (rangeMax >= 0)
+			max = std::min(rangeMax, max);
 
 
 		int min = 0;
 		int min = 0;
-		if (range_min >= 0)
-			min = std::min(range_min, max);
+		if (rangeMin >= 0)
+			min = std::min(rangeMin, max);
 
 
 		// Normal non-indexed drawing (no custom vertex map.)
 		// Normal non-indexed drawing (no custom vertex map.)
-		gl.drawArrays(mode, min, max - min + 1);
+		gl.drawArrays(getGLDrawMode(drawMode), min, max - min + 1);
 	}
 	}
 
 
-	glDisableVertexAttribArray(ATTRIB_TEXCOORD);
-	glDisableVertexAttribArray(ATTRIB_POS);
+	for (GLint attrib : attriblocations)
+	{
+		glDisableVertexAttribArray(attrib);
+		if (attrib == ATTRIB_COLOR)
+			gl.setColor(gl.getColor());
+	}
+}
 
 
-	if (hasVertexColors())
+size_t Mesh::getAttribFormatSize(const AttribFormat &format)
+{
+	switch (format.type)
 	{
 	{
-		glDisableVertexAttribArray(ATTRIB_COLOR);
-		// Using the color array leaves the GL constant color undefined.
-		gl.setColor(gl.getColor());
+	case DATA_BYTE:
+		return format.components * sizeof(uint8);
+	case DATA_FLOAT:
+		return format.components * sizeof(float);
+	default:
+		return 0;
 	}
 	}
 }
 }
 
 
-GLenum Mesh::getGLDrawMode(DrawMode mode) const
+GLenum Mesh::getGLDrawMode(DrawMode mode)
 {
 {
 	switch (mode)
 	switch (mode)
 	{
 	{
-	case DRAW_MODE_FAN:
+	case DRAWMODE_FAN:
 		return GL_TRIANGLE_FAN;
 		return GL_TRIANGLE_FAN;
-	case DRAW_MODE_STRIP:
+	case DRAWMODE_STRIP:
 		return GL_TRIANGLE_STRIP;
 		return GL_TRIANGLE_STRIP;
-	case DRAW_MODE_TRIANGLES:
+	case DRAWMODE_TRIANGLES:
 	default:
 	default:
 		return GL_TRIANGLES;
 		return GL_TRIANGLES;
-	case DRAW_MODE_POINTS:
+	case DRAWMODE_POINTS:
 		return GL_POINTS;
 		return GL_POINTS;
 	}
 	}
 }
 }
 
 
-GLenum Mesh::getGLDataTypeFromMax(size_t maxvalue) const
+GLenum Mesh::getGLDataType(DataType type)
+{
+	switch (type)
+	{
+	case DATA_BYTE:
+		return GL_UNSIGNED_BYTE;
+	case DATA_FLOAT:
+		return GL_FLOAT;
+	default:
+		return 0;
+	}
+}
+
+GLenum Mesh::getGLDataTypeFromMax(size_t maxvalue)
 {
 {
 	if (maxvalue > LOVE_UINT16_MAX)
 	if (maxvalue > LOVE_UINT16_MAX)
 		return GL_UNSIGNED_INT;
 		return GL_UNSIGNED_INT;
@@ -422,7 +693,7 @@ GLenum Mesh::getGLDataTypeFromMax(size_t maxvalue) const
 		return GL_UNSIGNED_SHORT;
 		return GL_UNSIGNED_SHORT;
 }
 }
 
 
-size_t Mesh::getGLDataTypeSize(GLenum datatype) const
+size_t Mesh::getGLDataTypeSize(GLenum datatype)
 {
 {
 	switch (datatype)
 	switch (datatype)
 	{
 	{
@@ -437,6 +708,31 @@ size_t Mesh::getGLDataTypeSize(GLenum datatype) const
 	}
 	}
 }
 }
 
 
+GLenum Mesh::getGLBufferUsage(Usage usage)
+{
+	switch (usage)
+	{
+	case USAGE_STREAM:
+		return GL_STREAM_DRAW;
+	case USAGE_DYNAMIC:
+		return GL_DYNAMIC_DRAW;
+	case USAGE_STATIC:
+		return GL_STATIC_DRAW;
+	default:
+		return 0;
+	}
+}
+
+bool Mesh::getConstant(const char *in, Usage &out)
+{
+	return usages.find(in, out);
+}
+
+bool Mesh::getConstant(Usage in, const char *&out)
+{
+	return usages.find(in, out);
+}
+
 bool Mesh::getConstant(const char *in, Mesh::DrawMode &out)
 bool Mesh::getConstant(const char *in, Mesh::DrawMode &out)
 {
 {
 	return drawModes.find(in, out);
 	return drawModes.find(in, out);
@@ -447,15 +743,42 @@ bool Mesh::getConstant(Mesh::DrawMode in, const char *&out)
 	return drawModes.find(in, out);
 	return drawModes.find(in, out);
 }
 }
 
 
-StringMap<Mesh::DrawMode, Mesh::DRAW_MODE_MAX_ENUM>::Entry Mesh::drawModeEntries[] =
+bool Mesh::getConstant(const char *in, DataType &out)
+{
+	return dataTypes.find(in, out);
+}
+
+bool Mesh::getConstant(DataType in, const char *&out)
+{
+	return dataTypes.find(in, out);
+}
+
+StringMap<Mesh::Usage, Mesh::USAGE_MAX_ENUM>::Entry Mesh::usageEntries[] =
+{
+	{"stream", USAGE_STREAM},
+	{"dynamic", USAGE_DYNAMIC},
+	{"static", USAGE_STATIC},
+};
+
+StringMap<Mesh::Usage, Mesh::USAGE_MAX_ENUM> Mesh::usages(Mesh::usageEntries, sizeof(Mesh::usageEntries));
+
+StringMap<Mesh::DrawMode, Mesh::DRAWMODE_MAX_ENUM>::Entry Mesh::drawModeEntries[] =
+{
+	{"fan", DRAWMODE_FAN},
+	{"strip", DRAWMODE_STRIP},
+	{"triangles", DRAWMODE_TRIANGLES},
+	{"points", DRAWMODE_POINTS},
+};
+
+StringMap<Mesh::DrawMode, Mesh::DRAWMODE_MAX_ENUM> Mesh::drawModes(Mesh::drawModeEntries, sizeof(Mesh::drawModeEntries));
+
+StringMap<Mesh::DataType, Mesh::DATA_MAX_ENUM>::Entry Mesh::dataTypeEntries[] =
 {
 {
-	{"fan", Mesh::DRAW_MODE_FAN},
-	{"strip", Mesh::DRAW_MODE_STRIP},
-	{"triangles", Mesh::DRAW_MODE_TRIANGLES},
-	{"points", Mesh::DRAW_MODE_POINTS},
+	{"byte", DATA_BYTE},
+	{"float", DATA_FLOAT},
 };
 };
 
 
-StringMap<Mesh::DrawMode, Mesh::DRAW_MODE_MAX_ENUM> Mesh::drawModes(Mesh::drawModeEntries, sizeof(Mesh::drawModeEntries));
+StringMap<Mesh::DataType, Mesh::DATA_MAX_ENUM> Mesh::dataTypes(Mesh::dataTypeEntries, sizeof(Mesh::dataTypeEntries));
 
 
 } // opengl
 } // opengl
 } // graphics
 } // graphics

+ 127 - 50
src/modules/graphics/opengl/Mesh.h

@@ -32,6 +32,7 @@
 
 
 // C++
 // C++
 #include <vector>
 #include <vector>
+#include <unordered_map>
 
 
 namespace love
 namespace love
 {
 {
@@ -42,63 +43,106 @@ namespace opengl
 
 
 /**
 /**
  * Holds and draws arbitrary vertex geometry.
  * Holds and draws arbitrary vertex geometry.
- * Each vertex in the Mesh has a position, texture coordinate, and color.
+ * Each vertex in the Mesh has a collection of vertex attributes specified on
+ * creation.
  **/
  **/
 class Mesh : public Drawable
 class Mesh : public Drawable
 {
 {
 public:
 public:
 
 
+	// The expected usage pattern of the Mesh's vertex data.
+	enum Usage
+	{
+		USAGE_STREAM,
+		USAGE_DYNAMIC,
+		USAGE_STATIC,
+		USAGE_MAX_ENUM
+	};
+
 	// How the Mesh's vertices are used when drawing.
 	// How the Mesh's vertices are used when drawing.
 	// http://escience.anu.edu.au/lecture/cg/surfaceModeling/image/surfaceModeling015.png
 	// http://escience.anu.edu.au/lecture/cg/surfaceModeling/image/surfaceModeling015.png
 	enum DrawMode
 	enum DrawMode
 	{
 	{
-		DRAW_MODE_FAN,
-		DRAW_MODE_STRIP,
-		DRAW_MODE_TRIANGLES,
-		DRAW_MODE_POINTS,
-		DRAW_MODE_MAX_ENUM
+		DRAWMODE_FAN,
+		DRAWMODE_STRIP,
+		DRAWMODE_TRIANGLES,
+		DRAWMODE_POINTS,
+		DRAWMODE_MAX_ENUM
+	};
+
+	// The type of data a vertex attribute can store.
+	enum DataType
+	{
+		DATA_BYTE,
+		DATA_FLOAT,
+		DATA_MAX_ENUM
 	};
 	};
 
 
+	struct AttribFormat
+	{
+		std::string name;
+		DataType type;
+		int components; // max 4
+	};
+
+	Mesh(const std::vector<AttribFormat> &vertexformat, const void *data, size_t datasize, DrawMode drawmode, Usage usage);
+	Mesh(const std::vector<AttribFormat> &vertexformat, int vertexcount, DrawMode drawmode, Usage usage);
+
+	Mesh(const std::vector<Vertex> &vertices, DrawMode drawmode, Usage usage);
+	Mesh(int vertexcount, DrawMode drawmode, Usage usage);
+
+	virtual ~Mesh();
+
 	/**
 	/**
-	 * Constructor.
-	 * @param verts The vertices to use in the Mesh.
-	 * @param mode The draw mode to use when drawing the Mesh.
+	 * Sets the values of all attributes at a specific vertex index in the Mesh.
+	 * The size of the data must be less than or equal to the total size of all
+	 * vertex attributes.
 	 **/
 	 **/
-	Mesh(const std::vector<Vertex> &verts, DrawMode mode = DRAW_MODE_FAN);
+	void setVertex(size_t vertindex, const void *data, size_t datasize);
+	size_t getVertex(size_t vertindex, void *data, size_t datasize);
+	void *getVertexScratchBuffer();
 
 
 	/**
 	/**
-	 * Constructor.
-	 * Creates a Mesh with a certain number of default-initialized (hidden)
-	 * vertices.
-	 * @param vertexcount The number of vertices to use in the Mesh.
-	 * @param mode The draw mode to use when drawing the Mesh.
+	 * Sets the values for a single attribute at a specific vertex index in the
+	 * Mesh. The size of the data must be less than or equal to the size of the
+	 * attribute.
 	 **/
 	 **/
-	Mesh(int vertexcount, DrawMode mode = DRAW_MODE_FAN);
+	void setVertexAttribute(size_t vertindex, int attribindex, const void *data, size_t datasize);
+	size_t getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize);
 
 
-	virtual ~Mesh();
+	/**
+	 * Gets the total number of vertices that can be used when drawing the Mesh.
+	 **/
+	size_t getVertexCount() const;
 
 
 	/**
 	/**
-	 * Replaces all the vertices in the Mesh with a new set of vertices.
+	 * Gets the size in bytes of the start of one vertex to the start of the
+	 * next, in the buffer.
 	 **/
 	 **/
-	void setVertices(const std::vector<Vertex> &verts);
+	size_t getVertexStride() const;
 
 
 	/**
 	/**
-	 * Gets all of the vertices in the Mesh as an array.
+	 * Gets the format of each vertex attribute stored in the Mesh.
 	 **/
 	 **/
-	const Vertex *getVertices() const;
+	const std::vector<AttribFormat> &getVertexFormat() const;
+	DataType getAttributeInfo(int attribindex, int &components) const;
 
 
 	/**
 	/**
-	 * Sets an individual vertex in the Mesh.
-	 * @param index The index into the list of vertices to use.
-	 * @param v The new vertex.
+	 * Sets whether a specific vertex attribute is used when drawing the Mesh.
 	 **/
 	 **/
-	void setVertex(size_t index, const Vertex &v);
-	Vertex getVertex(size_t index) const;
+	void setAttributeEnabled(const std::string &name, bool enable);
+	bool isAttributeEnabled(const std::string &name) const;
 
 
 	/**
 	/**
-	 * Gets the total number of vertices in the Mesh.
+	 * Attaches a vertex attribute from another Mesh to this one. The attribute
+	 * will be used when drawing this Mesh.
 	 **/
 	 **/
-	size_t getVertexCount() const;
+	void attachAttribute(const std::string &name, Mesh *mesh);
+
+	/**
+	 * Flushes all modified data to the GPU.
+	 **/
+	void flush();
 
 
 	/**
 	/**
 	 * Sets the vertex map to use when drawing the Mesh. The vertex map
 	 * Sets the vertex map to use when drawing the Mesh. The vertex map
@@ -145,46 +189,79 @@ public:
 	void setDrawRange();
 	void setDrawRange();
 	void getDrawRange(int &min, int &max) const;
 	void getDrawRange(int &min, int &max) const;
 
 
-	/**
-	 * Sets whether per-vertex colors are enabled. If this is disabled, the
-	 * global color (love.graphics.setColor) will be used for the entire Mesh.
-	 **/
-	void setVertexColors(bool enable);
-	bool hasVertexColors() const;
-
 	// Implements Drawable.
 	// Implements Drawable.
-	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
+	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) override;
+
+	static GLenum getGLBufferUsage(Usage usage);
+
+	static bool getConstant(const char *in, Usage &out);
+	static bool getConstant(Usage in, const char *&out);
 
 
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(const char *in, DrawMode &out);
 	static bool getConstant(DrawMode in, const char *&out);
 	static bool getConstant(DrawMode in, const char *&out);
 
 
+	static bool getConstant(const char *in, DataType &out);
+	static bool getConstant(DataType in, const char *&out);
+
 private:
 private:
 
 
-	GLenum getGLDrawMode(DrawMode mode) const;
-	GLenum getGLDataTypeFromMax(size_t maxvalue) const;
-	size_t getGLDataTypeSize(GLenum datatype) const;
+	struct AttachedAttribute
+	{
+		Mesh *mesh;
+		size_t index;
+		bool enabled;
+	};
+
+	void setupAttachedAttributes();
+	void calculateAttributeSizes();
+	size_t getAttributeOffset(size_t attribindex) const;
+
+	static size_t getAttribFormatSize(const AttribFormat &format);
+
+	static GLenum getGLDrawMode(DrawMode mode);
+	static GLenum getGLDataType(DataType type);
+	static GLenum getGLDataTypeFromMax(size_t maxvalue);
+	static size_t getGLDataTypeSize(GLenum datatype);
+
+	std::vector<AttribFormat> vertexFormat;
+	std::vector<size_t> attributeSizes;
 
 
-	// Vertex buffer.
+	std::unordered_map<std::string, AttachedAttribute> attachedAttributes;
+
+	// Vertex buffer, for the vertex data.
 	GLBuffer *vbo;
 	GLBuffer *vbo;
-	size_t vertex_count;
+	size_t vertexCount;
+	size_t vertexStride;
+
+	// Block of memory whose size is at least as large as a single vertex. Helps
+	// avoid memory allocations when using Mesh::setVertex etc.
+	char *vertexScratchBuffer;
+
+	// Tracks the range of the vertex buffer that has been used, to make unmap()
+	// calls as efficient as possible.
+	size_t vboUsedOffset;
+	size_t vboUsedSize;
 
 
 	// Element (vertex index) buffer, for the vertex map.
 	// Element (vertex index) buffer, for the vertex map.
 	GLBuffer *ibo;
 	GLBuffer *ibo;
-	size_t element_count;
-	GLenum element_data_type;
+	size_t elementCount;
+	GLenum elementDataType;
 
 
-	DrawMode draw_mode;
+	DrawMode drawMode;
 
 
-	int range_min;
-	int range_max;
+	int rangeMin;
+	int rangeMax;
 
 
 	StrongRef<Texture> texture;
 	StrongRef<Texture> texture;
 
 
-	// Whether the per-vertex colors are used when drawing.
-	bool colors_enabled;
+	static StringMap<Usage, USAGE_MAX_ENUM>::Entry usageEntries[];
+	static StringMap<Usage, USAGE_MAX_ENUM> usages;
+
+	static StringMap<DrawMode, DRAWMODE_MAX_ENUM>::Entry drawModeEntries[];
+	static StringMap<DrawMode, DRAWMODE_MAX_ENUM> drawModes;
 
 
-	static StringMap<DrawMode, DRAW_MODE_MAX_ENUM>::Entry drawModeEntries[];
-	static StringMap<DrawMode, DRAW_MODE_MAX_ENUM> drawModes;
+	static StringMap<DataType, DATA_MAX_ENUM>::Entry dataTypeEntries[];
+	static StringMap<DataType, DATA_MAX_ENUM> dataTypes;
 
 
 }; // Mesh
 }; // Mesh
 
 

+ 24 - 0
src/modules/graphics/opengl/Shader.cpp

@@ -327,6 +327,8 @@ void Shader::unloadVolatile()
 	activeTexUnits.clear();
 	activeTexUnits.clear();
 	activeTexUnits.resize(gl.getMaxTextureUnits() - 1, 0);
 	activeTexUnits.resize(gl.getMaxTextureUnits() - 1, 0);
 
 
+	attributes.clear();
+
 	// same with uniform location list
 	// same with uniform location list
 	uniforms.clear();
 	uniforms.clear();
 
 
@@ -610,6 +612,18 @@ Shader::UniformType Shader::getExternVariable(const std::string &name, int &comp
 	return it->second.baseType;
 	return it->second.baseType;
 }
 }
 
 
+GLint Shader::getAttribLocation(const std::string &name)
+{
+	auto it = attributes.find(name);
+	if (it != attributes.end())
+		return it->second;
+
+	GLint location = glGetAttribLocation(program, name.c_str());
+
+	attributes[name] = location;
+	return location;
+}
+
 bool Shader::hasVertexAttrib(VertexAttribID attrib) const
 bool Shader::hasVertexAttrib(VertexAttribID attrib) const
 {
 {
 	return builtinAttributes[int(attrib)] != -1;
 	return builtinAttributes[int(attrib)] != -1;
@@ -821,6 +835,16 @@ bool Shader::getConstant(UniformType in, const char *&out)
 	return uniformTypes.find(in, out);
 	return uniformTypes.find(in, out);
 }
 }
 
 
+bool Shader::getConstant(const char *in, VertexAttribID &out)
+{
+	return attribNames.find(in, out);
+}
+
+bool Shader::getConstant(VertexAttribID in, const char *&out)
+{
+	return attribNames.find(in, out);
+}
+
 StringMap<Shader::ShaderStage, Shader::STAGE_MAX_ENUM>::Entry Shader::stageNameEntries[] =
 StringMap<Shader::ShaderStage, Shader::STAGE_MAX_ENUM>::Entry Shader::stageNameEntries[] =
 {
 {
 	{"vertex", Shader::STAGE_VERTEX},
 	{"vertex", Shader::STAGE_VERTEX},

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

@@ -174,6 +174,8 @@ public:
 	 **/
 	 **/
 	UniformType getExternVariable(const std::string &name, int &components, int &count);
 	UniformType getExternVariable(const std::string &name, int &components, int &count);
 
 
+	GLint getAttribLocation(const std::string &name);
+
 	/**
 	/**
 	 * Internal use only.
 	 * Internal use only.
 	 **/
 	 **/
@@ -192,6 +194,9 @@ public:
 	static bool getConstant(const char *in, UniformType &out);
 	static bool getConstant(const char *in, UniformType &out);
 	static bool getConstant(UniformType in, const char *&out);
 	static bool getConstant(UniformType in, const char *&out);
 
 
+	static bool getConstant(const char *in, VertexAttribID &out);
+	static bool getConstant(VertexAttribID in, const char *&out);
+
 private:
 private:
 
 
 	// Represents a single uniform/extern shader variable.
 	// Represents a single uniform/extern shader variable.
@@ -237,6 +242,8 @@ private:
 	// Location values for any generic vertex attribute variables.
 	// Location values for any generic vertex attribute variables.
 	GLint builtinAttributes[ATTRIB_MAX_ENUM];
 	GLint builtinAttributes[ATTRIB_MAX_ENUM];
 
 
+	std::map<std::string, GLint> attributes;
+
 	// Uniform location buffer map
 	// Uniform location buffer map
 	std::map<std::string, Uniform> uniforms;
 	std::map<std::string, Uniform> uniforms;
 
 

+ 5 - 37
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -41,7 +41,7 @@ namespace graphics
 namespace opengl
 namespace opengl
 {
 {
 
 
-SpriteBatch::SpriteBatch(Texture *texture, int size, int usage)
+SpriteBatch::SpriteBatch(Texture *texture, int size, Mesh::Usage usage)
 	: texture(texture)
 	: texture(texture)
 	, size(size)
 	, size(size)
 	, next(0)
 	, next(0)
@@ -54,26 +54,13 @@ SpriteBatch::SpriteBatch(Texture *texture, int size, int usage)
 	if (size <= 0)
 	if (size <= 0)
 		throw love::Exception("Invalid SpriteBatch size.");
 		throw love::Exception("Invalid SpriteBatch size.");
 
 
-	GLenum gl_usage;
-	switch (usage)
-	{
-	default:
-	case USAGE_DYNAMIC:
-		gl_usage = GL_DYNAMIC_DRAW;
-		break;
-	case USAGE_STATIC:
-		gl_usage = GL_STATIC_DRAW;
-		break;
-	case USAGE_STREAM:
-		gl_usage = GL_STREAM_DRAW;
-		break;
-	}
+	GLenum gl_usage = Mesh::getGLBufferUsage(usage);
 
 
 	const size_t vertex_size = sizeof(Vertex) * 4 * size;
 	const size_t vertex_size = sizeof(Vertex) * 4 * size;
 
 
 	try
 	try
 	{
 	{
-		array_buf = GLBuffer::Create(vertex_size, GL_ARRAY_BUFFER, gl_usage);
+		array_buf = new GLBuffer(vertex_size, nullptr, GL_ARRAY_BUFFER, gl_usage);
 	}
 	}
 	catch (love::Exception &)
 	catch (love::Exception &)
 	{
 	{
@@ -162,7 +149,7 @@ void SpriteBatch::setColor(const Color &color)
 void SpriteBatch::setColor()
 void SpriteBatch::setColor()
 {
 {
 	delete color;
 	delete color;
-	color = 0;
+	color = nullptr;
 }
 }
 
 
 const Color *SpriteBatch::getColor() const
 const Color *SpriteBatch::getColor() const
@@ -195,7 +182,7 @@ void SpriteBatch::setBufferSize(int newsize)
 
 
 	try
 	try
 	{
 	{
-		new_array_buf = GLBuffer::Create(vertex_size, array_buf->getTarget(), array_buf->getUsage());
+		new_array_buf = new GLBuffer(vertex_size, nullptr, array_buf->getTarget(), array_buf->getUsage());
 
 
 		// Copy as much of the old data into the new GLBuffer as can fit.
 		// Copy as much of the old data into the new GLBuffer as can fit.
 		GLBuffer::Bind bind(*new_array_buf);
 		GLBuffer::Bind bind(*new_array_buf);
@@ -311,25 +298,6 @@ void SpriteBatch::setColorv(Vertex *v, const Color &color)
 	}
 	}
 }
 }
 
 
-bool SpriteBatch::getConstant(const char *in, UsageHint &out)
-{
-	return usageHints.find(in, out);
-}
-
-bool SpriteBatch::getConstant(UsageHint in, const char *&out)
-{
-	return usageHints.find(in, out);
-}
-
-StringMap<SpriteBatch::UsageHint, SpriteBatch::USAGE_MAX_ENUM>::Entry SpriteBatch::usageHintEntries[] =
-{
-	{"dynamic", SpriteBatch::USAGE_DYNAMIC},
-	{"static",  SpriteBatch::USAGE_STATIC},
-	{"stream",  SpriteBatch::USAGE_STREAM},
-};
-
-StringMap<SpriteBatch::UsageHint, SpriteBatch::USAGE_MAX_ENUM> SpriteBatch::usageHints(usageHintEntries, sizeof(usageHintEntries));
-
 } // opengl
 } // opengl
 } // graphics
 } // graphics
 } // love
 } // love

+ 2 - 16
src/modules/graphics/opengl/SpriteBatch.h

@@ -27,12 +27,12 @@
 // LOVE
 // LOVE
 #include "common/math.h"
 #include "common/math.h"
 #include "common/Matrix.h"
 #include "common/Matrix.h"
-#include "common/StringMap.h"
 #include "graphics/Drawable.h"
 #include "graphics/Drawable.h"
 #include "graphics/Volatile.h"
 #include "graphics/Volatile.h"
 #include "graphics/Color.h"
 #include "graphics/Color.h"
 #include "graphics/Quad.h"
 #include "graphics/Quad.h"
 #include "GLBuffer.h"
 #include "GLBuffer.h"
+#include "Mesh.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -49,15 +49,7 @@ class SpriteBatch : public Drawable
 {
 {
 public:
 public:
 
 
-	enum UsageHint
-	{
-		USAGE_DYNAMIC,
-		USAGE_STATIC,
-		USAGE_STREAM,
-		USAGE_MAX_ENUM
-	};
-
-	SpriteBatch(Texture *texture, int size, int usage);
+	SpriteBatch(Texture *texture, int size, Mesh::Usage usage);
 	virtual ~SpriteBatch();
 	virtual ~SpriteBatch();
 
 
 	int add(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky, int index = -1);
 	int add(float x, float y, float a, float sx, float sy, float ox, float oy, float kx, float ky, int index = -1);
@@ -109,9 +101,6 @@ public:
 	// Implements Drawable.
 	// Implements Drawable.
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
 
 
-	static bool getConstant(const char *in, UsageHint &out);
-	static bool getConstant(UsageHint in, const char *&out);
-
 private:
 private:
 
 
 	void addv(const Vertex *v, const Matrix &m, int index);
 	void addv(const Vertex *v, const Matrix &m, int index);
@@ -144,9 +133,6 @@ private:
 	size_t buffer_used_offset;
 	size_t buffer_used_offset;
 	size_t buffer_used_size;
 	size_t buffer_used_size;
 
 
-	static StringMap<UsageHint, USAGE_MAX_ENUM>::Entry usageHintEntries[];
-	static StringMap<UsageHint, USAGE_MAX_ENUM> usageHints;
-
 }; // SpriteBatch
 }; // SpriteBatch
 
 
 } // opengl
 } // opengl

+ 1 - 1
src/modules/graphics/opengl/Text.cpp

@@ -59,7 +59,7 @@ void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t
 		if (vbo != nullptr)
 		if (vbo != nullptr)
 			newsize = std::max(size_t(vbo->getSize() * 1.5), newsize);
 			newsize = std::max(size_t(vbo->getSize() * 1.5), newsize);
 
 
-		GLBuffer *new_vbo = GLBuffer::Create(newsize, GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW);
+		GLBuffer *new_vbo = new GLBuffer(newsize, nullptr, GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW);
 
 
 		if (vbo != nullptr)
 		if (vbo != nullptr)
 		{
 		{

+ 207 - 43
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -258,14 +258,14 @@ int w_newImage(lua_State *L)
 		{
 		{
 			luax_catchexcept(L,
 			luax_catchexcept(L,
 				[&]() { cdata = image->newCompressedData(fdata); },
 				[&]() { cdata = image->newCompressedData(fdata); },
-				[&]() { fdata->release(); }
+				[&](bool) { fdata->release(); }
 			);
 			);
 		}
 		}
 		else
 		else
 		{
 		{
 			luax_catchexcept(L,
 			luax_catchexcept(L,
 				[&]() { data = image->newImageData(fdata); },
 				[&]() { data = image->newImageData(fdata); },
-				[&]() { fdata->release(); }
+				[&](bool) { fdata->release(); }
 			);
 			);
 		}
 		}
 
 
@@ -289,7 +289,7 @@ int w_newImage(lua_State *L)
 			else if (data)
 			else if (data)
 				image = instance()->newImage(data, flags);
 				image = instance()->newImage(data, flags);
 		},
 		},
-		[&]() {
+		[&](bool) {
 			if (releasedata && data)
 			if (releasedata && data)
 				data->release();
 				data->release();
 			else if (releasedata && cdata)
 			else if (releasedata && cdata)
@@ -393,11 +393,11 @@ int w_newSpriteBatch(lua_State *L)
 {
 {
 	Texture *texture = luax_checktexture(L, 1);
 	Texture *texture = luax_checktexture(L, 1);
 	int size = luaL_optint(L, 2, 1000);
 	int size = luaL_optint(L, 2, 1000);
-	SpriteBatch::UsageHint usage = SpriteBatch::USAGE_DYNAMIC;
+	Mesh::Usage usage = Mesh::USAGE_DYNAMIC;
 	if (lua_gettop(L) > 2)
 	if (lua_gettop(L) > 2)
 	{
 	{
 		const char *usagestr = luaL_checkstring(L, 3);
 		const char *usagestr = luaL_checkstring(L, 3);
-		if (!SpriteBatch::getConstant(usagestr, usage))
+		if (!Mesh::getConstant(usagestr, usage))
 			return luaL_error(L, "Invalid SpriteBatch usage hint: %s", usagestr);
 			return luaL_error(L, "Invalid SpriteBatch usage hint: %s", usagestr);
 	}
 	}
 
 
@@ -558,43 +558,62 @@ int w_newShader(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-int w_newMesh(lua_State *L)
+static Mesh::Usage luax_optmeshusage(lua_State *L, int idx, Mesh::Usage def)
 {
 {
-	// Check first argument: table of vertices or number of vertices.
-	int ttype = lua_type(L, 1);
-	if (ttype != LUA_TTABLE && ttype != LUA_TNUMBER)
-		luaL_argerror(L, 1, "table or number expected");
+	const char *usagestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx);
 
 
-	// Second argument: optional texture.
-	Texture *tex = nullptr;
-	if (!lua_isnoneornil(L, 2))
-		tex = luax_checktexture(L, 2);
+	if (usagestr && !Mesh::getConstant(usagestr, def))
+		luaL_error(L, "Invalid mesh usage hint: %s", usagestr);
+
+	return def;
+}
+
+static Mesh::DrawMode luax_optmeshdrawmode(lua_State *L, int idx, Mesh::DrawMode def)
+{
+	const char *modestr = lua_isnoneornil(L, idx) ? nullptr : luaL_checkstring(L, idx);
+
+	if (modestr && !Mesh::getConstant(modestr, def))
+		luaL_error(L, "Invalid mesh draw mode: %s", modestr);
 
 
-	// Third argument: optional draw mode.
-	const char *str = 0;
-	Mesh::DrawMode mode = Mesh::DRAW_MODE_FAN;
-	str = lua_isnoneornil(L, 3) ? 0 : luaL_checkstring(L, 3);
+	return def;
+}
+
+template <typename T>
+static inline size_t writeVertexData(lua_State *L, int startidx, int components, char *data)
+{
+	T *componentdata = (T *) data;
 
 
-	if (str && !Mesh::getConstant(str, mode))
-		return luaL_error(L, "Invalid mesh draw mode: %s", str);
+	for (int i = 0; i < components; i++)
+		componentdata[i] = (T) luaL_checknumber(L, startidx + i);
 
 
+	return sizeof(T) * components;
+}
+
+static Mesh *newStandardMesh(lua_State *L)
+{
 	Mesh *t = nullptr;
 	Mesh *t = nullptr;
 
 
-	if (ttype == LUA_TTABLE)
+	Mesh::DrawMode drawmode = luax_optmeshdrawmode(L, 2, Mesh::DRAWMODE_FAN);
+	Mesh::Usage usage = luax_optmeshusage(L, 3, Mesh::USAGE_DYNAMIC);
+
+	// First argument is a table of standard vertices, or the number of
+	// standard vertices.
+	if (lua_istable(L, 1))
 	{
 	{
-		size_t vertex_count = lua_objlen(L, 1);
+		size_t vertexcount = lua_objlen(L, 1);
 		std::vector<Vertex> vertices;
 		std::vector<Vertex> vertices;
-		vertices.reserve(vertex_count);
-
-		bool use_colors = false;
+		vertices.reserve(vertexcount);
 
 
 		// Get the vertices from the table.
 		// Get the vertices from the table.
-		for (size_t i = 1; i <= vertex_count; i++)
+		for (size_t i = 1; i <= vertexcount; i++)
 		{
 		{
 			lua_rawgeti(L, 1, (int) i);
 			lua_rawgeti(L, 1, (int) i);
 
 
 			if (lua_type(L, -1) != LUA_TTABLE)
 			if (lua_type(L, -1) != LUA_TTABLE)
-				return luax_typerror(L, 1, "table of tables");
+			{
+				luax_typerror(L, 1, "table of tables");
+				return nullptr;
+			}
 
 
 			for (int j = 1; j <= 8; j++)
 			for (int j = 1; j <= 8; j++)
 				lua_rawgeti(L, -j, j);
 				lua_rawgeti(L, -j, j);
@@ -603,34 +622,178 @@ int w_newMesh(lua_State *L)
 
 
 			v.x = (float) luaL_checknumber(L, -8);
 			v.x = (float) luaL_checknumber(L, -8);
 			v.y = (float) luaL_checknumber(L, -7);
 			v.y = (float) luaL_checknumber(L, -7);
-
 			v.s = (float) luaL_optnumber(L, -6, 0.0);
 			v.s = (float) luaL_optnumber(L, -6, 0.0);
 			v.t = (float) luaL_optnumber(L, -5, 0.0);
 			v.t = (float) luaL_optnumber(L, -5, 0.0);
-
 			v.r = (unsigned char) luaL_optinteger(L, -4, 255);
 			v.r = (unsigned char) luaL_optinteger(L, -4, 255);
 			v.g = (unsigned char) luaL_optinteger(L, -3, 255);
 			v.g = (unsigned char) luaL_optinteger(L, -3, 255);
 			v.b = (unsigned char) luaL_optinteger(L, -2, 255);
 			v.b = (unsigned char) luaL_optinteger(L, -2, 255);
 			v.a = (unsigned char) luaL_optinteger(L, -1, 255);
 			v.a = (unsigned char) luaL_optinteger(L, -1, 255);
 
 
-			// Enable per-vertex coloring if any color is not the default.
-			if (!use_colors && (v.r != 255 || v.g != 255 || v.b != 255 || v.a != 255))
-				use_colors = true;
-
 			lua_pop(L, 9);
 			lua_pop(L, 9);
 			vertices.push_back(v);
 			vertices.push_back(v);
 		}
 		}
 
 
-		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertices, mode); });
-		t->setVertexColors(use_colors);
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertices, drawmode, usage); });
 	}
 	}
 	else
 	else
 	{
 	{
 		int count = luaL_checkint(L, 1);
 		int count = luaL_checkint(L, 1);
-		luax_catchexcept(L, [&](){ t = instance()->newMesh(count, mode); });
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(count, drawmode, usage); });
+	}
+
+	return t;
+}
+
+static Mesh *newCustomMesh(lua_State *L)
+{
+	Mesh *t = nullptr;
+
+	// First argument is the vertex format, second is a table of vertices or
+	// the number of vertices.
+	std::vector<Mesh::AttribFormat> vertexformat;
+
+	Mesh::DrawMode drawmode = luax_optmeshdrawmode(L, 3, Mesh::DRAWMODE_FAN);
+	Mesh::Usage usage = luax_optmeshusage(L, 4, Mesh::USAGE_DYNAMIC);
+
+	lua_rawgeti(L, 1, 1);
+	if (!lua_istable(L, -1))
+	{
+		luaL_argerror(L, 1, "table of tables expected");
+		return nullptr;
+	}
+	lua_pop(L, 1);
+
+	// Per-vertex attribute formats.
+	for (int i = 1; i <= (int) lua_objlen(L, 1); i++)
+	{
+		lua_rawgeti(L, 1, i);
+
+		// {name, datatype, components}
+		for (int j = 1; j <= 3; j++)
+			lua_rawgeti(L, -j, j);
+
+		Mesh::AttribFormat format;
+		format.name = luaL_checkstring(L, -3);
+
+		const char *tname = luaL_checkstring(L, -2);
+		if (!Mesh::getConstant(tname, format.type))
+		{
+			luaL_error(L, "Invalid Mesh vertex data type name: %s", tname);
+			return nullptr;
+		}
+
+		format.components = luaL_checkint(L, -1);
+		if (format.components <= 0 || format.components > 4)
+		{
+			luaL_error(L, "Number of vertex attribute components must be between 1 and 4 (got %d)", format.components);
+			return nullptr;
+		}
+
+		lua_pop(L, 4);
+		vertexformat.push_back(format);
+	}
+
+	if (lua_isnumber(L, 2))
+	{
+		int vertexcount = luaL_checkint(L, 2);
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertexformat, vertexcount, drawmode, usage); });
+	}
+	else if (luax_istype(L, 2, DATA_ID))
+	{
+		// Vertex data comes directly from a Data object.
+		Data *data = luax_checktype<Data>(L, 2, DATA_ID);
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertexformat, data->getData(), data->getSize(), drawmode, usage); });
+	}
+	else
+	{
+		// Table of vertices.
+		lua_rawgeti(L, 2, 1);
+		if (!lua_istable(L, -1))
+		{
+			luaL_argerror(L, 2, "expected table of tables");
+			return nullptr;
+		}
+		lua_pop(L, 1);
+
+		int vertexcomponents = 0;
+		for (const Mesh::AttribFormat &format : vertexformat)
+			vertexcomponents += format.components;
+
+		size_t numvertices = lua_objlen(L, 2);
+
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertexformat, numvertices, drawmode, usage); });
+
+		// Maximum possible data size for a single vertex attribute.
+		char data[sizeof(float) * 4];
+
+		for (size_t vertindex = 0; vertindex < numvertices; vertindex++)
+		{
+			// get vertices[vertindex]
+			lua_rawgeti(L, 2, vertindex + 1);
+			luaL_checktype(L, -1, LUA_TTABLE);
+
+			if ((int) lua_objlen(L, -1) < vertexcomponents)
+			{
+				t->release();
+				const char *err = "Invalid number of components in vertex #%d (expected %d components, got %d)";
+				luaL_error(L, err, vertindex+1, vertexcomponents, lua_objlen(L, -1));
+				return nullptr;
+			}
+
+			int n = 0;
+			for (size_t i = 0; i < vertexformat.size(); i++)
+			{
+				int components = vertexformat[i].components;
+
+				// get vertices[vertindex][n]
+				for (int c = 0; c < components; c++)
+				{
+					n++;
+					lua_rawgeti(L, -(c + 1), n);
+				}
+
+				// Fetch the values from Lua and store them in data buffer.
+				switch (vertexformat[i].type)
+				{
+				case Mesh::DATA_BYTE:
+					writeVertexData<uint8>(L, -components, components, data);
+					break;
+				case Mesh::DATA_FLOAT:
+				default:
+					writeVertexData<float>(L, -components, components, data);
+					break;
+				}
+
+				lua_pop(L, components);
+
+				luax_catchexcept(L,
+					[&](){ t->setVertexAttribute(vertindex, i, data, sizeof(float) * 4); },
+					[&](bool diderror){ if (diderror) t->release(); }
+				);
+			}
+
+			lua_pop(L, 1); // pop vertices[vertindex]
+		}
+
+		t->flush();
 	}
 	}
 
 
-	if (tex)
-		t->setTexture(tex);
+	return t;
+}
+
+int w_newMesh(lua_State *L)
+{
+	// Check first argument: table or number of vertices.
+	int ttype = lua_type(L, 1);
+	if (ttype != LUA_TTABLE && ttype != LUA_TNUMBER)
+		luaL_argerror(L, 1, "table or number expected");
+
+	Mesh *t = nullptr;
+
+	if (ttype == LUA_TTABLE && (lua_istable(L, 2) || lua_type(L, 2) == LUA_TNUMBER))
+		t = newCustomMesh(L);
+	else
+		t = newStandardMesh(L);
 
 
 	luax_pushtype(L, GRAPHICS_MESH_ID, t);
 	luax_pushtype(L, GRAPHICS_MESH_ID, t);
 	t->release();
 	t->release();
@@ -1086,7 +1249,6 @@ int w_setDefaultShaderCode(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
-
 int w_getSupported(lua_State *L)
 int w_getSupported(lua_State *L)
 {
 {
 	lua_createtable(L, 0, (int) Graphics::SUPPORT_MAX_ENUM);
 	lua_createtable(L, 0, (int) Graphics::SUPPORT_MAX_ENUM);
@@ -1246,10 +1408,12 @@ int w_draw(lua_State *L)
 	float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
 	float kx = (float) luaL_optnumber(L, startidx + 7, 0.0);
 	float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
 	float ky = (float) luaL_optnumber(L, startidx + 8, 0.0);
 
 
-	if (texture && quad)
-		texture->drawq(quad, x, y, a, sx, sy, ox, oy, kx, ky);
-	else if (drawable)
-		drawable->draw(x, y, a, sx, sy, ox, oy, kx, ky);
+	luax_catchexcept(L, [&]() {
+		if (texture && quad)
+			texture->drawq(quad, x, y, a, sx, sy, ox, oy, kx, ky);
+		else if (drawable)
+			drawable->draw(x, y, a, sx, sy, ox, oy, kx, ky);
+	});
 
 
 	return 0;
 	return 0;
 }
 }

+ 187 - 118
src/modules/graphics/opengl/wrap_Mesh.cpp

@@ -39,161 +39,238 @@ Mesh *luax_checkmesh(lua_State *L, int idx)
 	return luax_checktype<Mesh>(L, idx, GRAPHICS_MESH_ID);
 	return luax_checktype<Mesh>(L, idx, GRAPHICS_MESH_ID);
 }
 }
 
 
+template <typename T>
+static inline size_t writeData(lua_State *L, int startidx, int components, char *data)
+{
+	T *componentdata = (T *) data;
+	for (int i = 0; i < components; i++)
+		componentdata[i] = (T) luaL_checknumber(L, startidx + i);
+
+	return sizeof(T) * components;
+}
+
+static inline char *writeAttributeData(lua_State *L, int startidx, Mesh::DataType type, int components, char *data)
+{
+	switch (type)
+	{
+	case Mesh::DATA_BYTE:
+		data += writeData<uint8>(L, startidx, components, data);
+		break;
+	case Mesh::DATA_FLOAT:
+		data += writeData<float>(L, startidx, components, data);
+		break;
+	default:
+		break;
+	}
+
+	return data;
+}
+
+template <typename T>
+static inline size_t readData(lua_State *L, int components, const char *data)
+{
+	const T *componentdata = (const T *) data;
+	for (int i = 0; i < components; i++)
+		lua_pushnumber(L, (lua_Number) componentdata[i]);
+
+	return sizeof(T) * components;
+}
+
+static inline const char *readAttributeData(lua_State *L, Mesh::DataType type, int components, const char *data)
+{
+	switch (type)
+	{
+	case Mesh::DATA_BYTE:
+		data += readData<uint8>(L, components, data);
+		break;
+	case Mesh::DATA_FLOAT:
+		data += readData<float>(L, components, data);
+		break;
+	default:
+		break;
+	}
+
+	return data;
+}
+
 int w_Mesh_setVertex(lua_State *L)
 int w_Mesh_setVertex(lua_State *L)
 {
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
-	size_t i = size_t(luaL_checkinteger(L, 2) - 1);
+	size_t index = (size_t) luaL_checkinteger(L, 2) - 1;
+
+	bool istable = lua_istable(L, 3);
+
+	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
 
 
-	Vertex v;
+	char *data = (char *) t->getVertexScratchBuffer();
+	char *writtendata = data;
 
 
-	if (lua_istable(L, 3))
+	int idx = istable ? 1 : 3;
+
+	if (istable)
 	{
 	{
-		for (int i = 1; i <= 8; i++)
-			lua_rawgeti(L, 3, i);
-
-		v.x = luaL_checknumber(L, -8);
-		v.y = luaL_checknumber(L, -7);
-		v.s = luaL_optnumber(L, -6, 0.0);
-		v.t = luaL_optnumber(L, -5, 0.0);
-		v.r = luaL_optinteger(L, -4, 255);
-		v.g = luaL_optinteger(L, -3, 255);
-		v.b = luaL_optinteger(L, -2, 255);
-		v.a = luaL_optinteger(L, -1, 255);
-
-		lua_pop(L, 8);
+		for (const Mesh::AttribFormat &format : vertexformat)
+		{
+			for (int i = idx; i < idx + format.components; i++)
+				lua_rawgeti(L, 3, i);
+
+			// Fetch the values from Lua and store them in data buffer.
+			writtendata = writeAttributeData(L, -format.components, format.type, format.components, writtendata);
+
+			idx += format.components;
+			lua_pop(L, format.components);
+		}
 	}
 	}
 	else
 	else
 	{
 	{
-		v.x = luaL_checknumber(L, 3);
-		v.y = luaL_checknumber(L, 4);
-		v.s = luaL_optnumber(L, 5, 0.0);
-		v.t = luaL_optnumber(L, 6, 0.0);
-		v.r = luaL_optinteger(L,  7, 255);
-		v.g = luaL_optinteger(L,  8, 255);
-		v.b = luaL_optinteger(L,  9, 255);
-		v.a = luaL_optinteger(L, 10, 255);
+		for (const Mesh::AttribFormat &format : vertexformat)
+		{
+			// Fetch the values from Lua and store them in data buffer.
+			writtendata = writeAttributeData(L, idx, format.type, format.components, writtendata);
+			idx += format.components;
+		}
 	}
 	}
 
 
-	luax_catchexcept(L, [&](){ t->setVertex(i, v); });
+	luax_catchexcept(L, [&](){ t->setVertex(index, data, t->getVertexStride()); });
 	return 0;
 	return 0;
 }
 }
 
 
 int w_Mesh_getVertex(lua_State *L)
 int w_Mesh_getVertex(lua_State *L)
 {
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
-	size_t i = (size_t) (luaL_checkinteger(L, 2) - 1);
+	size_t index = (size_t) luaL_checkinteger(L, 2) - 1;
+
+	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
+
+	char *data = (char *) t->getVertexScratchBuffer();
+	const char *readdata = data;
 
 
-	Vertex v;
-	luax_catchexcept(L, [&](){ v = t->getVertex(i); });
+	luax_catchexcept(L, [&](){ t->getVertex(index, data, t->getVertexStride()); });
 
 
-	lua_pushnumber(L, v.x);
-	lua_pushnumber(L, v.y);
-	lua_pushnumber(L, v.s);
-	lua_pushnumber(L, v.t);
-	lua_pushnumber(L, v.r);
-	lua_pushnumber(L, v.g);
-	lua_pushnumber(L, v.b);
-	lua_pushnumber(L, v.a);
+	int n = 0;
 
 
-	return 8;
+	for (const Mesh::AttribFormat &format : vertexformat)
+	{
+		readdata = readAttributeData(L, format.type, format.components, readdata);
+		n += format.components;
+	}
+
+	return n;
 }
 }
 
 
-int w_Mesh_setVertices(lua_State *L)
+int w_Mesh_setVertexAttribute(lua_State *L)
 {
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
+	size_t vertindex = (size_t) luaL_checkinteger(L, 2) - 1;
+	int attribindex = (int) luaL_checkinteger(L, 3) - 1;
 
 
-	int vertex_count = (int) lua_objlen(L, 2);
-	std::vector<Vertex> vertices;
-	vertices.reserve(vertex_count);
-
-	// Get the vertices from the table.
-	for (int i = 1; i <= vertex_count; i++)
-	{
-		lua_rawgeti(L, 2, i);
+	Mesh::DataType type;
+	int components;
+	luax_catchexcept(L, [&](){ type = t->getAttributeInfo(attribindex, components); });
 
 
-		if (lua_type(L, -1) != LUA_TTABLE)
-			return luax_typerror(L, 2, "table of tables");
+	// Maximum possible size for a single vertex attribute.
+	char data[sizeof(float) * 4];
 
 
-		for (int j = 1; j <= 8; j++)
-			lua_rawgeti(L, -j, j);
+	// Fetch the values from Lua and store them in the data buffer.
+	writeAttributeData(L, 4, type, components, data);
 
 
-		Vertex v;
+	luax_catchexcept(L, [&](){ t->setVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
+	return 0;
+}
 
 
-		v.x = (float) luaL_checknumber(L, -8);
-		v.y = (float) luaL_checknumber(L, -7);
+int w_Mesh_getVertexAttribute(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	size_t vertindex = (size_t) luaL_checkinteger(L, 2) - 1;
+	int attribindex = (int) luaL_checkinteger(L, 3) - 1;
 
 
-		v.s = (float) luaL_optnumber(L, -6, 0.0);
-		v.t = (float) luaL_optnumber(L, -5, 0.0);
+	Mesh::DataType type;
+	int components;
+	luax_catchexcept(L, [&](){ type = t->getAttributeInfo(attribindex, components); });
 
 
-		v.r = (unsigned char) luaL_optinteger(L, -4, 255);
-		v.g = (unsigned char) luaL_optinteger(L, -3, 255);
-		v.b = (unsigned char) luaL_optinteger(L, -2, 255);
-		v.a = (unsigned char) luaL_optinteger(L, -1, 255);
+	// Maximum possible size for a single vertex attribute.
+	char data[sizeof(float) * 4];
 
 
-		lua_pop(L, 9);
-		vertices.push_back(v);
-	}
+	luax_catchexcept(L, [&](){ t->getVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); });
 
 
-	luax_catchexcept(L, [&](){ t->setVertices(vertices); });
-	return 0;
+	readAttributeData(L, type, components, data);
+	return components;
 }
 }
 
 
-int w_Mesh_getVertices(lua_State *L)
+int w_Mesh_getVertexCount(lua_State *L)
 {
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
+	lua_pushinteger(L, t->getVertexCount());
+	return 1;
+}
 
 
-	const Vertex *vertices = t->getVertices();
+int w_Mesh_getVertexFormat(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
 
 
-	int count = (int) t->getVertexCount();
-	lua_createtable(L, count, 0);
+	const std::vector<Mesh::AttribFormat> &vertexformat = t->getVertexFormat();
+	lua_createtable(L, (int) vertexformat.size(), 0);
 
 
-	if (count == 0 || vertices == nullptr)
-		return 1;
+	const char *tname = nullptr;
 
 
-	for (int i = 0; i < count; i++)
+	for (size_t i = 0; i < vertexformat.size(); i++)
 	{
 	{
-		// Create vertex table.
-		lua_createtable(L, 8, 0);
+		if (!Mesh::getConstant(vertexformat[i].type, tname))
+			return luaL_error(L, "Unknown vertex attribute data type.");
+
+		lua_createtable(L, 3, 0);
 
 
-		lua_pushnumber(L, vertices[i].x);
+		lua_pushstring(L, vertexformat[i].name.c_str());
 		lua_rawseti(L, -2, 1);
 		lua_rawseti(L, -2, 1);
 
 
-		lua_pushnumber(L, vertices[i].y);
+		lua_pushstring(L, tname);
 		lua_rawseti(L, -2, 2);
 		lua_rawseti(L, -2, 2);
 
 
-		lua_pushnumber(L, vertices[i].s);
+		lua_pushinteger(L, vertexformat[i].components);
 		lua_rawseti(L, -2, 3);
 		lua_rawseti(L, -2, 3);
 
 
-		lua_pushnumber(L, vertices[i].t);
-		lua_rawseti(L, -2, 4);
-
-		lua_pushnumber(L, vertices[i].r);
-		lua_rawseti(L, -2, 5);
-
-		lua_pushnumber(L, vertices[i].g);
-		lua_rawseti(L, -2, 6);
-
-		lua_pushnumber(L, vertices[i].b);
-		lua_rawseti(L, -2, 7);
-
-		lua_pushnumber(L, vertices[i].a);
-		lua_rawseti(L, -2, 8);
-
-		// Insert vertex table into vertices table.
-		lua_rawseti(L, -2, i + 1);
+		// format[i] = {name, type, components}
+		lua_rawseti(L, -2, (int) i + 1);
 	}
 	}
-
-	// Return vertices table.
+	
 	return 1;
 	return 1;
 }
 }
 
 
-int w_Mesh_getVertexCount(lua_State *L)
+int w_Mesh_setAttributeEnabled(lua_State *L)
 {
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
-	lua_pushinteger(L, t->getVertexCount());
+	const char *name = luaL_checkstring(L, 2);
+	bool enable = luax_toboolean(L, 3);
+	luax_catchexcept(L, [&](){ t->setAttributeEnabled(name, enable); });
+	return 0;
+}
+
+int w_Mesh_isAttributeEnabled(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	const char *name = luaL_checkstring(L, 2);
+	bool enabled = false;
+	luax_catchexcept(L, [&](){ enabled = t->isAttributeEnabled(name); });
+	lua_pushboolean(L, enabled);
 	return 1;
 	return 1;
 }
 }
 
 
+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);
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, mesh); });
+	return 0;
+}
+
+int w_Mesh_flush(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	t->flush();
+	return 0;
+}
+
 int w_Mesh_setVertexMap(lua_State *L)
 int w_Mesh_setVertexMap(lua_State *L)
 {
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	Mesh *t = luax_checkmesh(L, 1);
@@ -204,15 +281,18 @@ int w_Mesh_setVertexMap(lua_State *L)
 	std::vector<uint32> vertexmap;
 	std::vector<uint32> vertexmap;
 	vertexmap.reserve(nargs);
 	vertexmap.reserve(nargs);
 
 
-	for (int i = 0; i < nargs; i++)
+	if (is_table)
 	{
 	{
-		if (is_table)
+		for (int i = 0; i < nargs; i++)
 		{
 		{
 			lua_rawgeti(L, 2, i + 1);
 			lua_rawgeti(L, 2, i + 1);
 			vertexmap.push_back(uint32(luaL_checkinteger(L, -1) - 1));
 			vertexmap.push_back(uint32(luaL_checkinteger(L, -1) - 1));
 			lua_pop(L, 1);
 			lua_pop(L, 1);
 		}
 		}
-		else
+	}
+	else
+	{
+		for (int i = 0; i < nargs; i++)
 			vertexmap.push_back(uint32(luaL_checkinteger(L, i + 2) - 1));
 			vertexmap.push_back(uint32(luaL_checkinteger(L, i + 2) - 1));
 	}
 	}
 
 
@@ -332,27 +412,18 @@ int w_Mesh_getDrawRange(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
-int w_Mesh_setVertexColors(lua_State *L)
-{
-	Mesh *t = luax_checkmesh(L, 1);
-	t->setVertexColors(luax_toboolean(L, 2));
-	return 0;
-}
-
-int w_Mesh_hasVertexColors(lua_State *L)
-{
-	Mesh *t = luax_checkmesh(L, 1);
-	luax_pushboolean(L, t->hasVertexColors());
-	return 1;
-}
-
 static const luaL_Reg functions[] =
 static const luaL_Reg functions[] =
 {
 {
 	{ "setVertex", w_Mesh_setVertex },
 	{ "setVertex", w_Mesh_setVertex },
 	{ "getVertex", w_Mesh_getVertex },
 	{ "getVertex", w_Mesh_getVertex },
-	{ "setVertices", w_Mesh_setVertices },
-	{ "getVertices", w_Mesh_getVertices },
+	{ "setVertexAttribute", w_Mesh_setVertexAttribute },
+	{ "getVertexAttribute", w_Mesh_getVertexAttribute },
 	{ "getVertexCount", w_Mesh_getVertexCount },
 	{ "getVertexCount", w_Mesh_getVertexCount },
+	{ "getVertexFormat", w_Mesh_getVertexFormat },
+	{ "setAttributeEnabled", w_Mesh_setAttributeEnabled },
+	{ "isAttributeEnabled", w_Mesh_isAttributeEnabled },
+	{ "attachAttribute", w_Mesh_attachAttribute },
+	{ "flush", w_Mesh_flush },
 	{ "setVertexMap", w_Mesh_setVertexMap },
 	{ "setVertexMap", w_Mesh_setVertexMap },
 	{ "getVertexMap", w_Mesh_getVertexMap },
 	{ "getVertexMap", w_Mesh_getVertexMap },
 	{ "setTexture", w_Mesh_setTexture },
 	{ "setTexture", w_Mesh_setTexture },
@@ -361,8 +432,6 @@ static const luaL_Reg functions[] =
 	{ "getDrawMode", w_Mesh_getDrawMode },
 	{ "getDrawMode", w_Mesh_getDrawMode },
 	{ "setDrawRange", w_Mesh_setDrawRange },
 	{ "setDrawRange", w_Mesh_setDrawRange },
 	{ "getDrawRange", w_Mesh_getDrawRange },
 	{ "getDrawRange", w_Mesh_getDrawRange },
-	{ "setVertexColors", w_Mesh_setVertexColors },
-	{ "hasVertexColors", w_Mesh_hasVertexColors },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 7 - 6
src/modules/graphics/opengl/wrap_Mesh.h

@@ -33,12 +33,16 @@ namespace opengl
 {
 {
 
 
 Mesh *luax_checkmesh(lua_State *L, int idx);
 Mesh *luax_checkmesh(lua_State *L, int idx);
-
 int w_Mesh_setVertex(lua_State *L);
 int w_Mesh_setVertex(lua_State *L);
 int w_Mesh_getVertex(lua_State *L);
 int w_Mesh_getVertex(lua_State *L);
-int w_Mesh_setVertices(lua_State *L);
-int w_Mesh_getVertices(lua_State *L);
+int w_Mesh_setVertexAttribute(lua_State *L);
+int w_Mesh_getVertexAttribute(lua_State *L);
 int w_Mesh_getVertexCount(lua_State *L);
 int w_Mesh_getVertexCount(lua_State *L);
+int w_Mesh_getVertexFormat(lua_State *L);
+int w_Mesh_setAttributeEnabled(lua_State *L);
+int w_Mesh_isAttributeEnabled(lua_State *L);
+int w_Mesh_attachAttribute(lua_State *L);
+int w_Mesh_flush(lua_State *L);
 int w_Mesh_setVertexMap(lua_State *L);
 int w_Mesh_setVertexMap(lua_State *L);
 int w_Mesh_getVertexMap(lua_State *L);
 int w_Mesh_getVertexMap(lua_State *L);
 int w_Mesh_setTexture(lua_State *L);
 int w_Mesh_setTexture(lua_State *L);
@@ -47,9 +51,6 @@ int w_Mesh_setDrawMode(lua_State *L);
 int w_Mesh_getDrawMode(lua_State *L);
 int w_Mesh_getDrawMode(lua_State *L);
 int w_Mesh_setDrawRange(lua_State *L);
 int w_Mesh_setDrawRange(lua_State *L);
 int w_Mesh_getDrawRange(lua_State *L);
 int w_Mesh_getDrawRange(lua_State *L);
-int w_Mesh_setVertexColors(lua_State *L);
-int w_Mesh_hasVertexColors(lua_State *L);
-
 extern "C" int luaopen_mesh(lua_State *L);
 extern "C" int luaopen_mesh(lua_State *L);
 
 
 } // opengl
 } // opengl

+ 1 - 1
src/modules/graphics/opengl/wrap_Shader.cpp

@@ -234,7 +234,7 @@ int w_Shader_sendMatrix(lua_State *L)
 
 
 	luax_catchexcept(L,
 	luax_catchexcept(L,
 		[&]() { shader->sendMatrix(name, dimension, values, count); },
 		[&]() { shader->sendMatrix(name, dimension, values, count); },
-		[&]() { delete[] values; }
+		[&](bool) { delete[] values; }
 	);
 	);
 
 
 	return 0;
 	return 0;

+ 2 - 2
src/modules/image/wrap_Image.cpp

@@ -58,7 +58,7 @@ int w_newImageData(lua_State *L)
 	ImageData *t = nullptr;
 	ImageData *t = nullptr;
 	luax_catchexcept(L,
 	luax_catchexcept(L,
 		[&]() { t = instance()->newImageData(data); },
 		[&]() { t = instance()->newImageData(data); },
-		[&]() { data->release(); }
+		[&](bool) { data->release(); }
 	);
 	);
 
 
 	luax_pushtype(L, IMAGE_IMAGE_DATA_ID, t);
 	luax_pushtype(L, IMAGE_IMAGE_DATA_ID, t);
@@ -73,7 +73,7 @@ int w_newCompressedData(lua_State *L)
 	CompressedImageData *t = nullptr;
 	CompressedImageData *t = nullptr;
 	luax_catchexcept(L,
 	luax_catchexcept(L,
 		[&]() { t = instance()->newCompressedData(data); },
 		[&]() { t = instance()->newCompressedData(data); },
-		[&]() { data->release(); }
+		[&](bool) { data->release(); }
 	);
 	);
 
 
 	luax_pushtype(L, IMAGE_COMPRESSED_IMAGE_DATA_ID, t);
 	luax_pushtype(L, IMAGE_COMPRESSED_IMAGE_DATA_ID, t);

+ 1 - 1
src/modules/sound/wrap_Sound.cpp

@@ -71,7 +71,7 @@ int w_newDecoder(lua_State *L)
 	Decoder *t = nullptr;
 	Decoder *t = nullptr;
 	luax_catchexcept(L,
 	luax_catchexcept(L,
 		[&]() { t = instance()->newDecoder(data, bufferSize); },
 		[&]() { t = instance()->newDecoder(data, bufferSize); },
-		[&]() { data->release(); }
+		[&](bool) { data->release(); }
 	);
 	);
 
 
 	if (t == nullptr)
 	if (t == nullptr)