Browse Source

Add low-level hardware instancing support to Meshes.

Add “instancing” Graphics Feature constant.

Add love.graphics.drawInstanced(mesh, instancecount, x, y, …) which draws the mesh multiple times in a single draw call. Each instance of the mesh will appear in the exact same spot unless one of the following are used:

- Add an optional vertex attribute step type argument to Mesh:attachAttribute. “pervertex” is the default. “perinstance” causes the attribute to be per-instance instead of per-vertex, when the mesh is drawn.

- Add love_InstanceID as a built-in read only int variable in GLSL 3 vertex shaders. It is always 0 except when a Mesh is drawn with more than 1 instance specified in the draw call. It can be used to manually compute or index into per-instance values in a shader.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
bea7cfdaab

+ 1 - 0
src/modules/graphics/Graphics.cpp

@@ -1339,6 +1339,7 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featur
 	{ "fullnpot",           FEATURE_FULL_NPOT            },
 	{ "pixelshaderhighp",   FEATURE_PIXEL_SHADER_HIGHP   },
 	{ "glsl3",              FEATURE_GLSL3                },
+	{ "instancing",         FEATURE_INSTANCING           },
 };
 
 StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries));

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

@@ -167,6 +167,7 @@ public:
 		FEATURE_FULL_NPOT,
 		FEATURE_PIXEL_SHADER_HIGHP,
 		FEATURE_GLSL3,
+		FEATURE_INSTANCING,
 		FEATURE_MAX_ENUM
 	};
 

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

@@ -490,6 +490,11 @@ void Graphics::flushStreamDraws()
 	streamBufferState.indexCount = 0;
 }
 
+void Graphics::drawInstanced(Mesh *mesh, const love::Matrix4 &m, int instancecount)
+{
+	mesh->drawInstanced(this, m, instancecount);
+}
+
 static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/)
 {
 	// Human-readable strings for the debug info.
@@ -1487,6 +1492,8 @@ bool Graphics::isSupported(Feature feature) const
 		return gl.isPixelShaderHighpSupported();
 	case FEATURE_GLSL3:
 		return GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
+	case FEATURE_INSTANCING:
+		return gl.isInstancingSupported();
 	default:
 		return false;
 	}

+ 2 - 0
src/modules/graphics/opengl/Graphics.h

@@ -96,6 +96,8 @@ public:
 
 	void flushStreamDraws() override;
 
+	void drawInstanced(Mesh *mesh, const Matrix4 &m, int instancecount);
+
 	void clear(Colorf color) override;
 	void clear(const std::vector<OptionalColorf> &colors) override;
 

+ 52 - 14
src/modules/graphics/opengl/Mesh.cpp

@@ -151,7 +151,7 @@ void Mesh::setupAttachedAttributes()
 		if (attachedAttributes.find(name) != attachedAttributes.end())
 			throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
 
-		attachedAttributes[name] = {this, (int) i, true};
+		attachedAttributes[name] = {this, (int) i, STEP_PER_VERTEX, true};
 	}
 }
 
@@ -312,8 +312,11 @@ bool Mesh::isAttributeEnabled(const std::string &name) const
 	return it->second.enabled;
 }
 
-void Mesh::attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname)
+void Mesh::attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname, AttributeStep step)
 {
+	if (step == STEP_PER_INSTANCE && !gl.isInstancingSupported())
+		throw love::Exception("Vertex attribute instancing is not supported on this system.");
+
 	if (mesh != this)
 	{
 		for (const auto &it : mesh->attachedAttributes)
@@ -335,6 +338,7 @@ void Mesh::attachAttribute(const std::string &name, Mesh *mesh, const std::strin
 	newattrib.mesh = mesh;
 	newattrib.enabled = oldattrib.mesh ? oldattrib.enabled : true;
 	newattrib.index = mesh->getAttributeIndex(attachname);
+	newattrib.step = step;
 
 	if (newattrib.index < 0)
 		throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", attachname.c_str());
@@ -598,16 +602,20 @@ int Mesh::bindAttributeToShaderInput(int attributeindex, const std::string &inpu
 	return attriblocation;
 }
 
-void Mesh::draw(Graphics *gfx, const Matrix4 &m)
+void Mesh::drawInstanced(love::graphics::Graphics *gfx, const love::Matrix4 &m, int instancecount)
 {
-	if (vertexCount <= 0)
+	if (vertexCount <= 0 || instancecount <= 0)
 		return;
 
+	if (instancecount > 1 && !gl.isInstancingSupported())
+		throw love::Exception("Instancing is not supported on this system.");
+
 	gfx->flushStreamDraws();
 
 	OpenGL::TempDebugGroup debuggroup("Mesh draw");
 
 	uint32 enabledattribs = 0;
+	uint32 instancedattribs = 0;
 
 	for (const auto &attrib : attachedAttributes)
 	{
@@ -618,14 +626,21 @@ void Mesh::draw(Graphics *gfx, const Matrix4 &m)
 		int location = mesh->bindAttributeToShaderInput(attrib.second.index, attrib.first);
 
 		if (location >= 0)
-			enabledattribs |= 1u << (uint32) location;
+		{
+			uint32 bit = 1u << (uint32) location;
+
+			enabledattribs |= bit;
+
+			if (attrib.second.step == STEP_PER_INSTANCE)
+				instancedattribs |= bit;
+		}
 	}
 
 	// Not supported on all platforms or GL versions, I believe.
 	if (!(enabledattribs & ATTRIBFLAG_POS))
 		throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
 
-	gl.useVertexAttribArrays(enabledattribs);
+	gl.useVertexAttribArrays(enabledattribs, instancedattribs);
 
 	gl.bindTextureToUnit(texture, 0, false);
 
@@ -654,7 +669,7 @@ void Mesh::draw(Graphics *gfx, const Matrix4 &m)
 		GLenum type = OpenGL::getGLIndexDataType(elementDataType);
 
 		if (count > 0)
-			gl.drawElements(getGLDrawMode(drawMode), count, type, indices);
+			gl.drawElements(getGLDrawMode(drawMode), count, type, indices, instancecount);
 	}
 	else
 	{
@@ -668,10 +683,15 @@ void Mesh::draw(Graphics *gfx, const Matrix4 &m)
 
 		// Normal non-indexed drawing (no custom vertex map.)
 		if (count > 0)
-			gl.drawArrays(getGLDrawMode(drawMode), start, count);
+			gl.drawArrays(getGLDrawMode(drawMode), start, count, instancecount);
 	}
 }
 
+void Mesh::draw(love::graphics::Graphics *gfx, const love::Matrix4 &m)
+{
+	drawInstanced(gfx, m, 1);
+}
+
 size_t Mesh::getAttribFormatSize(const AttribFormat &format)
 {
 	switch (format.type)
@@ -742,24 +762,42 @@ bool Mesh::getConstant(DataType in, const char *&out)
 	return dataTypes.find(in, out);
 }
 
+bool Mesh::getConstant(const char *in, AttributeStep &out)
+{
+	return attributeSteps.find(in, out);
+}
+
+bool Mesh::getConstant(AttributeStep in, const char *&out)
+{
+	return attributeSteps.find(in, out);
+}
+
 StringMap<Mesh::DrawMode, Mesh::DRAWMODE_MAX_ENUM>::Entry Mesh::drawModeEntries[] =
 {
-	{"fan", DRAWMODE_FAN},
-	{"strip", DRAWMODE_STRIP},
-	{"triangles", DRAWMODE_TRIANGLES},
-	{"points", DRAWMODE_POINTS},
+	{ "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[] =
 {
-	{"byte", DATA_BYTE},
-	{"float", DATA_FLOAT},
+	{ "byte", DATA_BYTE   },
+	{ "float", DATA_FLOAT },
 };
 
 StringMap<Mesh::DataType, Mesh::DATA_MAX_ENUM> Mesh::dataTypes(Mesh::dataTypeEntries, sizeof(Mesh::dataTypeEntries));
 
+StringMap<Mesh::AttributeStep, Mesh::STEP_MAX_ENUM>::Entry Mesh::attributeStepEntries[] =
+{
+	{ "pervertex",   STEP_PER_VERTEX   },
+	{ "perinstance", STEP_PER_INSTANCE },
+};
+
+StringMap<Mesh::AttributeStep, Mesh::STEP_MAX_ENUM> Mesh::attributeSteps(Mesh::attributeStepEntries, sizeof(Mesh::attributeStepEntries));
+
 } // opengl
 } // graphics
 } // love

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

@@ -72,6 +72,13 @@ public:
 		DATA_MAX_ENUM
 	};
 
+	enum AttributeStep
+	{
+		STEP_PER_VERTEX,
+		STEP_PER_INSTANCE,
+		STEP_MAX_ENUM
+	};
+
 	struct AttribFormat
 	{
 		std::string name;
@@ -132,7 +139,7 @@ public:
 	 * Attaches a vertex attribute from another Mesh to this one. The attribute
 	 * will be used when drawing this Mesh.
 	 **/
-	void attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname);
+	void attachAttribute(const std::string &name, Mesh *mesh, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX);
 	bool detachAttribute(const std::string &name);
 
 	void *mapVertexData();
@@ -192,6 +199,8 @@ public:
 
 	int bindAttributeToShaderInput(int attributeindex, const std::string &inputname);
 
+	void drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount);
+
 	// Implements Drawable.
 	void draw(Graphics *gfx, const Matrix4 &m) override;
 
@@ -201,12 +210,16 @@ public:
 	static bool getConstant(const char *in, DataType &out);
 	static bool getConstant(DataType in, const char *&out);
 
+	static bool getConstant(const char *in, AttributeStep &out);
+	static bool getConstant(AttributeStep in, const char *&out);
+
 private:
 
 	struct AttachedAttribute
 	{
 		Mesh *mesh;
 		int index;
+		AttributeStep step;
 		bool enabled;
 	};
 
@@ -254,6 +267,9 @@ private:
 	static StringMap<DataType, DATA_MAX_ENUM>::Entry dataTypeEntries[];
 	static StringMap<DataType, DATA_MAX_ENUM> dataTypes;
 
+	static StringMap<AttributeStep, STEP_MAX_ENUM>::Entry attributeStepEntries[];
+	static StringMap<AttributeStep, STEP_MAX_ENUM> attributeSteps;
+
 }; // Mesh
 
 } // opengl

+ 32 - 7
src/modules/graphics/opengl/OpenGL.cpp

@@ -134,8 +134,15 @@ void OpenGL::setupContext()
 
 	GLint maxvertexattribs = 1;
 	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxvertexattribs);
+
 	state.enabledAttribArrays = (uint32) ((1ull << uint32(maxvertexattribs)) - 1);
-	useVertexAttribArrays(0);
+
+	if (GLAD_ES_VERSION_3_0 || isCoreProfile())
+		state.instancedAttribArrays = state.enabledAttribArrays;
+	else
+		state.instancedAttribArrays = 0;
+
+	useVertexAttribArrays(0, 0);
 
 	// Get the current viewport.
 	glGetIntegerv(GL_VIEWPORT, (GLint *) &state.viewport.x);
@@ -427,23 +434,32 @@ void OpenGL::deleteBuffer(GLuint buffer)
 	}
 }
 
-void OpenGL::drawArrays(GLenum mode, GLint first, GLsizei count)
+void OpenGL::drawArrays(GLenum mode, GLint first, GLsizei count, GLsizei instancecount)
 {
-	glDrawArrays(mode, first, count);
+	if (instancecount > 1)
+		glDrawArraysInstanced(mode, first, count, instancecount);
+	else
+		glDrawArrays(mode, first, count);
+
 	++stats.drawCalls;
 }
 
-void OpenGL::drawElements(GLenum mode, GLsizei count, GLenum type, const void *indices)
+void OpenGL::drawElements(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount)
 {
-	glDrawElements(mode, count, type, indices);
+	if (count > 1)
+		glDrawElementsInstanced(mode, count, type, indices, instancecount);
+	else
+		glDrawElements(mode, count, type, indices);
+
 	++stats.drawCalls;
 }
 
-void OpenGL::useVertexAttribArrays(uint32 arraybits)
+void OpenGL::useVertexAttribArrays(uint32 arraybits, uint32 instancedbits)
 {
 	uint32 diff = arraybits ^ state.enabledAttribArrays;
+	uint32 instancediff = instancedbits ^ state.instancedAttribArrays;
 
-	if (diff == 0)
+	if (diff == 0 && instancediff == 0)
 		return;
 
 	// Max 32 attributes. As of when this was written, no GL driver exposes more
@@ -459,9 +475,13 @@ void OpenGL::useVertexAttribArrays(uint32 arraybits)
 			else
 				glDisableVertexAttribArray(i);
 		}
+
+		if (instancediff & bit)
+			glVertexAttribDivisor(i, (instancedbits & bit) != 0 ? 1 : 0);
 	}
 
 	state.enabledAttribArrays = arraybits;
+	state.instancedAttribArrays = instancedbits;
 
 	// glDisableVertexAttribArray will make the constant value for a vertex
 	// attribute undefined. We rely on the per-vertex color attribute being
@@ -733,6 +753,11 @@ bool OpenGL::isPixelShaderHighpSupported() const
 	return pixelShaderHighpSupported;
 }
 
+bool OpenGL::isInstancingSupported() const
+{
+	return GLAD_ES_VERSION_3_0 || isCoreProfile();
+}
+
 int OpenGL::getMaxTextureSize() const
 {
 	return maxTextureSize;

+ 5 - 3
src/modules/graphics/opengl/OpenGL.h

@@ -212,8 +212,8 @@ public:
 	 * glDrawArrays and glDrawElements which increment the draw-call counter by
 	 * themselves.
 	 **/
-	void drawArrays(GLenum mode, GLint first, GLsizei count);
-	void drawElements(GLenum mode, GLsizei count, GLenum type, const void *indices);
+	void drawArrays(GLenum mode, GLint first, GLsizei count, GLsizei instancecount = 1);
+	void drawElements(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount = 1);
 
 	/**
 	 * Sets the enabled vertex attribute arrays based on the specified attribute
@@ -222,7 +222,7 @@ public:
 	 * See the VertexAttribFlags enum for the standard vertex attributes.
 	 * This function *must* be used instead of glEnable/DisableVertexAttribArray.
 	 **/
-	void useVertexAttribArrays(uint32 arraybits);
+	void useVertexAttribArrays(uint32 arraybits, uint32 instancedbits = 0);
 
 	/**
 	 * Sets the OpenGL rendering viewport to the specified rectangle.
@@ -315,6 +315,7 @@ public:
 
 	bool isClampZeroTextureWrapSupported() const;
 	bool isPixelShaderHighpSupported() const;
+	bool isInstancingSupported() const;
 
 	/**
 	 * Returns the maximum supported width or height of a texture.
@@ -410,6 +411,7 @@ private:
 		int curTextureUnit;
 
 		uint32 enabledAttribArrays;
+		uint32 instancedAttribArrays;
 
 		Colorf constantColor;
 		Colorf lastConstantColor;

+ 31 - 0
src/modules/graphics/opengl/wrap_Graphics.cpp

@@ -1670,6 +1670,36 @@ int w_draw(lua_State *L)
 	return 0;
 }
 
+int w_drawInstanced(lua_State *L)
+{
+	Mesh *t = luax_checkmesh(L, 1);
+	int instancecount = (int) luaL_checkinteger(L, 2);
+
+	if (luax_istype(L, 3, math::Transform::type))
+	{
+		math::Transform *tf = luax_totype<math::Transform>(L, 3);
+		luax_catchexcept(L, [&]() { instance()->drawInstanced(t, tf->getMatrix(), instancecount); });
+	}
+	else
+	{
+		float x  = (float) luaL_optnumber(L, 3,  0.0);
+		float y  = (float) luaL_optnumber(L, 4,  0.0);
+		float a  = (float) luaL_optnumber(L, 5,  0.0);
+		float sx = (float) luaL_optnumber(L, 6,  1.0);
+		float sy = (float) luaL_optnumber(L, 7,  sx);
+		float ox = (float) luaL_optnumber(L, 8,  0.0);
+		float oy = (float) luaL_optnumber(L, 9,  0.0);
+		float kx = (float) luaL_optnumber(L, 10, 0.0);
+		float ky = (float) luaL_optnumber(L, 11, 0.0);
+
+		Matrix4 m(x, y, a, sx, sy, ox, oy, kx, ky);
+
+		luax_catchexcept(L, [&]() { instance()->drawInstanced(t, m, instancecount); });
+	}
+
+	return 0;
+}
+
 int w_print(lua_State *L)
 {
 	std::vector<Font::ColoredString> str;
@@ -2195,6 +2225,7 @@ static const luaL_Reg functions[] =
 	{ "captureScreenshot", w_captureScreenshot },
 
 	{ "draw", w_draw },
+	{ "drawInstanced", w_drawInstanced },
 
 	{ "print", w_print },
 	{ "printf", w_printf },

+ 5 - 4
src/modules/graphics/opengl/wrap_Graphics.lua

@@ -142,6 +142,7 @@ GLSL.VERTEX = {
 	#define varying out
 	#ifndef LOVE_GLSL1_ON_GLSL3
 		#define love_VertexID gl_VertexID
+		#define love_InstanceID gl_InstanceID
 	#endif
 #endif
 
@@ -221,15 +222,15 @@ vec4 VideoTexel(vec2 texcoords) {
 }]],
 
 	FOOTER = [[
-uniform sampler2D MainTexture;
+uniform sampler2D MainTex;
 void main() {
-	love_PixelColor = effect(VaryingColor, MainTexture, VaryingTexCoord.st, love_PixelCoord);
+	love_PixelColor = effect(VaryingColor, MainTex, VaryingTexCoord.st, love_PixelCoord);
 }]],
 
 	FOOTER_MULTI_CANVAS = [[
-uniform sampler2D MainTexture;
+uniform sampler2D MainTex;
 void main() {
-	effects(VaryingColor, MainTexture, VaryingTexCoord.st, love_PixelCoord);
+	effects(VaryingColor, MainTex, VaryingTexCoord.st, love_PixelCoord);
 }]],
 }
 

+ 9 - 2
src/modules/graphics/opengl/wrap_Mesh.cpp

@@ -338,8 +338,15 @@ 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);
-	const char *attachname = luaL_optstring(L, 4, name);
-	luax_catchexcept(L, [&](){ t->attachAttribute(name, mesh, attachname); });
+
+	Mesh::AttributeStep step = Mesh::STEP_PER_VERTEX;
+	const char *stepstr = lua_isnoneornil(L, 4) ? nullptr : luaL_checkstring(L, 4);
+	if (stepstr != nullptr && !Mesh::getConstant(stepstr, step))
+		return luaL_error(L, "Invalid vertex attribute step: %s", stepstr);
+
+	const char *attachname = luaL_optstring(L, 5, name);
+
+	luax_catchexcept(L, [&](){ t->attachAttribute(name, mesh, attachname, step); });
 	return 0;
 }