Browse Source

Add love.graphics.newMesh variant to create a Mesh from existing Buffers.

This allows a Mesh to be created that doesn't have its own internal vertex buffer and purely references other vertex buffers instead.

The prototype looks like this:
mesh = love.graphics.newMesh(attributelist, drawmode)

where attributelist is an array of tables each with the following fields, similar to Mesh:attachAttribute:
{
    buffer = vertexbuffer,
    name = "VertexPosition", -- the name this vertex attribute will use in a shader
    nameinbuffer = nil, -- the name of the attribute in the vertex buffer. Defaults to the name field.
    step = nil, -- vertex attribute step ("pervertex" or "perinstance"), defaults to "pervertex".
    startindex = nil, -- 1-based array index within the given vertex buffer where the attribute data will start being pulled from during rendering. Defaults to 1.
}
Sasha Szpakowski 1 year ago
parent
commit
dbc7d7f3ca
3 changed files with 109 additions and 16 deletions
  1. 31 15
      src/modules/graphics/Mesh.cpp
  2. 2 0
      src/modules/graphics/Mesh.h
  3. 76 1
      src/modules/graphics/wrap_Graphics.cpp

+ 31 - 15
src/modules/graphics/Mesh.cpp

@@ -107,15 +107,18 @@ Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType d
 		throw love::Exception("At least one buffer attribute must be specified in this constructor.");
 
 	attachedAttributes = attributes;
-
 	vertexCount = attachedAttributes.size() > 0 ? LOVE_UINT32_MAX : 0;
 
-	for (const auto &attrib : attachedAttributes)
+	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+
+	for (int i = 0; i < (int) attachedAttributes.size(); i++)
 	{
-		if ((attrib.buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0)
-			throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
+		auto &attrib = attachedAttributes[i];
 
-		if (getAttachedAttributeIndex(attrib.name) != -1)
+		finalizeAttribute(gfx, attrib);
+
+		int attributeIndex = getAttachedAttributeIndex(attrib.name);
+		if (attributeIndex != i && attributeIndex != -1)
 			throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str());
 
 		vertexCount = std::min(vertexCount, attrib.buffer->getArrayLength());
@@ -140,7 +143,7 @@ void Mesh::setupAttachedAttributes()
 		if (getAttachedAttributeIndex(name) != -1)
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
-		attachedAttributes.push_back({name, vertexBuffer, nullptr, (int) i, 0, STEP_PER_VERTEX, true});
+		attachedAttributes.push_back({name, vertexBuffer, nullptr, name, (int) i, 0, STEP_PER_VERTEX, true});
 	}
 }
 
@@ -155,6 +158,24 @@ int Mesh::getAttachedAttributeIndex(const std::string &name) const
 	return -1;
 }
 
+void Mesh::finalizeAttribute(Graphics *gfx, BufferAttribute &attrib) const
+{
+	if ((attrib.buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0)
+		throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
+
+	if (attrib.step == STEP_PER_INSTANCE && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
+		throw love::Exception("Vertex attribute instancing is not supported on this system.");
+
+	if (attrib.startArrayIndex < 0 || attrib.startArrayIndex >= (int)attrib.buffer->getArrayLength())
+		throw love::Exception("Invalid start array index %d.", attrib.startArrayIndex + 1);
+
+	int indexInBuffer = attrib.buffer->getDataMemberIndex(attrib.nameInBuffer);
+	if (indexInBuffer < 0)
+		throw love::Exception("Buffer does not have a vertex attribute with name '%s'.", attrib.nameInBuffer.c_str());
+
+	attrib.indexInBuffer = indexInBuffer;
+}
+
 void *Mesh::checkVertexDataOffset(size_t vertindex, size_t *byteoffset)
 {
 	if (vertindex >= vertexCount)
@@ -210,15 +231,7 @@ bool Mesh::isAttributeEnabled(const std::string &name) const
 
 void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, int startindex, AttributeStep step)
 {
-	if ((buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0)
-		throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
-
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-	if (step == STEP_PER_INSTANCE && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
-		throw love::Exception("Vertex attribute instancing is not supported on this system.");
-
-	if (startindex < 0 || startindex >= (int) buffer->getArrayLength())
-		throw love::Exception("Invalid start array index %d.", startindex + 1);
 
 	BufferAttribute oldattrib = {};
 	BufferAttribute newattrib = {};
@@ -233,10 +246,13 @@ void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh,
 	newattrib.buffer = buffer;
 	newattrib.mesh = mesh;
 	newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true;
-	newattrib.indexInBuffer = buffer->getDataMemberIndex(attachname);
+	newattrib.nameInBuffer = attachname;
+	newattrib.indexInBuffer = -1;
 	newattrib.startArrayIndex = startindex;
 	newattrib.step = step;
 
+	finalizeAttribute(gfx, newattrib);
+
 	if (newattrib.indexInBuffer < 0)
 		throw love::Exception("The specified vertex buffer does not have a vertex attribute named '%s'", attachname.c_str());
 

+ 2 - 0
src/modules/graphics/Mesh.h

@@ -56,6 +56,7 @@ public:
 		std::string name;
 		StrongRef<Buffer> buffer;
 		StrongRef<Mesh> mesh;
+		std::string nameInBuffer;
 		int indexInBuffer;
 		int startArrayIndex;
 		AttributeStep step;
@@ -186,6 +187,7 @@ private:
 
 	void setupAttachedAttributes();
 	int getAttachedAttributeIndex(const std::string &name) const;
+	void finalizeAttribute(Graphics *gfx, BufferAttribute &attrib) const;
 
 	void drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buffer *indirectargs, int argsindex);
 

+ 76 - 1
src/modules/graphics/wrap_Graphics.cpp

@@ -1990,6 +1990,79 @@ static Mesh *newCustomMesh(lua_State *L)
 	return t;
 }
 
+static bool luax_isbufferattributetable(lua_State* L, int idx)
+{
+	if (lua_type(L, idx) != LUA_TTABLE)
+		return false;
+
+	lua_rawgeti(L, idx, 1);
+	if (lua_type(L, -1) != LUA_TTABLE)
+	{
+		lua_pop(L, 1);
+		return false;
+	}
+
+	lua_getfield(L, -1, "buffer");
+	bool isbuffer = luax_istype(L, -1, Buffer::type);
+	lua_pop(L, 2);
+	return isbuffer;
+}
+
+static Mesh::BufferAttribute luax_checkbufferattributetable(lua_State *L, int idx)
+{
+	Mesh::BufferAttribute attrib = {};
+
+	attrib.step = STEP_PER_VERTEX;
+	attrib.enabled = true;
+
+	lua_getfield(L, idx, "buffer");
+	attrib.buffer = luax_checkbuffer(L, -1);
+	lua_pop(L, 1);
+
+	lua_getfield(L, idx, "name");
+	attrib.name = luax_checkstring(L, -1);
+	lua_pop(L, 1);
+
+	lua_getfield(L, idx, "step");
+	if (!lua_isnoneornil(L, -1))
+	{
+		const char *stepstr = luaL_checkstring(L, -1);
+		if (!getConstant(stepstr, attrib.step))
+			luax_enumerror(L, "vertex attribute step", getConstants(attrib.step), stepstr);
+	}
+	lua_pop(L, 1);
+
+	lua_getfield(L, idx, "nameinbuffer");	
+	if (!lua_isnoneornil(L, -1))
+		attrib.nameInBuffer = luax_checkstring(L, -1);
+	else
+		attrib.nameInBuffer = attrib.name;
+	lua_pop(L, 1);
+
+	lua_getfield(L, idx, "startindex");
+	attrib.startArrayIndex = (int) luaL_optinteger(L, -1, 1) - 1;
+	lua_pop(L, 1);
+
+	return attrib;
+}
+
+static Mesh* newMeshFromBuffers(lua_State *L)
+{
+	std::vector<Mesh::BufferAttribute> attributes;
+	for (size_t i = 1; i <= luax_objlen(L, 1); i++)
+	{
+		lua_rawgeti(L, 1, i);
+		attributes.push_back(luax_checkbufferattributetable(L, -1));
+		lua_pop(L, 1);
+	}
+
+	PrimitiveType drawmode = luax_checkmeshdrawmode(L, 2);
+
+	Mesh *t = nullptr;
+	luax_catchexcept(L, [&]() { t = instance()->newMesh(attributes, drawmode); });
+	return t;
+}
+
 int w_newMesh(lua_State *L)
 {
 	luax_checkgraphicscreated(L);
@@ -2002,7 +2075,9 @@ int w_newMesh(lua_State *L)
 	Mesh *t = nullptr;
 
 	int arg2type = lua_type(L, 2);
-	if (arg1type == LUA_TTABLE && (arg2type == LUA_TTABLE || arg2type == LUA_TNUMBER || arg2type == LUA_TUSERDATA))
+	if (luax_isbufferattributetable(L, 1))
+		t = newMeshFromBuffers(L);
+	else if (arg1type == LUA_TTABLE && (arg2type == LUA_TTABLE || arg2type == LUA_TNUMBER || arg2type == LUA_TUSERDATA))
 		t = newCustomMesh(L);
 	else
 		t = newStandardMesh(L);