Commits

Alex Szpakowski committed c091822

Added instancing support to Meshes via Mesh:setInstanceCount. Added a new built-in variable to vertex shaders: int love_InstanceID.

love.graphics.draw(mesh) will draw the mesh instancecount times, using hardware instancing when available. The only way to draw individual instances differently from each other is to use the love_InstanceID variable in a vertex shader.

Added love.graphics.isSupported(“instancing”). Hardware instancing is supported if true, otherwise a (slower) pseudo-instancing fallback is used internally when drawing instanced meshes.

  • Participants
  • Parent commits e34c7a9

Comments (0)

Files changed (13)

File src/modules/graphics/Graphics.cpp

 	{ "mipmap", Graphics::SUPPORT_MIPMAP },
 	{ "dxt", Graphics::SUPPORT_DXT },
 	{ "bc5", Graphics::SUPPORT_BC5 },
+	{ "instancing", Graphics::SUPPORT_INSTANCING },
 };
 
 StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM> Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries));

File src/modules/graphics/Graphics.h

 		SUPPORT_MIPMAP,
 		SUPPORT_DXT,
 		SUPPORT_BC5,
+		SUPPORT_INSTANCING,
 		SUPPORT_MAX_ENUM
 	};
 

File src/modules/graphics/opengl/Mesh.cpp

 	, vertex_count(0)
 	, ibo(nullptr)
 	, element_count(0)
+	, instance_count(1)
 	, draw_mode(mode)
 	, texture(nullptr)
 	, colors_enabled(false)
 
 void Mesh::setVertices(const std::vector<Vertex> &verts)
 {
-	if (verts.size() < 3)
-		throw love::Exception("At least 3 vertices are required.");
+	if (verts.size() == 0)
+		throw love::Exception("At least one vertex is required.");
 
 	size_t size = sizeof(Vertex) * verts.size();
 
 	return element_count;
 }
 
+void Mesh::setInstanceCount(int count)
+{
+	if (count < 1)
+		count = 1;
+
+	instance_count = count;
+}
+
+int Mesh::getInstanceCount() const
+{
+	return instance_count;
+}
+
 void Mesh::setTexture(Texture *tex)
 {
 	tex->retain();
 
 	if (ibo && element_count > 0)
 	{
+		// Use the custom vertex map (index buffer) to draw the vertices.
 		VertexBuffer::Bind ibo_bind(*ibo);
 
 		// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
 		ibo->unmap();
 
-		// Use the custom vertex map to draw the vertices.
-		glDrawElements(mode, element_count, GL_UNSIGNED_INT, ibo->getPointer(0));
+		const void *indices = ibo->getPointer(0);
+		const GLenum type = GL_UNSIGNED_INT;
+
+		if (instance_count > 1)
+			gl.drawElementsInstanced(mode, element_count, type, indices, instance_count);
+		else
+			glDrawElements(mode, element_count, type, indices);
 	}
 	else
 	{
 		// Normal non-indexed drawing (no custom vertex map.)
-		glDrawArrays(mode, 0, vertex_count);
+		if (instance_count > 1)
+			gl.drawArraysInstanced(mode, 0, vertex_count, instance_count);
+		else
+			glDrawArrays(mode, 0, vertex_count);
 	}
 
 	if (wireframe)

File src/modules/graphics/opengl/Mesh.h

 	size_t getVertexMapCount() const;
 
 	/**
+	 * Sets the number of instances of this Mesh to draw (uses hardware
+	 * instancing when possible.)
+	 * A custom vertex shader is necessary in order to introduce differences
+	 * in each instance.
+	 **/
+	void setInstanceCount(int count);
+	int getInstanceCount() const;
+
+	/**
 	 * Sets the texture used when drawing the Mesh.
 	 **/
 	void setTexture(Texture *texture);
 	VertexBuffer *ibo;
 	size_t element_count;
 
+	int instance_count;
+
 	DrawMode draw_mode;
 
 	Texture *texture;

File src/modules/graphics/opengl/OpenGL.cpp

 	initMaxValues();
 	createDefaultTexture();
 
+	state.lastPseudoInstanceID = -1;
+
 	contextInitialized = true;
 }
 
 
 void OpenGL::prepareDraw()
 {
-	// Make sure the active shader has the correct values for the built-in
-	// screen params uniform.
-	if (Shader::current)
-		Shader::current->checkSetScreenParams();
+	Shader *shader = Shader::current;
+	if (shader != nullptr)
+	{
+		// Make sure the active shader has the correct values for its
+		// love-provided uniforms.
+		shader->checkSetScreenParams();
+
+		// Make sure the Instance ID variable is up-to-date when
+		// pseudo-instancing is used.
+		if (state.lastPseudoInstanceID != 0 && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID))
+		{
+			glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, 0.0f);
+			state.lastPseudoInstanceID = 0;
+		}
+	}
+}
+
+void OpenGL::drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
+{
+	Shader *shader = Shader::current;
+
+	if (GLEE_ARB_draw_instanced)
+		glDrawArraysInstancedARB(mode, first, count, primcount);
+	else
+	{
+		bool shaderHasID = shader && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID);
+
+		// Pseudo-instancing fallback.
+		for (int i = 0; i < primcount; i++)
+		{
+			if (shaderHasID)
+				glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, (GLfloat) i);
+
+			glDrawArrays(mode, first, count);
+		}
+
+		if (shaderHasID)
+			state.lastPseudoInstanceID = primcount - 1;
+	}
+}
+
+void OpenGL::drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount)
+{
+	Shader *shader = Shader::current;
+
+	if (GLEE_ARB_draw_instanced)
+		glDrawElementsInstancedARB(mode, count, type, indices, primcount);
+	else
+	{
+		bool shaderHasID = shader && shader->hasVertexAttrib(ATTRIB_PSEUDO_INSTANCE_ID);
+
+		// Pseudo-instancing fallback.
+		for (int i = 0; i < primcount; i++)
+		{
+			if (shaderHasID)
+				glVertexAttrib1f((GLuint) ATTRIB_PSEUDO_INSTANCE_ID, (GLfloat) i);
+
+			glDrawElements(mode, count, type, indices);
+		}
+
+		if (shaderHasID)
+			state.lastPseudoInstanceID = primcount - 1;
+	}
 }
 
 void OpenGL::setColor(const Color &c)

File src/modules/graphics/opengl/OpenGL.h

 		VENDOR_UNKNOWN
 	};
 
+	// Vertex attributes used in shaders by LOVE. The values map to OpenGL
+	// generic vertex attribute indices, when applicable.
+	// LOVE uses the old hard-coded attribute APIs for positions, colors, etc.
+	// (for now.)
+	enum VertexAttrib
+	{
+		// Instance ID when pseudo-instancing is used.
+		ATTRIB_PSEUDO_INSTANCE_ID = 1,
+		ATTRIB_MAX_ENUM
+	};
+
 	// A rectangle representing an OpenGL viewport or a scissor box.
 	struct Viewport
 	{
 	void prepareDraw();
 
 	/**
+	 * glDrawArraysInstanced with a pseudo-instancing fallback.
+	 **/
+	void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount);
+
+	/**
+	 * glDrawElementsInstanced with a pseudo-instancing fallback.
+	 **/
+	void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount);
+
+	/**
 	 * Sets the current constant color.
 	 **/
 	void setColor(const Color &c);
 		Viewport viewport;
 		Viewport scissor;
 
+		// The last ID value used for pseudo-instancing.
+		int lastPseudoInstanceID;
+
 	} state;
 
 }; // OpenGL

File src/modules/graphics/opengl/Shader.cpp

 	: shaderSources(sources)
 	, program(0)
 	, builtinUniforms()
+	, vertexAttributes()
 	, lastCanvas((Canvas *) -1)
 {
 	if (shaderSources.empty())
 	for (it = shaderids.begin(); it != shaderids.end(); ++it)
 		glAttachShader(program, *it);
 
+	// Bind generic vertex attribute indices to names in the shader.
+	for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)
+	{
+		const char *name = nullptr;
+		if (attribNames.find((OpenGL::VertexAttrib) i, name))
+			glBindAttribLocation(program, i, (const GLchar *) name);
+	}
+
 	glLinkProgram(program);
 
 	// flag shaders for auto-deletion when the program object is deleted.
 	// Retreive all active uniform variables in this shader from OpenGL.
 	mapActiveUniforms();
 
+	for (int i = 0; i < int(OpenGL::ATTRIB_MAX_ENUM); i++)
+	{
+		const char *name = nullptr;
+		if (attribNames.find(OpenGL::VertexAttrib(i), name))
+			vertexAttributes[i] = glGetAttribLocation(program, name);
+		else
+			vertexAttributes[i] = -1;
+	}
+
 	if (current == this)
 	{
 		// make sure glUseProgram gets called.
 	return texunit;
 }
 
+bool Shader::hasVertexAttrib(OpenGL::VertexAttrib attrib) const
+{
+	return vertexAttributes[int(attrib)] != -1;
+}
+
 bool Shader::hasBuiltinExtern(BuiltinExtern builtin) const
 {
 	return builtinUniforms[int(builtin)] != -1;
 
 StringMap<Shader::ShaderType, Shader::TYPE_MAX_ENUM> Shader::typeNames(Shader::typeNameEntries, sizeof(Shader::typeNameEntries));
 
+StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM>::Entry Shader::attribNameEntries[] =
+{
+	{"love_PseudoInstanceID", OpenGL::ATTRIB_PSEUDO_INSTANCE_ID},
+};
+
+StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM> Shader::attribNames(Shader::attribNameEntries, sizeof(Shader::attribNameEntries));
+
 StringMap<Shader::BuiltinExtern, Shader::BUILTIN_MAX_ENUM>::Entry Shader::builtinNameEntries[] =
 {
 	{"love_ScreenParams", Shader::BUILTIN_SCREEN_PARAMS},

File src/modules/graphics/opengl/Shader.h

 	/**
 	 * Internal use only.
 	 **/
+	bool hasVertexAttrib(OpenGL::VertexAttrib attrib) const;
 	bool hasBuiltinExtern(BuiltinExtern builtin) const;
 	bool sendBuiltinFloat(BuiltinExtern builtin, int size, const GLfloat *m, int count);
 	void checkSetScreenParams();
 	// Location values for any built-in uniform variables.
 	GLint builtinUniforms[BUILTIN_MAX_ENUM];
 
+	// Location values for any generic vertex attribute variables.
+	GLint vertexAttributes[OpenGL::ATTRIB_MAX_ENUM];
+
 	// Uniform location buffer map
 	std::map<std::string, Uniform> uniforms;
 
 	static StringMap<ShaderType, TYPE_MAX_ENUM>::Entry typeNameEntries[];
 	static StringMap<ShaderType, TYPE_MAX_ENUM> typeNames;
 
+	// Names for the generic vertex attributes used by love.
+	static StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM>::Entry attribNameEntries[];
+	static StringMap<OpenGL::VertexAttrib, OpenGL::ATTRIB_MAX_ENUM> attribNames;
+
 	// Names for the built-in uniform variables.
 	static StringMap<BuiltinExtern, BUILTIN_MAX_ENUM>::Entry builtinNameEntries[];
 	static StringMap<BuiltinExtern, BUILTIN_MAX_ENUM> builtinNames;

File src/modules/graphics/opengl/wrap_Graphics.cpp

 			if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5))
 				supported = false;
 			break;
+		case Graphics::SUPPORT_INSTANCING:
+			if (!GLEE_ARB_draw_instanced)
+				supported = false;
+			break;
 		default:
 			supported = false;
 		}

File src/modules/graphics/opengl/wrap_Mesh.cpp

 	return 1;
 }
 
+int w_Mesh_setInstanceCount(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	t->setInstanceCount(luaL_checkint(L, 2));
+	return 0;
+}
+
+int w_Mesh_getInstanceCount(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	lua_pushinteger(L, t->getInstanceCount());
+	return 1;
+}
+
 int w_Mesh_setTexture(lua_State *L)
 {
 	Mesh *t = luax_checkmesh(L, 1);
 	{ "getVertexCount", w_Mesh_getVertexCount },
 	{ "setVertexMap", w_Mesh_setVertexMap },
 	{ "getVertexMap", w_Mesh_getVertexMap },
+	{ "setInstanceCount", w_Mesh_setInstanceCount },
+	{ "getInstanceCount", w_Mesh_getInstanceCount },
 	{ "setTexture", w_Mesh_setTexture },
 	{ "getTexture", w_Mesh_getTexture },
 	{ "setDrawMode", w_Mesh_setDrawMode },

File src/modules/graphics/opengl/wrap_Mesh.h

 int w_Mesh_getVertexCount(lua_State *L);
 int w_Mesh_setVertexMap(lua_State *L);
 int w_Mesh_getVertexMap(lua_State *L);
+int w_Mesh_setInstanceCount(lua_State *L);
+int w_Mesh_getInstanceCount(lua_State *L);
 int w_Mesh_setTexture(lua_State *L);
 int w_Mesh_getTexture(lua_State *L);
 int w_Mesh_setDrawMode(lua_State *L);

File src/scripts/graphics.lua

 #define VertexColor gl_Color
 
 #define VaryingTexCoord gl_TexCoord[0]
-#define VaryingColor gl_FrontColor]],
+#define VaryingColor gl_FrontColor
+
+#if defined(GL_ARB_draw_instanced)
+	#extension GL_ARB_draw_instanced : enable
+	#define love_InstanceID gl_InstanceIDARB
+#else
+	attribute float love_PseudoInstanceID;
+	int love_InstanceID = int(love_PseudoInstanceID);
+#endif]],
 
 		FOOTER = [[
 void main() {

File src/scripts/graphics.lua.h

 
 	0x2d, 0x2d, 0x5b, 0x5b, 0x0a,
 	0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 
-	0x2d, 0x32, 0x30, 0x31, 0x33, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 
+	0x2d, 0x32, 0x30, 0x31, 0x34, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 
 	0x6d, 0x65, 0x6e, 0x74, 0x20, 0x54, 0x65, 0x61, 0x6d, 0x0a,
 	0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 
 	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x27, 0x61, 0x73, 0x2d, 0x69, 0x73, 0x27, 0x2c, 0x20, 0x77, 
 	0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x67, 0x6c, 0x5f, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x5b, 
 	0x30, 0x5d, 0x0a,
 	0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 
-	0x6f, 0x72, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x5d, 0x5d, 
-	0x2c, 0x0a,
+	0x6f, 0x72, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x0a,
+	0x23, 0x69, 0x66, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x28, 0x47, 0x4c, 0x5f, 0x41, 0x52, 0x42, 
+	0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x29, 0x0a,
+	0x09, 0x23, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x47, 0x4c, 0x5f, 0x41, 0x52, 0x42, 
+	0x5f, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x64, 0x20, 0x3a, 0x20, 
+	0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x0a,
+	0x09, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x49, 0x6e, 0x73, 0x74, 
+	0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x20, 0x67, 0x6c, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 
+	0x49, 0x44, 0x41, 0x52, 0x42, 0x0a,
+	0x23, 0x65, 0x6c, 0x73, 0x65, 0x0a,
+	0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6c, 
+	0x6f, 0x76, 0x65, 0x5f, 0x50, 0x73, 0x65, 0x75, 0x64, 0x6f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 
+	0x49, 0x44, 0x3b, 0x0a,
+	0x09, 0x69, 0x6e, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 
+	0x49, 0x44, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x74, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x50, 0x73, 0x65, 0x75, 
+	0x64, 0x6f, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x44, 0x29, 0x3b, 0x0a,
+	0x23, 0x65, 0x6e, 0x64, 0x69, 0x66, 0x5d, 0x5d, 0x2c, 0x0a,
 	0x09, 0x09, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a,
 	0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a,
 	0x09, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x20, 0x3d,