From 3c1c13793ed7d66490efc2d488071026dc63c7b4 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 26 Nov 2015 21:24:29 -0400 Subject: [PATCH] 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. --- src/modules/graphics/opengl/GLBuffer.cpp | 3 + src/modules/graphics/opengl/Mesh.cpp | 92 ++++++++++--------- src/modules/graphics/opengl/Mesh.h | 5 +- src/modules/graphics/opengl/SpriteBatch.cpp | 66 ++++++++++--- src/modules/graphics/opengl/SpriteBatch.h | 17 ++++ .../graphics/opengl/wrap_SpriteBatch.cpp | 11 +++ .../graphics/opengl/wrap_SpriteBatch.h | 1 + 7 files changed, 140 insertions(+), 55 deletions(-) diff --git a/src/modules/graphics/opengl/GLBuffer.cpp b/src/modules/graphics/opengl/GLBuffer.cpp index 06d0f6d39..2a7789243 100644 --- a/src/modules/graphics/opengl/GLBuffer.cpp +++ b/src/modules/graphics/opengl/GLBuffer.cpp @@ -90,6 +90,9 @@ void *GLBuffer::map() void GLBuffer::unmapStatic(size_t offset, size_t size) { + if (size == 0) + return; + // Upload the mapped data to the buffer. glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, memory_map + offset); } diff --git a/src/modules/graphics/opengl/Mesh.cpp b/src/modules/graphics/opengl/Mesh.cpp index b722d923f..a248c11c9 100644 --- a/src/modules/graphics/opengl/Mesh.cpp +++ b/src/modules/graphics/opengl/Mesh.cpp @@ -149,7 +149,7 @@ void Mesh::setupAttachedAttributes() if (attachedAttributes.find(name) != attachedAttributes.end()) 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; } +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) { auto it = attachedAttributes.find(name); @@ -328,20 +339,10 @@ void Mesh::attachAttribute(const std::string &name, Mesh *mesh) oldattrib = it->second; newattrib.mesh = mesh; - newattrib.index = std::numeric_limits::max(); 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::max()) + if (newattrib.index < 0) throw love::Exception("The specified mesh does not have a vertex attribute named '%s'", name.c_str()); if (newattrib.mesh != this) @@ -537,6 +538,39 @@ void Mesh::getDrawRange(int &min, int &max) const 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) { 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; Mesh *mesh = attrib.second.mesh; - const AttribFormat &format = mesh->vertexFormat[attrib.second.index]; + int location = mesh->bindAttributeToShaderInput(attrib.second.index, attrib.first); - 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); - - enabledattribs |= 1 << uint32(attriblocation); + if (location >= 0) + enabledattribs |= 1u << (uint32) location; } // Not supported on all platforms or GL versions, I believe. diff --git a/src/modules/graphics/opengl/Mesh.h b/src/modules/graphics/opengl/Mesh.h index c3c124790..95c334821 100644 --- a/src/modules/graphics/opengl/Mesh.h +++ b/src/modules/graphics/opengl/Mesh.h @@ -126,6 +126,7 @@ public: **/ const std::vector &getVertexFormat() 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. @@ -193,6 +194,8 @@ public: void setDrawRange(); void getDrawRange(int &min, int &max) const; + int bindAttributeToShaderInput(int attributeindex, const std::string &inputname); + // Implements Drawable. 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 { Mesh *mesh; - size_t index; + int index; bool enabled; }; diff --git a/src/modules/graphics/opengl/SpriteBatch.cpp b/src/modules/graphics/opengl/SpriteBatch.cpp index 6b4ff109a..2936274af 100644 --- a/src/modules/graphics/opengl/SpriteBatch.cpp +++ b/src/modules/graphics/opengl/SpriteBatch.cpp @@ -193,6 +193,28 @@ int SpriteBatch::getBufferSize() const 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) { 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()); - 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; - // 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.prepareDraw(); + + GLBuffer::Bind element_bind(*quad_indices.getBuffer()); gl.drawElements(GL_TRIANGLES, (GLsizei) quad_indices.getIndexCount(next), quad_indices.getType(), quad_indices.getPointer(0)); } diff --git a/src/modules/graphics/opengl/SpriteBatch.h b/src/modules/graphics/opengl/SpriteBatch.h index 0db524c5f..f64adcff8 100644 --- a/src/modules/graphics/opengl/SpriteBatch.h +++ b/src/modules/graphics/opengl/SpriteBatch.h @@ -24,6 +24,9 @@ // C #include +// C++ +#include + // LOVE #include "common/math.h" #include "common/Matrix.h" @@ -98,11 +101,23 @@ public: **/ 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. void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); private: + struct AttachedAttribute + { + StrongRef mesh; + int index; + }; + void addv(const Vertex *v, const Matrix3 &m, int index); /** @@ -129,6 +144,8 @@ private: GLBuffer *array_buf; QuadIndices quad_indices; + std::unordered_map attached_attributes; + }; // SpriteBatch } // opengl diff --git a/src/modules/graphics/opengl/wrap_SpriteBatch.cpp b/src/modules/graphics/opengl/wrap_SpriteBatch.cpp index 52f76d3c8..bde1cf428 100644 --- a/src/modules/graphics/opengl/wrap_SpriteBatch.cpp +++ b/src/modules/graphics/opengl/wrap_SpriteBatch.cpp @@ -203,6 +203,16 @@ int w_SpriteBatch_getBufferSize(lua_State *L) 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(L, 3, GRAPHICS_MESH_ID); + + luax_catchexcept(L, [&](){ t->attachAttribute(name, m); }); + return 0; +} + static const luaL_Reg functions[] = { { "add", w_SpriteBatch_add }, @@ -216,6 +226,7 @@ static const luaL_Reg functions[] = { "getCount", w_SpriteBatch_getCount }, { "setBufferSize", w_SpriteBatch_setBufferSize }, { "getBufferSize", w_SpriteBatch_getBufferSize }, + { "attachAttribute", w_SpriteBatch_attachAttribute }, { 0, 0 } }; diff --git a/src/modules/graphics/opengl/wrap_SpriteBatch.h b/src/modules/graphics/opengl/wrap_SpriteBatch.h index 84c92699f..3926b9ff3 100644 --- a/src/modules/graphics/opengl/wrap_SpriteBatch.h +++ b/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_setBufferSize(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);