Browse Source

Added SpriteBatch:attachAttribute(attributename, mesh), which lets SpriteBatches use per-vertex information from a vertex attribute in a Mesh when drawing.

Each sprite in a SpriteBatch has 4 vertices in the following order: top-left, bottom-left, top-right, bottom-right. The index returned by SpriteBatch:add (and used by SpriteBatch:set) can be multiplied by 4 to determine the first vertex in a specific sprite.
Alex Szpakowski 9 years ago
parent
commit
3c1c13793e

+ 3 - 0
src/modules/graphics/opengl/GLBuffer.cpp

@@ -90,6 +90,9 @@ void *GLBuffer::map()
 
 
 void GLBuffer::unmapStatic(size_t offset, size_t size)
 void GLBuffer::unmapStatic(size_t offset, size_t size)
 {
 {
+	if (size == 0)
+		return;
+
 	// Upload the mapped data to the buffer.
 	// Upload the mapped data to the buffer.
 	glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, memory_map + offset);
 	glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, memory_map + offset);
 }
 }

+ 50 - 42
src/modules/graphics/opengl/Mesh.cpp

@@ -149,7 +149,7 @@ void Mesh::setupAttachedAttributes()
 		if (attachedAttributes.find(name) != attachedAttributes.end())
 		if (attachedAttributes.find(name) != attachedAttributes.end())
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
 
-		attachedAttributes[name] = {this, i, true};
+		attachedAttributes[name] = {this, (int) i, true};
 	}
 	}
 }
 }
 
 
@@ -287,6 +287,17 @@ Mesh::DataType Mesh::getAttributeInfo(int attribindex, int &components) const
 	return type;
 	return type;
 }
 }
 
 
+int Mesh::getAttributeIndex(const std::string &name) const
+{
+	for (int i = 0; i < (int) vertexFormat.size(); i++)
+	{
+		if (vertexFormat[i].name == name)
+			return i;
+	}
+
+	return -1;
+}
+
 void Mesh::setAttributeEnabled(const std::string &name, bool enable)
 void Mesh::setAttributeEnabled(const std::string &name, bool enable)
 {
 {
 	auto it = attachedAttributes.find(name);
 	auto it = attachedAttributes.find(name);
@@ -328,20 +339,10 @@ void Mesh::attachAttribute(const std::string &name, Mesh *mesh)
 		oldattrib = it->second;
 		oldattrib = it->second;
 
 
 	newattrib.mesh = mesh;
 	newattrib.mesh = mesh;
-	newattrib.index = std::numeric_limits<size_t>::max();
 	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
 	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
+	newattrib.index = mesh->getAttributeIndex(name);
 
 
-	// 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())
+	if (newattrib.index < 0)
 		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
 		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
 
 
 	if (newattrib.mesh != this)
 	if (newattrib.mesh != this)
@@ -537,6 +538,39 @@ void Mesh::getDrawRange(int &min, int &max) const
 	max = rangeMax;
 	max = rangeMax;
 }
 }
 
 
+int Mesh::bindAttributeToShaderInput(int attributeindex, const std::string &inputname)
+{
+	const AttribFormat &format = vertexFormat[attributeindex];
+
+	GLint attriblocation = -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.
+	VertexAttribID builtinattrib;
+	if (Shader::getConstant(inputname.c_str(), builtinattrib))
+		attriblocation = (GLint) builtinattrib;
+	else if (Shader::current)
+		attriblocation = Shader::current->getAttribLocation(inputname);
+
+	// The active shader might not use this vertex attribute name.
+	if (attriblocation < 0)
+		return attriblocation;
+
+	// Needed for unmap and glVertexAttribPointer.
+	GLBuffer::Bind vbobind(*vbo);
+
+	// Make sure the buffer isn't mapped (sends data to GPU if needed.)
+	vbo->unmap();
+
+	const void *gloffset = vbo->getPointer(getAttributeOffset(attributeindex));
+	GLenum datatype = getGLDataType(format.type);
+	GLboolean normalized = (datatype == GL_UNSIGNED_BYTE);
+
+	glVertexAttribPointer(attriblocation, format.components, datatype, normalized, vertexStride, gloffset);
+
+	return attriblocation;
+}
+
 void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 {
 {
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
@@ -549,36 +583,10 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo
 			continue;
 			continue;
 
 
 		Mesh *mesh = attrib.second.mesh;
 		Mesh *mesh = attrib.second.mesh;
-		const AttribFormat &format = mesh->vertexFormat[attrib.second.index];
-
-		GLint attriblocation = -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.
-		VertexAttribID builtinattrib;
-		if (Shader::getConstant(format.name.c_str(), builtinattrib))
-			attriblocation = (GLint) builtinattrib;
-		else if (Shader::current)
-			attriblocation = Shader::current->getAttribLocation(format.name);
-
-		// The active shader might not use this vertex attribute name.
-		if (attriblocation < 0)
-			continue;
-
-		// Needed for unmap and glVertexAttribPointer.
-		GLBuffer::Bind vbobind(*mesh->vbo);
-
-		// Make sure the buffer isn't mapped (sends data to GPU if needed.)
-		mesh->vbo->unmap();
-
-		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(attriblocation, format.components, datatype, normalized, mesh->vertexStride, gloffset);
+		int location = mesh->bindAttributeToShaderInput(attrib.second.index, attrib.first);
 
 
-		enabledattribs |= 1 << uint32(attriblocation);
+		if (location >= 0)
+			enabledattribs |= 1u << (uint32) location;
 	}
 	}
 
 
 	// Not supported on all platforms or GL versions, I believe.
 	// Not supported on all platforms or GL versions, I believe.

+ 4 - 1
src/modules/graphics/opengl/Mesh.h

@@ -126,6 +126,7 @@ public:
 	 **/
 	 **/
 	const std::vector<AttribFormat> &getVertexFormat() const;
 	const std::vector<AttribFormat> &getVertexFormat() const;
 	DataType getAttributeInfo(int attribindex, int &components) const;
 	DataType getAttributeInfo(int attribindex, int &components) const;
+	int getAttributeIndex(const std::string &name) const;
 
 
 	/**
 	/**
 	 * Sets whether a specific vertex attribute is used when drawing the Mesh.
 	 * Sets whether a specific vertex attribute is used when drawing the Mesh.
@@ -193,6 +194,8 @@ public:
 	void setDrawRange();
 	void setDrawRange();
 	void getDrawRange(int &min, int &max) const;
 	void getDrawRange(int &min, int &max) const;
 
 
+	int bindAttributeToShaderInput(int attributeindex, const std::string &inputname);
+
 	// Implements Drawable.
 	// Implements Drawable.
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) override;
 	void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) override;
 
 
@@ -212,7 +215,7 @@ private:
 	struct AttachedAttribute
 	struct AttachedAttribute
 	{
 	{
 		Mesh *mesh;
 		Mesh *mesh;
-		size_t index;
+		int index;
 		bool enabled;
 		bool enabled;
 	};
 	};
 
 

+ 54 - 12
src/modules/graphics/opengl/SpriteBatch.cpp

@@ -193,6 +193,28 @@ int SpriteBatch::getBufferSize() const
 	return size;
 	return size;
 }
 }
 
 
+void SpriteBatch::attachAttribute(const std::string &name, Mesh *mesh)
+{
+	AttachedAttribute oldattrib = {};
+	AttachedAttribute newattrib = {};
+
+	if (mesh->getVertexCount() < (size_t) getBufferSize() * 4)
+		throw love::Exception("Mesh has too few vertices to be attached to this SpriteBatch (at least %d vertices are required)", getBufferSize()*4);
+
+	auto it = attached_attributes.find(name);
+	if (it != attached_attributes.end())
+		oldattrib = it->second;
+
+	newattrib.index = mesh->getAttributeIndex(name);
+
+	if (newattrib.index < 0)
+		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str());
+
+	newattrib.mesh = mesh;
+
+	attached_attributes[name] = newattrib;
+}
+
 void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
 void SpriteBatch::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 pos_offset   = offsetof(Vertex, x);
@@ -209,27 +231,47 @@ void SpriteBatch::draw(float x, float y, float angle, float sx, float sy, float
 
 
 	gl.bindTexture(*(GLuint *) texture->getHandle());
 	gl.bindTexture(*(GLuint *) texture->getHandle());
 
 
-	GLBuffer::Bind array_bind(*array_buf);
-	GLBuffer::Bind element_bind(*quad_indices.getBuffer());
-
-	// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
-	array_buf->unmap();
-
 	uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
 	uint32 enabledattribs = ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD;
 
 
-	// Apply per-sprite color, if a color is set.
-	if (color)
 	{
 	{
-		enabledattribs |= ATTRIBFLAG_COLOR;
-		glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), array_buf->getPointer(color_offset));
+		// Scope this bind so it doesn't interfere with the
+		// Mesh::bindAttributeToShaderInput calls below.
+		GLBuffer::Bind array_bind(*array_buf);
+
+		// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
+		array_buf->unmap();
+
+		// Apply per-sprite color, if a color is set.
+		if (color)
+		{
+			enabledattribs |= ATTRIBFLAG_COLOR;
+			glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), array_buf->getPointer(color_offset));
+		}
+
+		glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), array_buf->getPointer(pos_offset));
+		glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), array_buf->getPointer(texel_offset));
 	}
 	}
 
 
-	glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), array_buf->getPointer(pos_offset));
-	glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), array_buf->getPointer(texel_offset));
+	for (const auto &it : attached_attributes)
+	{
+		Mesh *mesh = it.second.mesh.get();
+
+		// We have to do this check here as well because setBufferSize can be
+		// called after attachAttribute.
+		if (mesh->getVertexCount() < (size_t) getBufferSize() * 4)
+			throw love::Exception("Mesh with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str());
+
+		int location = mesh->bindAttributeToShaderInput(it.second.index, it.first);
+
+		if (location >= 0)
+			enabledattribs |= 1u << (uint32) location;
+	}
 
 
 	gl.useVertexAttribArrays(enabledattribs);
 	gl.useVertexAttribArrays(enabledattribs);
 
 
 	gl.prepareDraw();
 	gl.prepareDraw();
+
+	GLBuffer::Bind element_bind(*quad_indices.getBuffer());
 	gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(next), quad_indices.getType(), quad_indices.getPointer(0));
 	gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(next), quad_indices.getType(), quad_indices.getPointer(0));
 }
 }
 
 

+ 17 - 0
src/modules/graphics/opengl/SpriteBatch.h

@@ -24,6 +24,9 @@
 // C
 // C
 #include <cstring>
 #include <cstring>
 
 
+// C++
+#include <unordered_map>
+
 // LOVE
 // LOVE
 #include "common/math.h"
 #include "common/math.h"
 #include "common/Matrix.h"
 #include "common/Matrix.h"
@@ -98,11 +101,23 @@ public:
 	 **/
 	 **/
 	int getBufferSize() const;
 	int getBufferSize() const;
 
 
+	/**
+	 * Attaches a specific vertex attribute from a Mesh to this SpriteBatch.
+	 * The vertex attribute will be used when drawing the SpriteBatch.
+	 **/
+	void attachAttribute(const std::string &name, Mesh *mesh);
+
 	// 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);
 
 
 private:
 private:
 
 
+	struct AttachedAttribute
+	{
+		StrongRef<Mesh> mesh;
+		int index;
+	};
+
 	void addv(const Vertex *v, const Matrix3 &m, int index);
 	void addv(const Vertex *v, const Matrix3 &m, int index);
 
 
 	/**
 	/**
@@ -129,6 +144,8 @@ private:
 	GLBuffer *array_buf;
 	GLBuffer *array_buf;
 	QuadIndices quad_indices;
 	QuadIndices quad_indices;
 
 
+	std::unordered_map<std::string, AttachedAttribute> attached_attributes;
+
 }; // SpriteBatch
 }; // SpriteBatch
 
 
 } // opengl
 } // opengl

+ 11 - 0
src/modules/graphics/opengl/wrap_SpriteBatch.cpp

@@ -203,6 +203,16 @@ int w_SpriteBatch_getBufferSize(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_SpriteBatch_attachAttribute(lua_State *L)
+{
+	SpriteBatch *t = luax_checkspritebatch(L, 1);
+	const char *name = luaL_checkstring(L, 2);
+	Mesh *m = luax_checktype<Mesh>(L, 3, GRAPHICS_MESH_ID);
+
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, m); });
+	return 0;
+}
+
 static const luaL_Reg functions[] =
 static const luaL_Reg functions[] =
 {
 {
 	{ "add", w_SpriteBatch_add },
 	{ "add", w_SpriteBatch_add },
@@ -216,6 +226,7 @@ static const luaL_Reg functions[] =
 	{ "getCount", w_SpriteBatch_getCount },
 	{ "getCount", w_SpriteBatch_getCount },
 	{ "setBufferSize", w_SpriteBatch_setBufferSize },
 	{ "setBufferSize", w_SpriteBatch_setBufferSize },
 	{ "getBufferSize", w_SpriteBatch_getBufferSize },
 	{ "getBufferSize", w_SpriteBatch_getBufferSize },
+	{ "attachAttribute", w_SpriteBatch_attachAttribute },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 1 - 0
src/modules/graphics/opengl/wrap_SpriteBatch.h

@@ -43,6 +43,7 @@ int w_SpriteBatch_getColor(lua_State *L);
 int w_SpriteBatch_getCount(lua_State *L);
 int w_SpriteBatch_getCount(lua_State *L);
 int w_SpriteBatch_setBufferSize(lua_State *L);
 int w_SpriteBatch_setBufferSize(lua_State *L);
 int w_SpriteBatch_getBufferSize(lua_State *L);
 int w_SpriteBatch_getBufferSize(lua_State *L);
+int w_SpriteBatch_attachAttribute(lua_State *L);
 
 
 extern "C" int luaopen_spritebatch(lua_State *L);
 extern "C" int luaopen_spritebatch(lua_State *L);