Browse Source

Internal restructuring of some Mesh code

Alex Szpakowski 5 years ago
parent
commit
4197a364fd

+ 6 - 11
src/modules/graphics/Graphics.cpp

@@ -268,24 +268,19 @@ Buffer *Graphics::newBuffer(const Buffer::Settings &settings, DataFormat format,
 	return newBuffer(settings, dataformat, data, size, arraylength);
 }
 
-Mesh *Graphics::newMesh(const std::vector<Vertex> &vertices, PrimitiveType drawmode, BufferUsage usage)
+Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
 {
-	return newMesh(Mesh::getDefaultVertexFormat(), &vertices[0], vertices.size() * sizeof(Vertex), drawmode, usage);
-}
-
-Mesh *Graphics::newMesh(int vertexcount, PrimitiveType drawmode, BufferUsage usage)
-{
-	return newMesh(Mesh::getDefaultVertexFormat(), vertexcount, drawmode, usage);
+	return new Mesh(this, vertexformat, vertexcount, drawmode, usage);
 }
 
-love::graphics::Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage)
+Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
 {
-	return new Mesh(this, vertexformat, vertexcount, drawmode, usage);
+	return new Mesh(this, vertexformat, data, datasize, drawmode, usage);
 }
 
-love::graphics::Mesh *Graphics::newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage)
+Mesh *Graphics::newMesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
 {
-	return new Mesh(this, vertexformat, data, datasize, drawmode, usage);
+	return new Mesh(attributes, drawmode);
 }
 
 love::graphics::Text *Graphics::newText(graphics::Font *font, const std::vector<Font::ColoredString> &text)

+ 1 - 2
src/modules/graphics/Graphics.h

@@ -442,10 +442,9 @@ public:
 	virtual Buffer *newBuffer(const Buffer::Settings &settings, const std::vector<Buffer::DataDeclaration> &format, const void *data, size_t size, size_t arraylength) = 0;
 	virtual Buffer *newBuffer(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength);
 
-	Mesh *newMesh(const std::vector<Vertex> &vertices, PrimitiveType drawmode, BufferUsage usage);
-	Mesh *newMesh(int vertexcount, PrimitiveType drawmode, BufferUsage usage);
 	Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage);
 	Mesh *newMesh(const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage);
+	Mesh *newMesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode);
 
 	Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
 

+ 100 - 54
src/modules/graphics/Mesh.cpp

@@ -34,27 +34,13 @@ namespace love
 namespace graphics
 {
 
-static const char *getBuiltinAttribName(BuiltinVertexAttribute attribid)
-{
-	const char *name = "";
-	getConstant(attribid, name);
-	return name;
-}
-
 static_assert(offsetof(Vertex, x) == sizeof(float) * 0, "Incorrect position offset in Vertex struct");
 static_assert(offsetof(Vertex, s) == sizeof(float) * 2, "Incorrect texture coordinate offset in Vertex struct");
 static_assert(offsetof(Vertex, color.r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");
 
 std::vector<Buffer::DataDeclaration> Mesh::getDefaultVertexFormat()
 {
-	// Corresponds to the love::Vertex struct.
-	std::vector<Buffer::DataDeclaration> vertexformat = {
-		{ getBuiltinAttribName(ATTRIB_POS),      DATAFORMAT_FLOAT_VEC2,  0 },
-		{ getBuiltinAttribName(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2,  0 },
-		{ getBuiltinAttribName(ATTRIB_COLOR),    DATAFORMAT_UNORM8_VEC4, 0 },
-	};
-
-	return vertexformat;
+	return Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
 }
 
 love::Type Mesh::type("Mesh", &Drawable::type);
@@ -63,6 +49,7 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &
 	: vertexBuffer(nullptr)
 	, vertexCount(0)
 	, vertexStride(0)
+	, vertexScratchBuffer(nullptr)
 	, indexBuffer(nullptr)
 	, useIndexBuffer(false)
 	, indexCount(0)
@@ -89,6 +76,7 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &
 	: vertexBuffer(nullptr)
 	, vertexCount((size_t) vertexcount)
 	, vertexStride(0)
+	, vertexScratchBuffer(nullptr)
 	, indexBuffer(nullptr)
 	, useIndexBuffer(false)
 	, indexCount(0)
@@ -115,6 +103,37 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &
 	vertexScratchBuffer = new char[vertexStride];
 }
 
+Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
+	: vertexBuffer(nullptr)
+	, vertexCount(0)
+	, vertexStride(0)
+	, vertexScratchBuffer(nullptr)
+	, indexBuffer(nullptr)
+	, useIndexBuffer(false)
+	, indexCount(0)
+	, indexDataType(INDEX_UINT16)
+	, primitiveType(drawmode)
+	, rangeStart(-1)
+	, rangeCount(-1)
+{
+	if (attributes.size() == 0)
+		throw love::Exception("At least one buffer attribute must be specified in this constructor.");
+
+	attachedAttributes = attributes;
+
+	vertexCount = LOVE_UINT32_MAX;
+
+	for (const auto &attrib : attachedAttributes)
+	{
+		if (getAttachedAttributeIndex(attrib.name) != -1)
+			throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str());
+
+		vertexCount = std::min(vertexCount, attrib.buffer->getArrayLength());
+	}
+
+	indexDataType = getIndexDataTypeFromMax(vertexCount);
+}
+
 Mesh::~Mesh()
 {
 	delete vertexScratchBuffer;
@@ -126,11 +145,22 @@ void Mesh::setupAttachedAttributes()
 	{
 		const std::string &name = vertexFormat[i].decl.name;
 
-		if (attachedAttributes.find(name) != attachedAttributes.end())
+		if (getAttachedAttributeIndex(name) != -1)
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
-		attachedAttributes[name] = {vertexBuffer, (int) i, STEP_PER_VERTEX, true};
+		attachedAttributes.push_back({name, vertexBuffer, (int) i, STEP_PER_VERTEX, true});
+	}
+}
+
+int Mesh::getAttachedAttributeIndex(const std::string &name) const
+{
+	for (int i = 0; i < (int) attachedAttributes.size(); i++)
+	{
+		if (attachedAttributes[i].name == name)
+			return i;
 	}
+
+	return -1;
 }
 
 void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
@@ -138,6 +168,9 @@ void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize)
 	if (vertindex >= vertexCount)
 		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
 
+	if (vertexBuffer.get() == nullptr)
+		throw love::Exception("setVertex can only be called on a Mesh which owns its own vertex buffer.");
+
 	size_t offset = vertindex * vertexStride;
 	size_t size = std::min(datasize, vertexStride);
 
@@ -152,6 +185,9 @@ size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize)
 	if (vertindex >= vertexCount)
 		throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
 
+	if (vertexBuffer.get() == nullptr)
+		throw love::Exception("getVertex can only be called on a Mesh which owns its own vertex buffer.");
+
 	size_t offset = vertindex * vertexStride;
 	size_t size = std::min(datasize, vertexStride);
 
@@ -175,6 +211,9 @@ void Mesh::setVertexAttribute(size_t vertindex, int attribindex, const void *dat
 	if (attribindex >= (int) vertexFormat.size())
 		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
 
+	if (vertexBuffer.get() == nullptr)
+		throw love::Exception("setVertexAttribute can only be called on a Mesh which owns its own vertex buffer.");
+
 	const auto &member = vertexFormat[attribindex];
 
 	size_t offset = vertindex * vertexStride + member.offset;
@@ -194,6 +233,9 @@ size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, s
 	if (attribindex >= (int) vertexFormat.size())
 		throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1);
 
+	if (vertexBuffer.get() == nullptr)
+		throw love::Exception("getVertexAttribute can only be called on a Mesh which owns its own vertex buffer.");
+
 	const auto &member = vertexFormat[attribindex];
 
 	size_t offset = vertindex * vertexStride + member.offset;
@@ -239,39 +281,37 @@ int Mesh::getAttributeIndex(const std::string &name) const
 
 void Mesh::setAttributeEnabled(const std::string &name, bool enable)
 {
-	auto it = attachedAttributes.find(name);
-
-	if (it == attachedAttributes.end())
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
 		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
 
-	it->second.enabled = enable;
+	attachedAttributes[index].enabled = enable;
 }
 
 bool Mesh::isAttributeEnabled(const std::string &name) const
 {
-	const auto it = attachedAttributes.find(name);
-
-	if (it == attachedAttributes.end())
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
 		throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
 
-	return it->second.enabled;
+	return attachedAttributes[index].enabled;
 }
 
 void Mesh::attachAttribute(const std::string &name, Buffer *buffer, const std::string &attachname, AttributeStep step)
 {
 	if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0)
-		throw love::Exception("GraphicsBuffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
+		throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
 
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	if (step == STEP_PER_INSTANCE && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
 		throw love::Exception("Vertex attribute instancing is not supported on this system.");
 
-	AttachedAttribute oldattrib = {};
-	AttachedAttribute newattrib = {};
+	BufferAttribute oldattrib = {};
+	BufferAttribute newattrib = {};
 
-	auto it = attachedAttributes.find(name);
-	if (it != attachedAttributes.end())
-		oldattrib = it->second;
+	int oldindex = getAttachedAttributeIndex(name);
+	if (oldindex != -1)
+		oldattrib = attachedAttributes[oldindex];
 	else if (attachedAttributes.size() + 1 > VertexAttributes::MAX)
 		throw love::Exception("A maximum of %d attributes can be attached at once.", VertexAttributes::MAX);
 
@@ -283,42 +323,46 @@ void Mesh::attachAttribute(const std::string &name, Buffer *buffer, const std::s
 	if (newattrib.index < 0)
 		throw love::Exception("The specified vertex buffer does not have a vertex attribute named '%s'", attachname.c_str());
 
-	attachedAttributes[name] = newattrib;
+	if (oldindex != -1)
+		attachedAttributes[oldindex] = newattrib;
+	else
+		attachedAttributes.push_back(newattrib);
 }
 
 bool Mesh::detachAttribute(const std::string &name)
 {
-	auto it = attachedAttributes.find(name);
-
-	if (it != attachedAttributes.end())
-	{
-		attachedAttributes.erase(it);
+	int index = getAttachedAttributeIndex(name);
+	if (index == -1)
+		return false;
 
-		if (getAttributeIndex(name) != -1)
-			attachAttribute(name, vertexBuffer, name);
+	attachedAttributes.erase(attachedAttributes.begin() + index);
 
-		return true;
-	}
+	if (getAttributeIndex(name) != -1)
+		attachAttribute(name, vertexBuffer, name);
 
-	return false;
+	return true;
 }
 
 void *Mesh::mapVertexData()
 {
-	return vertexBuffer->map();
+	return vertexBuffer.get() != nullptr ? vertexBuffer->map() : nullptr;
 }
 
 void Mesh::unmapVertexData(size_t modifiedoffset, size_t modifiedsize)
 {
+	if (!vertexBuffer.get())
+		return;
+
 	vertexBuffer->setMappedRangeModified(modifiedoffset, modifiedsize);
 	vertexBuffer->unmap();
 }
 
 void Mesh::flush()
 {
-	vertexBuffer->unmap();
+	if (vertexBuffer.get())
+		vertexBuffer->unmap();
 
-	if (indexBuffer != nullptr)
+	if (indexBuffer.get())
 		indexBuffer->unmap();
 }
 
@@ -352,7 +396,8 @@ void Mesh::setVertexMap(const std::vector<uint32> &map)
 	if (indexBuffer.get() == nullptr || size > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, vertexBuffer->getUsage());
+		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
+		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage);
 		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
 	}
 
@@ -386,7 +431,8 @@ void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasiz
 	if (indexBuffer.get() == nullptr || datasize > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat)
 	{
 		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, vertexBuffer->getUsage());
+		auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC;
+		Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage);
 		indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
 	}
 
@@ -529,32 +575,32 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
 
 	for (const auto &attrib : attachedAttributes)
 	{
-		if (!attrib.second.enabled)
+		if (!attrib.enabled)
 			continue;
 
-		Buffer *buffer = attrib.second.buffer.get();
+		Buffer *buffer = attrib.buffer.get();
 		int attributeindex = -1;
 
 		// If the attribute is one of the LOVE-defined ones, use the constant
 		// attribute index for it, otherwise query the index from the shader.
 		BuiltinVertexAttribute builtinattrib;
-		if (getConstant(attrib.first.c_str(), builtinattrib))
+		if (getConstant(attrib.name.c_str(), builtinattrib))
 			attributeindex = (int) builtinattrib;
 		else if (Shader::current)
-			attributeindex = Shader::current->getVertexAttributeIndex(attrib.first);
+			attributeindex = Shader::current->getVertexAttributeIndex(attrib.name);
 
 		if (attributeindex >= 0)
 		{
 			// Make sure the buffer isn't mapped (sends data to GPU if needed.)
 			buffer->unmap();
 
-			const auto &member = buffer->getDataMember(attrib.second.index);
+			const auto &member = buffer->getDataMember(attrib.index);
 
-			uint16 offset = (uint16) buffer->getMemberOffset(attrib.second.index);
+			uint16 offset = (uint16) buffer->getMemberOffset(attrib.index);
 			uint16 stride = (uint16) buffer->getArrayStride();
 
 			attributes.set(attributeindex, member.decl.format, offset, activebuffers);
-			attributes.setBufferLayout(activebuffers, stride, attrib.second.step);
+			attributes.setBufferLayout(activebuffers, stride, attrib.step);
 
 			// TODO: Ideally we want to reuse buffers with the same stride+step.
 			buffers.set(activebuffers, buffer, 0);

+ 13 - 9
src/modules/graphics/Mesh.h

@@ -50,10 +50,20 @@ class Mesh : public Drawable
 {
 public:
 
+	struct BufferAttribute
+	{
+		std::string name;
+		StrongRef<Buffer> buffer;
+		int index;
+		AttributeStep step;
+		bool enabled;
+	};
+
 	static love::Type type;
 
 	Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage);
 	Mesh(Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage);
+	Mesh(const std::vector<BufferAttribute> &attributes, PrimitiveType drawmode);
 
 	virtual ~Mesh();
 
@@ -105,6 +115,7 @@ public:
 	 **/
 	void attachAttribute(const std::string &name, Buffer *buffer, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX);
 	bool detachAttribute(const std::string &name);
+	const std::vector<BufferAttribute> &getAttachedAttributes();
 
 	void *mapVertexData();
 	void unmapVertexData(size_t modifiedoffset = 0, size_t modifiedsize = -1);
@@ -172,19 +183,12 @@ private:
 
 	friend class SpriteBatch;
 
-	struct AttachedAttribute
-	{
-		StrongRef<Buffer> buffer;
-		int index;
-		AttributeStep step;
-		bool enabled;
-	};
-
 	void setupAttachedAttributes();
+	int getAttachedAttributeIndex(const std::string &name) const;
 
 	std::vector<Buffer::DataMember> vertexFormat;
 
-	std::unordered_map<std::string, AttachedAttribute> attachedAttributes;
+	std::vector<BufferAttribute> attachedAttributes;
 
 	// Vertex buffer, for the vertex data.
 	StrongRef<Buffer> vertexBuffer;

+ 0 - 8
src/modules/graphics/vertex.cpp

@@ -358,14 +358,6 @@ DEFINE_STRINGMAP_BEGIN(AttributeStep, STEP_MAX_ENUM, attributeStep)
 }
 DEFINE_STRINGMAP_END(AttributeStep, STEP_MAX_ENUM, attributeStep)
 
-DEFINE_STRINGMAP_BEGIN(DataTypeDeprecated, DATADEPRECATED_MAX_ENUM, dataType)
-{
-	{ "unorm8",  DATADEPRECATED_UNORM8  },
-	{ "unorm16", DATADEPRECATED_UNORM16 },
-	{ "float",   DATADEPRECATED_FLOAT   },
-}
-DEFINE_STRINGMAP_END(DataTypeDeprecated, DATADEPRECATED_MAX_ENUM, dataType)
-
 DEFINE_STRINGMAP_BEGIN(DataFormat, DATAFORMAT_MAX_ENUM, dataFormat)
 {
 	{ "float",     DATAFORMAT_FLOAT      },

+ 0 - 9
src/modules/graphics/vertex.h

@@ -102,14 +102,6 @@ enum BufferUsage
 	BUFFERUSAGE_MAX_ENUM
 };
 
-enum DataTypeDeprecated
-{
-	DATADEPRECATED_UNORM8,
-	DATADEPRECATED_UNORM16,
-	DATADEPRECATED_FLOAT,
-	DATADEPRECATED_MAX_ENUM
-};
-
 // Value types used when interfacing with the GPU (vertex and shader data).
 // The order of this enum affects the dataFormatInfo array.
 enum DataFormat
@@ -387,7 +379,6 @@ DECLARE_STRINGMAP(IndexDataType);
 DECLARE_STRINGMAP(BufferUsage);
 DECLARE_STRINGMAP(PrimitiveType);
 DECLARE_STRINGMAP(AttributeStep);
-DECLARE_STRINGMAP(DataTypeDeprecated);
 DECLARE_STRINGMAP(DataFormat);
 DECLARE_STRINGMAP(DataBaseType);
 DECLARE_STRINGMAP(CullMode);

+ 33 - 14
src/modules/graphics/wrap_Graphics.cpp

@@ -1557,6 +1557,8 @@ static Mesh *newStandardMesh(lua_State *L)
 	PrimitiveType drawmode = luax_optmeshdrawmode(L, 2, PRIMITIVE_TRIANGLE_FAN);
 	BufferUsage usage = luax_optmeshusage(L, 3, BUFFERUSAGE_DYNAMIC);
 
+	auto format = Mesh::getDefaultVertexFormat();
+
 	// First argument is a table of standard vertices, or the number of
 	// standard vertices.
 	if (lua_istable(L, 1))
@@ -1595,12 +1597,12 @@ static Mesh *newStandardMesh(lua_State *L)
 			vertices.push_back(v);
 		}
 
-		luax_catchexcept(L, [&](){ t = instance()->newMesh(vertices, drawmode, usage); });
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(format, vertices.data(), vertices.size() * sizeof(Vertex), drawmode, usage); });
 	}
 	else
 	{
 		int count = (int) luaL_checkinteger(L, 1);
-		luax_catchexcept(L, [&](){ t = instance()->newMesh(count, drawmode, usage); });
+		luax_catchexcept(L, [&](){ t = instance()->newMesh(format, count, drawmode, usage); });
 	}
 
 	return t;
@@ -1641,24 +1643,41 @@ static Mesh *newCustomMesh(lua_State *L)
 
 		if (!getConstant(tname, format))
 		{
-			DataTypeDeprecated legacyType = DATADEPRECATED_FLOAT;
+			int components = (int) luaL_checkinteger(L, -1);
 
-			if (strcmp(tname, "byte") == 0) // Legacy name.
-				legacyType = DATADEPRECATED_UNORM8;
-			else if (!getConstant(tname, legacyType))
+			// Check deprecated format names.
+			if (strcmp(tname, "byte") == 0 || strcmp(tname, "unorm8") == 0)
 			{
-				luax_enumerror(L, "Mesh vertex data format name", getConstants(format), tname);
-				return nullptr;
+				if (components == 4)
+					format = DATAFORMAT_UNORM8_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
 			}
+			else if (strcmp(tname, "unorm16") == 0)
+			{
+				if (components == 2)
+					format = DATAFORMAT_UNORM16_VEC2;
+				else if (components == 4)
+					format = DATAFORMAT_UNORM16_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
 
-			int components = (int) luaL_checkinteger(L, -1);
-			if (components <= 0 || components > 4)
+			}
+			else if (strcmp(tname, "float") == 0)
 			{
-				luaL_error(L, "Number of vertex attribute components must be between 1 and 4 (got %d)", components);
-				return nullptr;
+				if (components == 1)
+					format = DATAFORMAT_FLOAT;
+				else if (components == 2)
+					format = DATAFORMAT_FLOAT_VEC2;
+				else if (components == 3)
+					format = DATAFORMAT_FLOAT_VEC3;
+				else if (components == 4)
+					format = DATAFORMAT_FLOAT_VEC4;
+				else
+					luaL_error(L, "Invalid component count (%d) for vertex data type %s", components, tname);
 			}
-
-			// TODO: convert legacy type+components to new format enum.
+			else
+				luax_enumerror(L, "vertex data format", getConstants(format), tname);
 		}
 
 		lua_pop(L, 4);