From 73f1ce0d40c0883666004bc93233e5c1b2eb895b Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Tue, 21 Jan 2014 06:01:50 -0400 Subject: [PATCH] Added instancing support to Meshes via Mesh:setInstanceCount. Added a new built-in variable to vertex shaders: int love_InstanceID. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/modules/graphics/Graphics.cpp | 1 + src/modules/graphics/Graphics.h | 1 + src/modules/graphics/opengl/Mesh.cpp | 33 +++++++-- src/modules/graphics/opengl/Mesh.h | 11 +++ src/modules/graphics/opengl/OpenGL.cpp | 69 +++++++++++++++++-- src/modules/graphics/opengl/OpenGL.h | 24 +++++++ src/modules/graphics/opengl/Shader.cpp | 30 ++++++++ src/modules/graphics/opengl/Shader.h | 8 +++ src/modules/graphics/opengl/wrap_Graphics.cpp | 4 ++ src/modules/graphics/opengl/wrap_Mesh.cpp | 16 +++++ src/modules/graphics/opengl/wrap_Mesh.h | 2 + src/scripts/graphics.lua | 10 ++- src/scripts/graphics.lua.h | 21 +++++- 13 files changed, 217 insertions(+), 13 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 93a13bef7..9d3e20a0a 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -175,6 +175,7 @@ StringMap::Entry Graphics::suppor { "mipmap", Graphics::SUPPORT_MIPMAP }, { "dxt", Graphics::SUPPORT_DXT }, { "bc5", Graphics::SUPPORT_BC5 }, + { "instancing", Graphics::SUPPORT_INSTANCING }, }; StringMap Graphics::support(Graphics::supportEntries, sizeof(Graphics::supportEntries)); diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 994e2bed3..90b87cac3 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -94,6 +94,7 @@ public: SUPPORT_MIPMAP, SUPPORT_DXT, SUPPORT_BC5, + SUPPORT_INSTANCING, SUPPORT_MAX_ENUM }; diff --git a/src/modules/graphics/opengl/Mesh.cpp b/src/modules/graphics/opengl/Mesh.cpp index f73ff7dca..12fa91d91 100644 --- a/src/modules/graphics/opengl/Mesh.cpp +++ b/src/modules/graphics/opengl/Mesh.cpp @@ -35,6 +35,7 @@ Mesh::Mesh(const std::vector &verts, Mesh::DrawMode mode) , vertex_count(0) , ibo(nullptr) , element_count(0) + , instance_count(1) , draw_mode(mode) , texture(nullptr) , colors_enabled(false) @@ -51,8 +52,8 @@ Mesh::~Mesh() void Mesh::setVertices(const std::vector &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(); @@ -170,6 +171,19 @@ size_t Mesh::getVertexMapCount() const 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(); @@ -270,18 +284,27 @@ void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, flo 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) diff --git a/src/modules/graphics/opengl/Mesh.h b/src/modules/graphics/opengl/Mesh.h index 498e501ed..ab2c02967 100644 --- a/src/modules/graphics/opengl/Mesh.h +++ b/src/modules/graphics/opengl/Mesh.h @@ -109,6 +109,15 @@ public: **/ 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. **/ @@ -165,6 +174,8 @@ private: VertexBuffer *ibo; size_t element_count; + int instance_count; + DrawMode draw_mode; Texture *texture; diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 18301f223..cae8c88b9 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -113,6 +113,8 @@ void OpenGL::initContext() initMaxValues(); createDefaultTexture(); + state.lastPseudoInstanceID = -1; + contextInitialized = true; } @@ -214,10 +216,69 @@ void OpenGL::createDefaultTexture() 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) diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index 29bdcf835..6a27fb6f2 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -63,6 +63,17 @@ public: 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 { @@ -99,6 +110,16 @@ public: **/ 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. **/ @@ -230,6 +251,9 @@ private: Viewport viewport; Viewport scissor; + // The last ID value used for pseudo-instancing. + int lastPseudoInstanceID; + } state; }; // OpenGL diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 88ebbdac3..b14ea7a74 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -70,6 +70,7 @@ Shader::Shader(const ShaderSources &sources) : shaderSources(sources) , program(0) , builtinUniforms() + , vertexAttributes() , lastCanvas((Canvas *) -1) { if (shaderSources.empty()) @@ -181,6 +182,14 @@ void Shader::createProgram(const std::vector &shaderids) 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. @@ -273,6 +282,15 @@ bool Shader::loadVolatile() // 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. @@ -633,6 +651,11 @@ int Shader::getTextureUnit(const std::string &name) 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; @@ -730,6 +753,13 @@ StringMap::Entry Shader::typeNameEntr StringMap Shader::typeNames(Shader::typeNameEntries, sizeof(Shader::typeNameEntries)); +StringMap::Entry Shader::attribNameEntries[] = +{ + {"love_PseudoInstanceID", OpenGL::ATTRIB_PSEUDO_INSTANCE_ID}, +}; + +StringMap Shader::attribNames(Shader::attribNameEntries, sizeof(Shader::attribNameEntries)); + StringMap::Entry Shader::builtinNameEntries[] = { {"love_ScreenParams", Shader::BUILTIN_SCREEN_PARAMS}, diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 97fcafc43..ab32bffd7 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -138,6 +138,7 @@ public: /** * 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(); @@ -198,6 +199,9 @@ private: // 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 uniforms; @@ -220,6 +224,10 @@ private: static StringMap::Entry typeNameEntries[]; static StringMap typeNames; + // Names for the generic vertex attributes used by love. + static StringMap::Entry attribNameEntries[]; + static StringMap attribNames; + // Names for the built-in uniform variables. static StringMap::Entry builtinNameEntries[]; static StringMap builtinNames; diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 00387e194..0fcf827b9 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -984,6 +984,10 @@ int w_isSupported(lua_State *L) 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; } diff --git a/src/modules/graphics/opengl/wrap_Mesh.cpp b/src/modules/graphics/opengl/wrap_Mesh.cpp index 1ea57e755..84c5cfedd 100644 --- a/src/modules/graphics/opengl/wrap_Mesh.cpp +++ b/src/modules/graphics/opengl/wrap_Mesh.cpp @@ -236,6 +236,20 @@ int w_Mesh_getVertexMap(lua_State *L) 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); @@ -338,6 +352,8 @@ static const luaL_Reg functions[] = { "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 }, diff --git a/src/modules/graphics/opengl/wrap_Mesh.h b/src/modules/graphics/opengl/wrap_Mesh.h index efae413dc..2e3a381f2 100644 --- a/src/modules/graphics/opengl/wrap_Mesh.h +++ b/src/modules/graphics/opengl/wrap_Mesh.h @@ -41,6 +41,8 @@ int w_Mesh_getVertices(lua_State *L); 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); diff --git a/src/scripts/graphics.lua b/src/scripts/graphics.lua index 2af520bcb..0d6ce715e 100644 --- a/src/scripts/graphics.lua +++ b/src/scripts/graphics.lua @@ -1315,7 +1315,15 @@ uniform vec2 love_ScreenParams;]] #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() { diff --git a/src/scripts/graphics.lua.h b/src/scripts/graphics.lua.h index c08ac6326..280c4c83c 100644 --- a/src/scripts/graphics.lua.h +++ b/src/scripts/graphics.lua.h @@ -27,7 +27,7 @@ const unsigned char graphics_lua[] = 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, @@ -6312,8 +6312,23 @@ const unsigned char graphics_lua[] = 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,