diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index ec30e1d75..b0d8b928a 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -1339,6 +1339,7 @@ StringMap::Entry Graphics::featur { "fullnpot", FEATURE_FULL_NPOT }, { "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP }, { "glsl3", FEATURE_GLSL3 }, + { "instancing", FEATURE_INSTANCING }, }; StringMap Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries)); diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 361bfc011..d6481ceb5 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -167,6 +167,7 @@ public: FEATURE_FULL_NPOT, FEATURE_PIXEL_SHADER_HIGHP, FEATURE_GLSL3, + FEATURE_INSTANCING, FEATURE_MAX_ENUM }; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 92109c6fe..69e29b1f3 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/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; } diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index cafcbf97b..e7af45807 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/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 &colors) override; diff --git a/src/modules/graphics/opengl/Mesh.cpp b/src/modules/graphics/opengl/Mesh.cpp index 56450e2c5..de37e512a 100644 --- a/src/modules/graphics/opengl/Mesh.cpp +++ b/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::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::drawModes(Mesh::drawModeEntries, sizeof(Mesh::drawModeEntries)); StringMap::Entry Mesh::dataTypeEntries[] = { - {"byte", DATA_BYTE}, - {"float", DATA_FLOAT}, + { "byte", DATA_BYTE }, + { "float", DATA_FLOAT }, }; StringMap Mesh::dataTypes(Mesh::dataTypeEntries, sizeof(Mesh::dataTypeEntries)); +StringMap::Entry Mesh::attributeStepEntries[] = +{ + { "pervertex", STEP_PER_VERTEX }, + { "perinstance", STEP_PER_INSTANCE }, +}; + +StringMap Mesh::attributeSteps(Mesh::attributeStepEntries, sizeof(Mesh::attributeStepEntries)); + } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Mesh.h b/src/modules/graphics/opengl/Mesh.h index 282fa3d3b..c103da590 100644 --- a/src/modules/graphics/opengl/Mesh.h +++ b/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::Entry dataTypeEntries[]; static StringMap dataTypes; + static StringMap::Entry attributeStepEntries[]; + static StringMap attributeSteps; + }; // Mesh } // opengl diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index e1d9c3895..92b2995ba 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/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; diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index b7f5a5167..ef0147742 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/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; diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index cac3ead61..0f3490e6b 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/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(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 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 }, diff --git a/src/modules/graphics/opengl/wrap_Graphics.lua b/src/modules/graphics/opengl/wrap_Graphics.lua index 420b8f4dd..80921003b 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.lua +++ b/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); }]], } diff --git a/src/modules/graphics/opengl/wrap_Mesh.cpp b/src/modules/graphics/opengl/wrap_Mesh.cpp index 17aad520f..2d1366aaa 100644 --- a/src/modules/graphics/opengl/wrap_Mesh.cpp +++ b/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; }