From 50ff1e4d2bda0e14c0d9b99442536814be4b4f23 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sat, 7 Sep 2024 18:06:03 -0300 Subject: [PATCH] use location numbers for vertex input attributes, instead of name-based binding. - add 'location' named field to buffer and mesh vertex format tables, replaces 'name' field. - location numbers are expected to match 'layout (location = #)' qualifiers in vertex inputs in shader code. - deprecate vertex inputs in shader code that don't have layout location qualifiers. - love's default vertex attributes use location 0 for position, 1 for texcoord, and 2 for color. - add new variants of Mesh:attachAttribute, setAttributeEnabled, and isAttributeEnabled which take location numbers instead of names. Improves draw performance in vulkan and metal backends. --- src/modules/graphics/Buffer.cpp | 60 +++++--- src/modules/graphics/Buffer.h | 9 +- src/modules/graphics/Font.cpp | 2 + src/modules/graphics/Font.h | 4 + src/modules/graphics/Graphics.cpp | 60 +++++--- src/modules/graphics/Graphics.h | 36 ++--- src/modules/graphics/Mesh.cpp | 186 +++++++++++++++++++---- src/modules/graphics/Mesh.h | 18 ++- src/modules/graphics/ParticleSystem.cpp | 6 +- src/modules/graphics/ParticleSystem.h | 2 +- src/modules/graphics/Shader.cpp | 27 ++++ src/modules/graphics/Shader.h | 6 + src/modules/graphics/SpriteBatch.cpp | 33 ++-- src/modules/graphics/SpriteBatch.h | 4 +- src/modules/graphics/TextBatch.cpp | 5 +- src/modules/graphics/TextBatch.h | 2 +- src/modules/graphics/metal/Graphics.h | 4 +- src/modules/graphics/metal/Graphics.mm | 21 +-- src/modules/graphics/metal/Shader.h | 4 +- src/modules/graphics/metal/Shader.mm | 20 ++- src/modules/graphics/opengl/Graphics.cpp | 15 +- src/modules/graphics/opengl/Graphics.h | 2 +- src/modules/graphics/vertex.h | 12 ++ src/modules/graphics/vulkan/Graphics.cpp | 91 ++++++----- src/modules/graphics/vulkan/Graphics.h | 6 +- src/modules/graphics/vulkan/Shader.cpp | 58 ++++--- src/modules/graphics/vulkan/Shader.h | 66 +++++--- src/modules/graphics/wrap_Buffer.cpp | 6 + src/modules/graphics/wrap_Graphics.cpp | 88 +++++++++-- src/modules/graphics/wrap_Mesh.cpp | 66 ++++++-- testing/tests/graphics.lua | 20 +-- 31 files changed, 682 insertions(+), 257 deletions(-) diff --git a/src/modules/graphics/Buffer.cpp b/src/modules/graphics/Buffer.cpp index d1b6e49fe..a37dcf02d 100644 --- a/src/modules/graphics/Buffer.cpp +++ b/src/modules/graphics/Buffer.cpp @@ -106,8 +106,13 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector= VertexAttributes::MAX) + { + if (decl.bindingLocation == -1 && !decl.name.empty()) + legacyVertexBindings = true; + else + throw love::Exception("Vertex buffer attributes must have a valid binding location value within [0, %d).", VertexAttributes::MAX); + } } if (texelbuffer) @@ -258,6 +263,17 @@ int Buffer::getDataMemberIndex(const std::string &name) const return -1; } +int Buffer::getDataMemberIndex(int bindingLocation) const +{ + for (size_t i = 0; i < dataMembers.size(); i++) + { + if (dataMembers[i].decl.bindingLocation == bindingLocation) + return (int)i; + } + + return -1; +} + void Buffer::clear(size_t offset, size_t size) { if (isImmutable()) @@ -280,53 +296,53 @@ std::vector Buffer::getCommonFormatDeclaration(CommonFo return {}; case CommonFormat::XYf: return { - { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 } + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_POS } }; case CommonFormat::XYZf: return { - { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC3 } + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC3, 0, ATTRIB_POS } }; case CommonFormat::RGBAub: return { - { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 } + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4, 0, ATTRIB_COLOR } }; case CommonFormat::STf_RGBAub: return { - { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, - { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_TEXCOORD }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4, 0, ATTRIB_COLOR }, }; case CommonFormat::STPf_RGBAub: return { - { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 }, - { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3, 0, ATTRIB_TEXCOORD }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4, 0, ATTRIB_COLOR }, }; case CommonFormat::XYf_STf: return { - { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, - { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_POS }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_TEXCOORD }, }; case CommonFormat::XYf_STPf: return { - { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, - { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3 }, + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_POS }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC3, 0, ATTRIB_TEXCOORD }, }; case CommonFormat::XYf_STf_RGBAub: return { - { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, - { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, - { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_POS }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_TEXCOORD }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4, 0, ATTRIB_COLOR }, }; case CommonFormat::XYf_STus_RGBAub: return { - { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, - { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_UNORM16_VEC2 }, - { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_POS }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_UNORM16_VEC2, 0, ATTRIB_TEXCOORD }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4, 0, ATTRIB_COLOR }, }; case CommonFormat::XYf_STPf_RGBAub: return { - { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2 }, - { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2 }, - { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4 }, + { getConstant(ATTRIB_POS), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_POS }, + { getConstant(ATTRIB_TEXCOORD), DATAFORMAT_FLOAT_VEC2, 0, ATTRIB_TEXCOORD }, + { getConstant(ATTRIB_COLOR), DATAFORMAT_UNORM8_VEC4, 0, ATTRIB_COLOR }, }; } diff --git a/src/modules/graphics/Buffer.h b/src/modules/graphics/Buffer.h index 67fc6757e..12276f32c 100644 --- a/src/modules/graphics/Buffer.h +++ b/src/modules/graphics/Buffer.h @@ -65,11 +65,13 @@ class Buffer : public love::Object, public Resource std::string name; DataFormat format; int arrayLength; + int bindingLocation; - DataDeclaration(const std::string &name, DataFormat format, int arrayLength = 0) + DataDeclaration(const std::string &name, DataFormat format, int arrayLength = 0, int bindingLocation = -1) : name(name) , format(format) , arrayLength(arrayLength) + , bindingLocation(bindingLocation) {} }; @@ -117,6 +119,7 @@ class Buffer : public love::Object, public Resource const DataMember &getDataMember(int index) const { return dataMembers[index]; } size_t getMemberOffset(int index) const { return dataMembers[index].offset; } int getDataMemberIndex(const std::string &name) const; + int getDataMemberIndex(int bindingLocation) const; const std::string &getDebugName() const { return debugName; } void setImmutable(bool immutable) { this->immutable = immutable; }; @@ -154,6 +157,8 @@ class Buffer : public love::Object, public Resource **/ virtual ptrdiff_t getTexelBufferHandle() const = 0; + bool hasLegacyVertexBindings() const { return legacyVertexBindings; } + static std::vector getCommonFormatDeclaration(CommonFormat format); class Mapper @@ -198,6 +203,8 @@ class Buffer : public love::Object, public Resource bool mapped; MapType mappedType; bool immutable; + + bool legacyVertexBindings = false; }; // Buffer diff --git a/src/modules/graphics/Font.cpp b/src/modules/graphics/Font.cpp index 4e18dbde2..b2b20fe81 100644 --- a/src/modules/graphics/Font.cpp +++ b/src/modules/graphics/Font.cpp @@ -92,6 +92,8 @@ Font::Font(love::font::Rasterizer *r, const SamplerState &s) if (pixelFormat == PIXELFORMAT_LA8_UNORM && !gfx->isPixelFormatSupported(pixelFormat, PIXELFORMATUSAGEFLAGS_SAMPLE)) pixelFormat = PIXELFORMAT_RGBA8_UNORM; + vertexAttributesID = gfx->registerVertexAttributes(VertexAttributes(vertexFormat, 0)); + loadVolatile(); ++fontCount; } diff --git a/src/modules/graphics/Font.h b/src/modules/graphics/Font.h index 3cb8afb0b..fe8c906be 100644 --- a/src/modules/graphics/Font.h +++ b/src/modules/graphics/Font.h @@ -150,6 +150,8 @@ class Font : public Object, public Volatile uint32 getTextureCacheID() const; + VertexAttributesID getVertexAttributesID() const { return vertexAttributesID; } + // Implements Volatile. bool loadVolatile() override; void unloadVolatile() override; @@ -204,6 +206,8 @@ class Font : public Object, public Volatile // ID which is incremented when the texture cache is invalidated. uint32 textureCacheID; + VertexAttributesID vertexAttributesID; + // 1 pixel of transparent padding between glyphs (so quads won't pick up // other glyphs), plus one pixel of transparent padding that the quads will // use, for edge antialiasing. diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 5c6f3c517..f9c0a604c 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -212,8 +212,10 @@ Graphics::Graphics(const char *name) states.reserve(10); states.push_back(DisplayState()); + noAttributesID = registerVertexAttributes(VertexAttributes()); + if (!Shader::initialize()) - throw love::Exception("Shader support failed to initialize!"); + throw love::Exception("Shader support failed to initialize."); } Graphics::~Graphics() @@ -1413,6 +1415,29 @@ void Graphics::updatePendingReadbacks() } } +VertexAttributesID Graphics::registerVertexAttributes(const VertexAttributes &attributes) +{ + for (size_t i = 0; i < vertexAttributesDatabase.size(); i++) + { + if (attributes == vertexAttributesDatabase[i]) + return { (int)i + 1 }; + } + + vertexAttributesDatabase.push_back(attributes); + return { (int)vertexAttributesDatabase.size() }; +} + +bool Graphics::findVertexAttributes(VertexAttributesID id, VertexAttributes &attributes) +{ + int index = id.id - 1; + + if (index < 0 || index >= (int)vertexAttributesDatabase.size()) + return false; + + attributes = vertexAttributesDatabase[index]; + return true; +} + void Graphics::intersectScissor(const Rect &rect) { Rect currect = states.back().scissorRect; @@ -2022,6 +2047,17 @@ void Graphics::flushBatchedDraws() VertexAttributes attributes; BufferBindings buffers; + VertexAttributesID attributesID = sbstate.attributesIDs[(int)sbstate.formats[0]][(int)sbstate.formats[1]]; + + if (!findVertexAttributes(attributesID, attributes)) + { + for (int i = 0; i < 2; i++) + attributes.setCommonFormat(sbstate.formats[i], (uint8)i); + + attributesID = registerVertexAttributes(attributes); + sbstate.attributesIDs[(int)sbstate.formats[0]][(int)sbstate.formats[1]] = attributesID; + } + size_t usedsizes[3] = {0, 0, 0}; for (int i = 0; i < 2; i++) @@ -2029,8 +2065,6 @@ void Graphics::flushBatchedDraws() if (sbstate.formats[i] == CommonFormat::NONE) continue; - attributes.setCommonFormat(sbstate.formats[i], (uint8) i); - usedsizes[i] = getFormatStride(sbstate.formats[i]) * sbstate.vertexCount; size_t offset = sbstate.vb[i]->unmap(usedsizes[i]); @@ -2053,7 +2087,7 @@ void Graphics::flushBatchedDraws() { usedsizes[2] = sizeof(uint16) * sbstate.indexCount; - DrawIndexedCommand cmd(&attributes, &buffers, sbstate.indexBuffer); + DrawIndexedCommand cmd(attributesID, &buffers, sbstate.indexBuffer); cmd.primitiveType = sbstate.primitiveMode; cmd.indexCount = sbstate.indexCount; cmd.indexType = INDEX_UINT16; @@ -2065,7 +2099,7 @@ void Graphics::flushBatchedDraws() } else { - DrawCommand cmd(&attributes, &buffers); + DrawCommand cmd(attributesID, &buffers); cmd.primitiveType = sbstate.primitiveMode; cmd.vertexStart = 0; cmd.vertexCount = sbstate.vertexCount; @@ -2156,10 +2190,8 @@ void Graphics::drawFromShader(PrimitiveType primtype, int vertexcount, int insta Shader::current->validateDrawState(primtype, maintexture); - VertexAttributes attributes; BufferBindings buffers; - - DrawCommand cmd(&attributes, &buffers); + DrawCommand cmd(noAttributesID, &buffers); cmd.primitiveType = primtype; cmd.vertexCount = vertexcount; @@ -2190,10 +2222,8 @@ void Graphics::drawFromShader(Buffer *indexbuffer, int indexcount, int instancec Shader::current->validateDrawState(PRIMITIVE_TRIANGLES, maintexture); - VertexAttributes attributes; BufferBindings buffers; - - DrawIndexedCommand cmd(&attributes, &buffers, indexbuffer); + DrawIndexedCommand cmd(noAttributesID, &buffers, indexbuffer); cmd.primitiveType = PRIMITIVE_TRIANGLES; cmd.indexCount = indexcount; @@ -2221,10 +2251,8 @@ void Graphics::drawFromShaderIndirect(PrimitiveType primtype, Buffer *indirectar Shader::current->validateDrawState(primtype, maintexture); - VertexAttributes attributes; BufferBindings buffers; - - DrawCommand cmd(&attributes, &buffers); + DrawCommand cmd(noAttributesID, &buffers); cmd.primitiveType = primtype; cmd.indirectBuffer = indirectargs; @@ -2248,10 +2276,8 @@ void Graphics::drawFromShaderIndirect(Buffer *indexbuffer, Buffer *indirectargs, Shader::current->validateDrawState(PRIMITIVE_TRIANGLES, maintexture); - VertexAttributes attributes; BufferBindings buffers; - - DrawIndexedCommand cmd(&attributes, &buffers, indexbuffer); + DrawIndexedCommand cmd(noAttributesID, &buffers, indexbuffer); cmd.primitiveType = PRIMITIVE_TRIANGLES; cmd.indexType = getIndexDataType(indexbuffer->getDataMember(0).decl.format); diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index a88c8fd1e..8b28e6e38 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -233,7 +233,7 @@ class Graphics : public Module { PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; - const VertexAttributes *attributes; + VertexAttributesID attributesID; const BufferBindings *buffers; int vertexStart = 0; @@ -248,8 +248,8 @@ class Graphics : public Module // TODO: This should be moved out to a state transition API? CullMode cullMode = CULL_NONE; - DrawCommand(const VertexAttributes *attribs, const BufferBindings *buffers) - : attributes(attribs) + DrawCommand(VertexAttributesID attributesID, const BufferBindings *buffers) + : attributesID(attributesID) , buffers(buffers) {} }; @@ -258,7 +258,7 @@ class Graphics : public Module { PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; - const VertexAttributes *attributes; + VertexAttributesID attributesID; const BufferBindings *buffers; int indexCount = 0; @@ -276,8 +276,8 @@ class Graphics : public Module // TODO: This should be moved out to a state transition API? CullMode cullMode = CULL_NONE; - DrawIndexedCommand(const VertexAttributes *attribs, const BufferBindings *buffers, Resource *indexbuffer) - : attributes(attribs) + DrawIndexedCommand(VertexAttributesID attributesID, const BufferBindings *buffers, Resource *indexbuffer) + : attributesID(attributesID) , buffers(buffers) , indexBuffer(indexbuffer) {} @@ -877,7 +877,7 @@ class Graphics : public Module virtual void draw(const DrawCommand &cmd) = 0; virtual void draw(const DrawIndexedCommand &cmd) = 0; - virtual void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, Texture *texture) = 0; + virtual void drawQuads(int start, int count, VertexAttributesID attributesID, const BufferBindings &buffers, Texture *texture) = 0; void flushBatchedDraws(); BatchedVertexData requestBatchedDraw(const BatchedDrawCommand &command); @@ -894,6 +894,9 @@ class Graphics : public Module void validateIndirectArgsBuffer(IndirectArgsType argstype, Buffer *indirectargs, int argsindex); + VertexAttributesID registerVertexAttributes(const VertexAttributes &attributes); + bool findVertexAttributes(VertexAttributesID id, VertexAttributes &attributes); + template T *getScratchBuffer(size_t count) { @@ -964,27 +967,22 @@ class Graphics : public Module struct BatchedDrawState { - StreamBuffer *vb[2]; + StreamBuffer *vb[2] = {}; StreamBuffer *indexBuffer = nullptr; PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; - CommonFormat formats[2]; + CommonFormat formats[2] = {}; StrongRef texture; Shader::StandardShader standardShaderType = Shader::STANDARD_DEFAULT; int vertexCount = 0; int indexCount = 0; - StreamBuffer::MapInfo vbMap[2]; + VertexAttributesID attributesIDs[(int)CommonFormat::COUNT][(int)CommonFormat::COUNT] = {}; + + StreamBuffer::MapInfo vbMap[2] = {}; StreamBuffer::MapInfo indexBufferMap = StreamBuffer::MapInfo(); bool flushing = false; - - BatchedDrawState() - { - vb[0] = vb[1] = nullptr; - formats[0] = formats[1] = CommonFormat::NONE; - vbMap[0] = vbMap[1] = StreamBuffer::MapInfo(); - } }; struct TemporaryBuffer @@ -1107,6 +1105,10 @@ class Graphics : public Module std::unordered_map cachedShaderStages[SHADERSTAGE_MAX_ENUM]; + std::vector vertexAttributesDatabase; + + VertexAttributesID noAttributesID; + }; // Graphics STRINGMAP_DECLARE(Renderer); diff --git a/src/modules/graphics/Mesh.cpp b/src/modules/graphics/Mesh.cpp index a534e8646..0d205ac0c 100644 --- a/src/modules/graphics/Mesh.cpp +++ b/src/modules/graphics/Mesh.cpp @@ -115,9 +115,19 @@ Mesh::Mesh(const std::vector &attributes, PrimitiveType d finalizeAttribute(attrib); - int attributeIndex = getAttachedAttributeIndex(attrib.name); - if (attributeIndex != i && attributeIndex != -1) - throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str()); + if (attrib.bindingLocation >= 0) + { + int attributeIndex = getAttachedAttributeIndex(attrib.bindingLocation); + if (attributeIndex != i && attributeIndex != -1) + throw love::Exception("Duplicate vertex attribute binding location: %d", attrib.bindingLocation); + } + + if (!attrib.name.empty()) + { + int attributeIndex = getAttachedAttributeIndex(attrib.name); + if (attributeIndex != i && attributeIndex != -1) + throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str()); + } vertexCount = std::min(vertexCount, attrib.buffer->getArrayLength()); } @@ -137,16 +147,28 @@ void Mesh::setupAttachedAttributes() for (size_t i = 0; i < vertexFormat.size(); i++) { const std::string &name = vertexFormat[i].decl.name; + int bindingLocation = vertexFormat[i].decl.bindingLocation; - if (getAttachedAttributeIndex(name) != -1) - throw love::Exception("Duplicate vertex attribute name: %s", name.c_str()); + if (bindingLocation >= 0) + { + if (getAttachedAttributeIndex(bindingLocation) != -1) + throw love::Exception("Duplicate vertex attribute binding location: %d", bindingLocation); + } - BuiltinVertexAttribute builtinattrib; - int builtinAttribIndex = -1; - if (getConstant(name.c_str(), builtinattrib)) - builtinAttribIndex = (int)builtinattrib; + if (!name.empty()) + { + if (getAttachedAttributeIndex(name) != -1) + throw love::Exception("Duplicate vertex attribute name: %s", name.c_str()); + } - attachedAttributes.push_back({name, vertexBuffer, nullptr, name, (int) i, 0, STEP_PER_VERTEX, builtinAttribIndex, true}); + if (bindingLocation < 0) + { + BuiltinVertexAttribute builtinattrib; + if (getConstant(name.c_str(), builtinattrib)) + bindingLocation = (int)builtinattrib; + } + + attachedAttributes.push_back({name, vertexBuffer, nullptr, name, bindingLocation, (int) i, 0, STEP_PER_VERTEX, bindingLocation, true}); } } @@ -161,6 +183,17 @@ int Mesh::getAttachedAttributeIndex(const std::string &name) const return -1; } +int Mesh::getAttachedAttributeIndex(int bindingLocation) const +{ + for (int i = 0; i < (int)attachedAttributes.size(); i++) + { + if (attachedAttributes[i].bindingLocation == bindingLocation) + return i; + } + + return -1; +} + void Mesh::finalizeAttribute(BufferAttribute &attrib) const { if ((attrib.buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0) @@ -169,17 +202,33 @@ void Mesh::finalizeAttribute(BufferAttribute &attrib) const if (attrib.startArrayIndex < 0 || attrib.startArrayIndex >= (int)attrib.buffer->getArrayLength()) throw love::Exception("Invalid start array index %d.", attrib.startArrayIndex + 1); - int indexInBuffer = attrib.buffer->getDataMemberIndex(attrib.nameInBuffer); - if (indexInBuffer < 0) - throw love::Exception("Buffer does not have a vertex attribute with name '%s'.", attrib.nameInBuffer.c_str()); - - BuiltinVertexAttribute builtinattrib; - if (getConstant(attrib.name.c_str(), builtinattrib)) - attrib.builtinAttributeIndex = (int)builtinattrib; + if (attrib.bindingLocationInBuffer >= 0) + { + int indexInBuffer = attrib.buffer->getDataMemberIndex(attrib.bindingLocationInBuffer); + if (indexInBuffer < 0) + throw love::Exception("Buffer does not have a vertex attribute with binding location %d.", attrib.bindingLocationInBuffer); + attrib.indexInBuffer = indexInBuffer; + } else - attrib.builtinAttributeIndex = -1; + { + int indexInBuffer = attrib.buffer->getDataMemberIndex(attrib.nameInBuffer); + if (indexInBuffer < 0) + throw love::Exception("Buffer does not have a vertex attribute with name '%s'.", attrib.nameInBuffer.c_str()); + attrib.indexInBuffer = indexInBuffer; + } + + if (attrib.bindingLocation < 0) + attrib.bindingLocation = attrib.buffer->getDataMember(attrib.indexInBuffer).decl.bindingLocation; + + if (attrib.bindingLocation < 0) + { + BuiltinVertexAttribute builtinattrib; + if (getConstant(attrib.name.c_str(), builtinattrib)) + attrib.bindingLocation = (int)builtinattrib; + } - attrib.indexInBuffer = indexInBuffer; + if (attrib.bindingLocation >= VertexAttributes::MAX || (attrib.bindingLocation < 0 && attrib.name.empty())) + throw love::Exception("Vertex attributes must have a valid binding location value within [0, %d).", VertexAttributes::MAX); } void *Mesh::checkVertexDataOffset(size_t vertindex, size_t *byteoffset) @@ -224,6 +273,7 @@ void Mesh::setAttributeEnabled(const std::string &name, bool enable) throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str()); attachedAttributes[index].enabled = enable; + attributesID.invalidate(); } bool Mesh::isAttributeEnabled(const std::string &name) const @@ -235,10 +285,29 @@ bool Mesh::isAttributeEnabled(const std::string &name) const return attachedAttributes[index].enabled; } +void Mesh::setAttributeEnabled(int bindingLocation, bool enable) +{ + int index = getAttachedAttributeIndex(bindingLocation); + if (index == -1) + throw love::Exception("Mesh does not have an attached vertex attribute with binding location %d", bindingLocation); + + attachedAttributes[index].enabled = enable; + attributesID.invalidate(); +} + +bool Mesh::isAttributeEnabled(int bindingLocation) const +{ + int index = getAttachedAttributeIndex(bindingLocation); + if (index == -1) + throw love::Exception("Mesh does not have an attached vertex attribute with binding location %d", bindingLocation); + + return attachedAttributes[index].enabled; +} + void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, int startindex, AttributeStep step) { - BufferAttribute oldattrib = {}; - BufferAttribute newattrib = {}; + BufferAttribute oldattrib; + BufferAttribute newattrib; int oldindex = getAttachedAttributeIndex(name); if (oldindex != -1) @@ -257,13 +326,42 @@ void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, finalizeAttribute(newattrib); - if (newattrib.indexInBuffer < 0) - throw love::Exception("The specified vertex buffer does not have a vertex attribute named '%s'", attachname.c_str()); + if (oldindex != -1) + attachedAttributes[oldindex] = newattrib; + else + attachedAttributes.push_back(newattrib); + + attributesID.invalidate(); +} + +void Mesh::attachAttribute(int bindingLocation, Buffer *buffer, Mesh *mesh, int attachBindingLocation, int startindex, AttributeStep step) +{ + BufferAttribute oldattrib; + BufferAttribute newattrib; + + int oldindex = getAttachedAttributeIndex(bindingLocation); + if (oldindex != -1) + oldattrib = attachedAttributes[oldindex]; + else if (attachedAttributes.size() + 1 > VertexAttributes::MAX) + throw love::Exception("A maximum of %d attributes can be attached at once.", VertexAttributes::MAX); + + newattrib.bindingLocation = bindingLocation; + newattrib.buffer = buffer; + newattrib.mesh = mesh; + newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true; + newattrib.bindingLocationInBuffer = attachBindingLocation; + newattrib.indexInBuffer = -1; + newattrib.startArrayIndex = startindex; + newattrib.step = step; + + finalizeAttribute(newattrib); if (oldindex != -1) attachedAttributes[oldindex] = newattrib; else attachedAttributes.push_back(newattrib); + + attributesID.invalidate(); } bool Mesh::detachAttribute(const std::string &name) @@ -277,6 +375,22 @@ bool Mesh::detachAttribute(const std::string &name) if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(name) != -1) attachAttribute(name, vertexBuffer, nullptr, name); + attributesID.invalidate(); + return true; +} + +bool Mesh::detachAttribute(int bindingLocation) +{ + int index = getAttachedAttributeIndex(bindingLocation); + if (index == -1) + return false; + + attachedAttributes.erase(attachedAttributes.begin() + index); + + if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(bindingLocation) != -1) + attachAttribute(bindingLocation, vertexBuffer, nullptr, bindingLocation); + + attributesID.invalidate(); return true; } @@ -593,6 +707,8 @@ void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buff if (Shader::current) Shader::current->validateDrawState(primitiveType, texture); + bool attributesIDneedsupdate = !attributesID.isValid(); + VertexAttributes attributes; BufferBindings buffers; @@ -604,14 +720,17 @@ void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buff continue; Buffer *buffer = attrib.buffer.get(); - int attributeindex = attrib.builtinAttributeIndex; + int bindinglocation = attrib.bindingLocation; - // If the attribute is one of the LOVE-defined ones, use the constant - // attribute index for it, otherwise query the index from the shader. - if (attributeindex < 0 && Shader::current) - attributeindex = Shader::current->getVertexAttributeIndex(attrib.name); + // Query the index from the shader as a fallback to support old code that + // hasn't set a binding location. + if (bindinglocation < 0 && Shader::current) + { + bindinglocation = Shader::current->getVertexAttributeIndex(attrib.name); + attributesIDneedsupdate = true; + } - if (attributeindex >= 0) + if (bindinglocation >= 0) { if (attrib.mesh.get()) attrib.mesh->flush(); @@ -622,7 +741,7 @@ void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buff uint16 stride = (uint16) buffer->getArrayStride(); size_t bufferoffset = (size_t) stride * attrib.startArrayIndex; - attributes.set(attributeindex, member.decl.format, offset, activebuffers); + attributes.set(bindinglocation, member.decl.format, offset, activebuffers); attributes.setBufferLayout(activebuffers, stride, attrib.step); // TODO: Ideally we want to reuse buffers with the same stride+step. @@ -635,6 +754,9 @@ void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buff if ((attributes.enableBits & ~(ATTRIBFLAG_TEXCOORD | ATTRIBFLAG_COLOR)) == 0) throw love::Exception("Mesh must have an enabled VertexPosition or custom attribute to be drawn."); + if (attributesIDneedsupdate) + attributesID = gfx->registerVertexAttributes(attributes); + Graphics::TempTransform transform(gfx, m); Buffer *indexbuffer = useIndexBuffer ? indexBuffer : nullptr; @@ -660,7 +782,7 @@ void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buff if (range.isValid()) r.intersect(range); - Graphics::DrawIndexedCommand cmd(&attributes, &buffers, indexbuffer); + Graphics::DrawIndexedCommand cmd(attributesID, &buffers, indexbuffer); cmd.primitiveType = primitiveType; cmd.indexType = indexDataType; @@ -683,7 +805,7 @@ void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buff if (range.isValid()) r.intersect(range); - Graphics::DrawCommand cmd(&attributes, &buffers); + Graphics::DrawCommand cmd(attributesID, &buffers); cmd.primitiveType = primitiveType; cmd.vertexStart = (int) r.getOffset(); diff --git a/src/modules/graphics/Mesh.h b/src/modules/graphics/Mesh.h index c99894d34..1276a5aad 100644 --- a/src/modules/graphics/Mesh.h +++ b/src/modules/graphics/Mesh.h @@ -57,11 +57,12 @@ class Mesh : public Drawable StrongRef buffer; StrongRef mesh; std::string nameInBuffer; - int indexInBuffer; - int startArrayIndex; - AttributeStep step; - int builtinAttributeIndex; - bool enabled; + int bindingLocationInBuffer = -1; + int indexInBuffer = 0; + int startArrayIndex = 0; + AttributeStep step = STEP_PER_VERTEX; + int bindingLocation = -1; + bool enabled = false; }; static love::Type type; @@ -104,6 +105,8 @@ class Mesh : public Drawable **/ void setAttributeEnabled(const std::string &name, bool enable); bool isAttributeEnabled(const std::string &name) const; + void setAttributeEnabled(int bindingLocation, bool enable); + bool isAttributeEnabled(int bindingLocation) const; /** * Attaches a vertex attribute from another vertex buffer to this Mesh. The @@ -114,6 +117,8 @@ class Mesh : public Drawable **/ void attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, int startindex = 0, AttributeStep step = STEP_PER_VERTEX); bool detachAttribute(const std::string &name); + void attachAttribute(int bindingLocation, Buffer *buffer, Mesh *mesh, int attachBindingLocation, int startindex = 0, AttributeStep step = STEP_PER_VERTEX); + bool detachAttribute(int bindingLocation); const std::vector &getAttachedAttributes() const; void *getVertexData() const; @@ -188,6 +193,7 @@ class Mesh : public Drawable void setupAttachedAttributes(); int getAttachedAttributeIndex(const std::string &name) const; + int getAttachedAttributeIndex(int bindingLocation) const; void finalizeAttribute(BufferAttribute &attrib) const; void drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buffer *indirectargs, int argsindex); @@ -196,6 +202,8 @@ class Mesh : public Drawable std::vector attachedAttributes; + VertexAttributesID attributesID; + // Vertex buffer, for the vertex data. StrongRef vertexBuffer; uint8 *vertexData = nullptr; diff --git a/src/modules/graphics/ParticleSystem.cpp b/src/modules/graphics/ParticleSystem.cpp index 64db60f70..bfc4fd16f 100644 --- a/src/modules/graphics/ParticleSystem.cpp +++ b/src/modules/graphics/ParticleSystem.cpp @@ -93,7 +93,7 @@ ParticleSystem::ParticleSystem(Texture *texture, uint32 size) , offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f) , defaultOffset(true) , relativeRotation(false) - , vertexAttributes(CommonFormat::XYf_STf_RGBAub, 0) + , vertexAttributesID(Module::getInstance(Module::M_GRAPHICS)->registerVertexAttributes(VertexAttributes(CommonFormat::XYf_STf_RGBAub, 0))) , buffer(nullptr) { if (size == 0 || size > MAX_PARTICLES) @@ -154,7 +154,7 @@ ParticleSystem::ParticleSystem(const ParticleSystem &p) , colors(p.colors) , quads(p.quads) , relativeRotation(p.relativeRotation) - , vertexAttributes(p.vertexAttributes) + , vertexAttributesID(p.vertexAttributesID) , buffer(nullptr) { setBufferSize(maxParticles); @@ -1087,7 +1087,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m) vertexbuffers.set(0, buffer, 0); Texture *tex = gfx->getTextureOrDefaultForActiveShader(texture); - gfx->drawQuads(0, pCount, vertexAttributes, vertexbuffers, tex); + gfx->drawQuads(0, pCount, vertexAttributesID, vertexbuffers, tex); } bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out) diff --git a/src/modules/graphics/ParticleSystem.h b/src/modules/graphics/ParticleSystem.h index 05727d838..9a9d72dc2 100644 --- a/src/modules/graphics/ParticleSystem.h +++ b/src/modules/graphics/ParticleSystem.h @@ -673,7 +673,7 @@ class ParticleSystem : public Drawable bool relativeRotation; - const VertexAttributes vertexAttributes; + VertexAttributesID vertexAttributesID; Buffer *buffer; static StringMap::Entry distributionsEntries[]; diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index bdecbc54a..ecb82c20d 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -650,6 +650,22 @@ Shader::Shader(StrongRef _stages[], const CompileOptions &options) if (!validateInternal(_stages, err, reflection)) throw love::Exception("%s", err.c_str()); + std::vector unsetVertexInputLocations; + + for (const auto &kvp : reflection.vertexInputs) + { + if (kvp.second < 0) + unsetVertexInputLocations.push_back(kvp.first); + } + + if (!unsetVertexInputLocations.empty()) + { + std::string str = unsetVertexInputLocations[0]; + for (size_t i = 1; i < unsetVertexInputLocations.size(); i++) + str += ", " + unsetVertexInputLocations[i]; + unsetVertexInputLocationsString = str; + } + activeTextures.resize(reflection.textureCount); activeBuffers.resize(reflection.bufferCount); @@ -1150,6 +1166,17 @@ bool Shader::validateInternal(StrongRef stages[], std::string &err, } } + for (int i = 0; i < program.getNumPipeInputs(); i++) + { + const glslang::TObjectReflection &info = program.getPipeInput(i); + + int location = info.layoutLocation(); + if (location == glslang::TQualifier::layoutLocationEnd) + location = -1; + + reflection.vertexInputs[info.name] = location; + } + reflection.textureCount = 0; reflection.bufferCount = 0; diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index 3fcddcbd8..a28af1a4b 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -269,6 +269,8 @@ class Shader : public Object, public Resource bool isUsingDeprecatedTextureFunctions() const; bool isUsingDeprecatedTextureUniform() const; + const std::string& getUnsetVertexInputLocationsString() const { return unsetVertexInputLocationsString; } + static SourceInfo getSourceInfo(const std::string &src); static std::string createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const CompileOptions &options, const SourceInfo &info, bool gles, bool checksystemfeatures); @@ -289,6 +291,8 @@ class Shader : public Object, public Resource struct Reflection { + std::map vertexInputs; + std::map texelBuffers; std::map storageBuffers; std::map sampledTextures; @@ -332,6 +336,8 @@ class Shader : public Object, public Resource std::string debugName; + std::string unsetVertexInputLocationsString; + }; // Shader } // graphics diff --git a/src/modules/graphics/SpriteBatch.cpp b/src/modules/graphics/SpriteBatch.cpp index ff6dd3486..c42cf38d5 100644 --- a/src/modules/graphics/SpriteBatch.cpp +++ b/src/modules/graphics/SpriteBatch.cpp @@ -46,6 +46,7 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferDataUs , next(0) , color(255, 255, 255, 255) , colorf(1.0f, 1.0f, 1.0f, 1.0f) + , attributesID() , array_buf(nullptr) , vertex_data(nullptr) , modified_sprites() @@ -134,7 +135,7 @@ int SpriteBatch::addLayer(int layer, const Matrix4 &m, int index) int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index) { if (vertex_format != CommonFormat::XYf_STPf_RGBAub) - throw love::Exception("addLayer can only be called on a SpriteBatch that uses an Array Texture!"); + throw love::Exception("addLayer can only be called on a SpriteBatch that uses an Array Texture."); if (index < -1 || index >= size) throw love::Exception("Invalid sprite index: %d", index + 1); @@ -284,14 +285,16 @@ void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer, Mesh newattrib.buffer = buffer; newattrib.mesh = mesh; + newattrib.bindingIndex = buffer->getDataMember(newattrib.index).decl.bindingLocation; BuiltinVertexAttribute builtinattrib; - if (getConstant(name.c_str(), builtinattrib)) - newattrib.builtinAttributeIndex = (int)builtinattrib; - else - newattrib.builtinAttributeIndex = -1; + if (newattrib.bindingIndex < 0 && getConstant(name.c_str(), builtinattrib)) + newattrib.bindingIndex = (int)builtinattrib; attached_attributes[name] = newattrib; + + // Invalidate attributes ID. + attributesID = VertexAttributesID(); } void SpriteBatch::setDrawRange(int start, int count) @@ -342,6 +345,8 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m) flush(); // Upload any modified sprite data to the GPU. + bool attributesIDneedsupdate = !attributesID.isValid(); + VertexAttributes attributes; BufferBindings buffers; @@ -361,14 +366,17 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m) if (buffer->getArrayLength() < (size_t) next * 4) throw love::Exception("Buffer with attribute '%s' attached to this SpriteBatch has too few vertices", it.first.c_str()); - int attributeindex = it.second.builtinAttributeIndex; + int bindingindex = it.second.bindingIndex; // If the attribute is one of the LOVE-defined ones, use the constant // attribute index for it, otherwise query the index from the shader. - if (attributeindex < 0 && Shader::current) - attributeindex = Shader::current->getVertexAttributeIndex(it.first); + if (bindingindex < 0 && Shader::current) + { + bindingindex = Shader::current->getVertexAttributeIndex(it.first); + attributesIDneedsupdate = true; + } - if (attributeindex >= 0) + if (bindingindex >= 0) { if (it.second.mesh.get()) it.second.mesh->flush(); @@ -378,7 +386,7 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m) uint16 offset = (uint16) buffer->getMemberOffset(it.second.index); uint16 stride = (uint16) buffer->getArrayStride(); - attributes.set(attributeindex, member.decl.format, offset, activebuffers); + attributes.set(bindingindex, member.decl.format, offset, activebuffers); attributes.setBufferLayout(activebuffers, stride); // TODO: We should reuse buffer bindings with the same buffer+stride+step. @@ -387,6 +395,9 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m) } } + if (attributesIDneedsupdate) + attributesID = gfx->registerVertexAttributes(attributes); + Graphics::TempTransform transform(gfx, m); int start = std::min(std::max(0, range_start), next - 1); @@ -400,7 +411,7 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m) if (count > 0) { Texture *tex = gfx->getTextureOrDefaultForActiveShader(texture); - gfx->drawQuads(start, count, attributes, buffers, tex); + gfx->drawQuads(start, count, attributesID, buffers, tex); } } diff --git a/src/modules/graphics/SpriteBatch.h b/src/modules/graphics/SpriteBatch.h index cbcd52c98..978664450 100644 --- a/src/modules/graphics/SpriteBatch.h +++ b/src/modules/graphics/SpriteBatch.h @@ -113,7 +113,7 @@ class SpriteBatch : public Drawable StrongRef buffer; StrongRef mesh; int index; - int builtinAttributeIndex; + int bindingIndex; }; /** @@ -137,6 +137,8 @@ class SpriteBatch : public Drawable CommonFormat vertex_format; size_t vertex_stride; + VertexAttributesID attributesID; + StrongRef array_buf; uint8 *vertex_data; diff --git a/src/modules/graphics/TextBatch.cpp b/src/modules/graphics/TextBatch.cpp index d809418fc..de4c65872 100644 --- a/src/modules/graphics/TextBatch.cpp +++ b/src/modules/graphics/TextBatch.cpp @@ -32,7 +32,7 @@ love::Type TextBatch::type("TextBatch", &Drawable::type); TextBatch::TextBatch(Font *font, const std::vector &text) : font(font) - , vertexAttributes(Font::vertexFormat, 0) + , vertexAttributesID(font->getVertexAttributesID()) , vertexData(nullptr) , modifiedVertices() , vertOffset(0) @@ -218,6 +218,7 @@ void TextBatch::setFont(Font *f) // Invalidate the texture cache ID since the font is different. We also have // to re-upload all the vertices based on the new font's textures. textureCacheID = (uint32) -1; + vertexAttributesID = font->getVertexAttributesID(); regenerateVertices(); } @@ -292,7 +293,7 @@ void TextBatch::draw(Graphics *gfx, const Matrix4 &m) for (const Font::DrawCommand &cmd : drawCommands) { Texture *tex = gfx->getTextureOrDefaultForActiveShader(cmd.texture); - gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, tex); + gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributesID, vertexBuffers, tex); } } diff --git a/src/modules/graphics/TextBatch.h b/src/modules/graphics/TextBatch.h index f6fc35d9a..62aa988e1 100644 --- a/src/modules/graphics/TextBatch.h +++ b/src/modules/graphics/TextBatch.h @@ -86,7 +86,7 @@ class TextBatch : public Drawable StrongRef font; - VertexAttributes vertexAttributes; + VertexAttributesID vertexAttributesID; BufferBindings vertexBuffers; StrongRef vertexBuffer; diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index c932ca9b2..78359a2e3 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -75,7 +75,7 @@ class Graphics final : public love::graphics::Graphics void draw(const DrawCommand &cmd) override; void draw(const DrawIndexedCommand &cmd) override; - void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture) override; + void drawQuads(int start, int count, VertexAttributesID attributesID, const BufferBindings &buffers, love::graphics::Texture *texture) override; void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) override; void clear(const std::vector &colors, OptionalInt stencil, OptionalDouble depth) override; @@ -209,7 +209,7 @@ class Graphics final : public love::graphics::Graphics void endPass(bool presenting); id getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil); - void applyRenderState(id renderEncoder, const VertexAttributes &attributes); + void applyRenderState(id renderEncoder, VertexAttributesID attributesID); bool applyShaderUniforms(id encoder, love::graphics::Shader *shader); void applyShaderUniforms(id renderEncoder, love::graphics::Shader *shader, Texture *maintex); diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 1e34b194d..451c319c0 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -910,7 +910,7 @@ static bool isClampOne(SamplerState::WrapMode w) return mtlstate; } -void Graphics::applyRenderState(id encoder, const VertexAttributes &attributes) +void Graphics::applyRenderState(id encoder, VertexAttributesID attributesID) { const uint32 pipelineStateBits = STATEBIT_SHADER | STATEBIT_BLEND | STATEBIT_COLORMASK; @@ -987,18 +987,18 @@ static bool isClampOne(SamplerState::WrapMode w) [encoder setCullMode:mode]; } - if ((dirtyState & pipelineStateBits) != 0 || !(attributes == lastRenderPipelineKey.vertexAttributes)) + if ((dirtyState & pipelineStateBits) != 0 || attributesID != lastRenderPipelineKey.vertexAttributesID) { auto &key = lastRenderPipelineKey; - key.vertexAttributes = attributes; + key.vertexAttributesID = attributesID; Shader *shader = (Shader *) Shader::current; id pipeline = nil; if (shader) { - key.blend = state.blend; + key.blendStateKey = state.blend.toKey(); key.colorChannelMask = state.colorMask; pipeline = shader->getCachedRenderPipeline(this, key); @@ -1233,7 +1233,7 @@ static void setVertexBuffers(id encoder, love::graphics dirtyRenderState |= STATEBIT_CULLMODE; } - applyRenderState(encoder, *cmd.attributes); + applyRenderState(encoder, cmd.attributesID); applyShaderUniforms(encoder, Shader::current, cmd.texture); setVertexBuffers(encoder, Shader::current, cmd.buffers, renderBindings); @@ -1265,7 +1265,7 @@ static void setVertexBuffers(id encoder, love::graphics dirtyRenderState |= STATEBIT_CULLMODE; } - applyRenderState(encoder, *cmd.attributes); + applyRenderState(encoder, cmd.attributesID); applyShaderUniforms(encoder, Shader::current, cmd.texture); setVertexBuffers(encoder, Shader::current, cmd.buffers, renderBindings); @@ -1317,7 +1317,7 @@ static inline void advanceVertexOffsets(const VertexAttributes &attributes, Buff } } -void Graphics::drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture) +void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, const BufferBindings &buffers, love::graphics::Texture *texture) { @autoreleasepool { const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX; const int MAX_QUADS_PER_DRAW = MAX_VERTICES_PER_DRAW / 4; @@ -1330,7 +1330,7 @@ static inline void advanceVertexOffsets(const VertexAttributes &attributes, Buff dirtyRenderState |= STATEBIT_CULLMODE; } - applyRenderState(encoder, attributes); + applyRenderState(encoder, attributesID); applyShaderUniforms(encoder, Shader::current, texture); id ib = getMTLBuffer(quadIndexBuffer); @@ -1361,6 +1361,9 @@ static inline void advanceVertexOffsets(const VertexAttributes &attributes, Buff } else { + VertexAttributes attributes; + findVertexAttributes(attributesID, attributes); + BufferBindings bufferscopy = buffers; if (start > 0) advanceVertexOffsets(attributes, bufferscopy, start * 4); @@ -1490,7 +1493,7 @@ static inline void advanceVertexOffsets(const VertexAttributes &attributes, Buff } lastRenderPipelineKey.depthStencilFormat = dsformat; - lastRenderPipelineKey.vertexAttributes = VertexAttributes(); + lastRenderPipelineKey.vertexAttributesID = VertexAttributesID(); dirtyRenderState = STATEBIT_ALL; }} diff --git a/src/modules/graphics/metal/Shader.h b/src/modules/graphics/metal/Shader.h index 0913c5483..943060176 100644 --- a/src/modules/graphics/metal/Shader.h +++ b/src/modules/graphics/metal/Shader.h @@ -61,8 +61,8 @@ class Shader final : public love::graphics::Shader struct RenderPipelineKey { - VertexAttributes vertexAttributes; - BlendState blend; + VertexAttributesID vertexAttributesID; + uint32 blendStateKey; uint64 colorRenderTargetFormats; uint32 depthStencilFormat; ColorChannelMask colorChannelMask; diff --git a/src/modules/graphics/metal/Shader.mm b/src/modules/graphics/metal/Shader.mm index 1218039de..b04aa4e1d 100644 --- a/src/modules/graphics/metal/Shader.mm +++ b/src/modules/graphics/metal/Shader.mm @@ -882,15 +882,17 @@ static EShLanguage getGLSLangStage(ShaderStageType stage) auto formatdesc = Metal::convertPixelFormat(device, format); attachment.pixelFormat = formatdesc.format; - if (key.blend.enable && gfx->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_BLEND)) + BlendState blend = BlendState::fromKey(key.blendStateKey); + + if (blend.enable && gfx->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_BLEND)) { attachment.blendingEnabled = YES; - attachment.sourceRGBBlendFactor = getMTLBlendFactor(key.blend.srcFactorRGB); - attachment.destinationRGBBlendFactor = getMTLBlendFactor(key.blend.dstFactorRGB); - attachment.rgbBlendOperation = getMTLBlendOperation(key.blend.operationRGB); - attachment.sourceAlphaBlendFactor = getMTLBlendFactor(key.blend.srcFactorA); - attachment.destinationAlphaBlendFactor = getMTLBlendFactor(key.blend.dstFactorA); - attachment.alphaBlendOperation = getMTLBlendOperation(key.blend.operationA); + attachment.sourceRGBBlendFactor = getMTLBlendFactor(blend.srcFactorRGB); + attachment.destinationRGBBlendFactor = getMTLBlendFactor(blend.dstFactorRGB); + attachment.rgbBlendOperation = getMTLBlendOperation(blend.operationRGB); + attachment.sourceAlphaBlendFactor = getMTLBlendFactor(blend.srcFactorA); + attachment.destinationAlphaBlendFactor = getMTLBlendFactor(blend.dstFactorA); + attachment.alphaBlendOperation = getMTLBlendOperation(blend.operationA); } MTLColorWriteMask writeMask = MTLColorWriteMaskNone; @@ -923,8 +925,10 @@ static EShLanguage getGLSLangStage(ShaderStageType stage) } } + VertexAttributes attributes; + gfx->findVertexAttributes(key.vertexAttributesID, attributes); + MTLVertexDescriptor *vertdesc = [MTLVertexDescriptor vertexDescriptor]; - const auto &attributes = key.vertexAttributes; for (const auto &pair : this->attributes) { diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 011831767..501638394 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -569,8 +569,11 @@ bool Graphics::dispatch(love::graphics::Shader *s, love::graphics::Buffer *indir void Graphics::draw(const DrawCommand &cmd) { + VertexAttributes attributes; + findVertexAttributes(cmd.attributesID, attributes); + gl.prepareDraw(this); - gl.setVertexAttributes(*cmd.attributes, *cmd.buffers); + gl.setVertexAttributes(attributes, *cmd.buffers); gl.bindTextureToUnit(cmd.texture, 0, false); gl.setCullMode(cmd.cullMode); @@ -591,8 +594,11 @@ void Graphics::draw(const DrawCommand &cmd) void Graphics::draw(const DrawIndexedCommand &cmd) { + VertexAttributes attributes; + findVertexAttributes(cmd.attributesID, attributes); + gl.prepareDraw(this); - gl.setVertexAttributes(*cmd.attributes, *cmd.buffers); + gl.setVertexAttributes(attributes, *cmd.buffers); gl.bindTextureToUnit(cmd.texture, 0, false); gl.setCullMode(cmd.cullMode); @@ -640,11 +646,14 @@ static inline void advanceVertexOffsets(const VertexAttributes &attributes, Buff } } -void Graphics::drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture) +void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, const BufferBindings &buffers, love::graphics::Texture *texture) { const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX; const int MAX_QUADS_PER_DRAW = MAX_VERTICES_PER_DRAW / 4; + VertexAttributes attributes; + findVertexAttributes(attributesID, attributes); + gl.prepareDraw(this); gl.bindTextureToUnit(texture, 0, false); gl.setCullMode(CULL_NONE); diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 351532b39..31dd4661e 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -71,7 +71,7 @@ class Graphics final : public love::graphics::Graphics void draw(const DrawCommand &cmd) override; void draw(const DrawIndexedCommand &cmd) override; - void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, love::graphics::Texture *texture) override; + void drawQuads(int start, int count, VertexAttributesID attributesID, const BufferBindings &buffers, love::graphics::Texture *texture) override; void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) override; void clear(const std::vector &colors, OptionalInt stencil, OptionalDouble depth) override; diff --git a/src/modules/graphics/vertex.h b/src/modules/graphics/vertex.h index c4fba7007..1cc0d4637 100644 --- a/src/modules/graphics/vertex.h +++ b/src/modules/graphics/vertex.h @@ -214,6 +214,7 @@ enum class CommonFormat XYf_STf_RGBAub, XYf_STus_RGBAub, XYf_STPf_RGBAub, + COUNT, }; struct DataFormatInfo @@ -379,6 +380,17 @@ struct VertexAttributes bool operator == (const VertexAttributes &other) const; }; +struct VertexAttributesID +{ + int id = 0; + + bool isValid() const { return id > 0; } + void invalidate() { id = 0; } + + bool operator == (VertexAttributesID other) const { return other.id == id; } + bool operator != (VertexAttributesID other) const { return other.id != id; } +}; + size_t getFormatStride(CommonFormat format); uint32 getFormatFlags(CommonFormat format); diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 91154a342..04795c015 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -867,7 +867,7 @@ Graphics::RendererInfo Graphics::getRendererInfo() const void Graphics::draw(const DrawCommand &cmd) { - prepareDraw(*cmd.attributes, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode); + prepareDraw(cmd.attributesID, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode); if (cmd.indirectBuffer != nullptr) { @@ -893,7 +893,7 @@ void Graphics::draw(const DrawCommand &cmd) void Graphics::draw(const DrawIndexedCommand &cmd) { - prepareDraw(*cmd.attributes, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode); + prepareDraw(cmd.attributesID, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode); vkCmdBindIndexBuffer( commandBuffers.at(currentFrame), @@ -924,12 +924,12 @@ void Graphics::draw(const DrawIndexedCommand &cmd) drawCalls++; } -void Graphics::drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, graphics::Texture *texture) +void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, const BufferBindings &buffers, graphics::Texture *texture) { const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX; const int MAX_QUADS_PER_DRAW = MAX_VERTICES_PER_DRAW / 4; - prepareDraw(attributes, buffers, texture, PRIMITIVE_TRIANGLES, CULL_NONE); + prepareDraw(attributesID, buffers, texture, PRIMITIVE_TRIANGLES, CULL_NONE); vkCmdBindIndexBuffer( commandBuffers.at(currentFrame), @@ -2328,7 +2328,7 @@ void Graphics::createVulkanVertexFormat( } } -void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindings &buffers, graphics::Texture *texture, PrimitiveType primitiveType, CullMode cullmode) +void Graphics::prepareDraw(VertexAttributesID attributesID, const BufferBindings &buffers, graphics::Texture *texture, PrimitiveType primitiveType, CullMode cullmode) { if (!renderPassState.active) startRenderPass(); @@ -2337,31 +2337,37 @@ void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindi usedShadersInFrame.insert(s); - GraphicsPipelineConfiguration configuration{}; + GraphicsPipelineConfigurationFull configuration{}; - configuration.renderPass = renderPassState.beginInfo.renderPass; - configuration.vertexAttributes = attributes; - configuration.wireFrame = states.back().wireframe; - configuration.blendState = states.back().blend; - configuration.colorChannelMask = states.back().colorMask; - configuration.msaaSamples = renderPassState.msaa; - configuration.numColorAttachments = renderPassState.numColorAttachments; - configuration.packedColorAttachmentFormats = renderPassState.packedColorAttachmentFormats; - configuration.primitiveType = primitiveType; + configuration.core.renderPass = renderPassState.beginInfo.renderPass; + configuration.core.attributesID = attributesID; + configuration.core.wireFrame = states.back().wireframe; + configuration.core.blendStateKey = states.back().blend.toKey(); + configuration.core.colorChannelMask = states.back().colorMask; + configuration.core.msaaSamples = renderPassState.msaa; + configuration.core.numColorAttachments = renderPassState.numColorAttachments; + configuration.core.packedColorAttachmentFormats = renderPassState.packedColorAttachmentFormats; + configuration.core.primitiveType = primitiveType; + + VkPipeline pipeline = VK_NULL_HANDLE; if (optionalDeviceExtensions.extendedDynamicState) + { vkCmdSetCullModeEXT(commandBuffers.at(currentFrame), Vulkan::getCullMode(cullmode)); + pipeline = s->getCachedGraphicsPipeline(this, configuration.core); + } else { - configuration.dynamicState.winding = states.back().winding; - configuration.dynamicState.depthState.compare = states.back().depthTest; - configuration.dynamicState.depthState.write = states.back().depthWrite; - configuration.dynamicState.stencilAction = states.back().stencil.action; - configuration.dynamicState.stencilCompare = states.back().stencil.compare; - configuration.dynamicState.cullmode = cullmode; + configuration.noDynamicState.winding = states.back().winding; + configuration.noDynamicState.depthState.compare = states.back().depthTest; + configuration.noDynamicState.depthState.write = states.back().depthWrite; + configuration.noDynamicState.stencilAction = states.back().stencil.action; + configuration.noDynamicState.stencilCompare = states.back().stencil.compare; + configuration.noDynamicState.cullmode = cullmode; + + pipeline = s->getCachedGraphicsPipeline(this, configuration); } - VkPipeline pipeline = s->getCachedGraphicsPipeline(this, configuration); if (pipeline != renderPassState.pipeline) { vkCmdBindPipeline(commandBuffers.at(currentFrame), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); @@ -2696,7 +2702,7 @@ VkSampler Graphics::getCachedSampler(const SamplerState &samplerState) } } -VkPipeline Graphics::createGraphicsPipeline(Shader *shader, const GraphicsPipelineConfiguration &configuration) +VkPipeline Graphics::createGraphicsPipeline(Shader *shader, const GraphicsPipelineConfigurationCore &configuration, const GraphicsPipelineConfigurationNoDynamicState *noDynamicStateConfiguration) { VkGraphicsPipelineCreateInfo pipelineInfo{}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; @@ -2706,7 +2712,10 @@ VkPipeline Graphics::createGraphicsPipeline(Shader *shader, const GraphicsPipeli std::vector bindingDescriptions; std::vector attributeDescriptions; - createVulkanVertexFormat(shader, configuration.vertexAttributes, bindingDescriptions, attributeDescriptions); + VertexAttributes vertexAttributes; + findVertexAttributes(configuration.attributesID, vertexAttributes); + + createVulkanVertexFormat(shader, vertexAttributes, bindingDescriptions, attributeDescriptions); VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; @@ -2733,8 +2742,8 @@ VkPipeline Graphics::createGraphicsPipeline(Shader *shader, const GraphicsPipeli rasterizer.lineWidth = 1.0f; if (!optionalDeviceExtensions.extendedDynamicState) { - rasterizer.cullMode = Vulkan::getCullMode(configuration.dynamicState.cullmode); - rasterizer.frontFace = Vulkan::getFrontFace(configuration.dynamicState.winding); + rasterizer.cullMode = Vulkan::getCullMode(noDynamicStateConfiguration->cullmode); + rasterizer.frontFace = Vulkan::getFrontFace(noDynamicStateConfiguration->winding); } rasterizer.depthBiasEnable = VK_FALSE; @@ -2752,8 +2761,8 @@ VkPipeline Graphics::createGraphicsPipeline(Shader *shader, const GraphicsPipeli depthStencil.depthTestEnable = VK_TRUE; if (!optionalDeviceExtensions.extendedDynamicState) { - depthStencil.depthWriteEnable = Vulkan::getBool(configuration.dynamicState.depthState.write); - depthStencil.depthCompareOp = Vulkan::getCompareOp(configuration.dynamicState.depthState.compare); + depthStencil.depthWriteEnable = Vulkan::getBool(noDynamicStateConfiguration->depthState.write); + depthStencil.depthCompareOp = Vulkan::getCompareOp(noDynamicStateConfiguration->depthState.compare); } depthStencil.depthBoundsTestEnable = VK_FALSE; depthStencil.minDepthBounds = 0.0f; @@ -2764,31 +2773,33 @@ VkPipeline Graphics::createGraphicsPipeline(Shader *shader, const GraphicsPipeli if (!optionalDeviceExtensions.extendedDynamicState) { depthStencil.front.failOp = VK_STENCIL_OP_KEEP; - depthStencil.front.passOp = Vulkan::getStencilOp(configuration.dynamicState.stencilAction); + depthStencil.front.passOp = Vulkan::getStencilOp(noDynamicStateConfiguration->stencilAction); depthStencil.front.depthFailOp = VK_STENCIL_OP_KEEP; - depthStencil.front.compareOp = Vulkan::getCompareOp(getReversedCompareMode(configuration.dynamicState.stencilCompare)); + depthStencil.front.compareOp = Vulkan::getCompareOp(getReversedCompareMode(noDynamicStateConfiguration->stencilCompare)); depthStencil.back.failOp = VK_STENCIL_OP_KEEP; - depthStencil.back.passOp = Vulkan::getStencilOp(configuration.dynamicState.stencilAction); + depthStencil.back.passOp = Vulkan::getStencilOp(noDynamicStateConfiguration->stencilAction); depthStencil.back.depthFailOp = VK_STENCIL_OP_KEEP; - depthStencil.back.compareOp = Vulkan::getCompareOp(getReversedCompareMode(configuration.dynamicState.stencilCompare)); + depthStencil.back.compareOp = Vulkan::getCompareOp(getReversedCompareMode(noDynamicStateConfiguration->stencilCompare)); } pipelineInfo.pDepthStencilState = &depthStencil; + BlendState blendState = BlendState::fromKey(configuration.blendStateKey); + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; colorBlendAttachment.colorWriteMask = Vulkan::getColorMask(configuration.colorChannelMask); - colorBlendAttachment.blendEnable = Vulkan::getBool(configuration.blendState.enable); - colorBlendAttachment.srcColorBlendFactor = Vulkan::getBlendFactor(configuration.blendState.srcFactorRGB); - colorBlendAttachment.dstColorBlendFactor = Vulkan::getBlendFactor(configuration.blendState.dstFactorRGB); - colorBlendAttachment.colorBlendOp = Vulkan::getBlendOp(configuration.blendState.operationRGB); - colorBlendAttachment.srcAlphaBlendFactor = Vulkan::getBlendFactor(configuration.blendState.srcFactorA); - colorBlendAttachment.dstAlphaBlendFactor = Vulkan::getBlendFactor(configuration.blendState.dstFactorA); - colorBlendAttachment.alphaBlendOp = Vulkan::getBlendOp(configuration.blendState.operationA); + colorBlendAttachment.blendEnable = Vulkan::getBool(blendState.enable); + colorBlendAttachment.srcColorBlendFactor = Vulkan::getBlendFactor(blendState.srcFactorRGB); + colorBlendAttachment.dstColorBlendFactor = Vulkan::getBlendFactor(blendState.dstFactorRGB); + colorBlendAttachment.colorBlendOp = Vulkan::getBlendOp(blendState.operationRGB); + colorBlendAttachment.srcAlphaBlendFactor = Vulkan::getBlendFactor(blendState.srcFactorA); + colorBlendAttachment.dstAlphaBlendFactor = Vulkan::getBlendFactor(blendState.dstFactorA); + colorBlendAttachment.alphaBlendOp = Vulkan::getBlendOp(blendState.operationA); std::vector colorBlendAttachments(configuration.numColorAttachments, colorBlendAttachment); - if (configuration.blendState.enable) + if (blendState.enable) { for (uint32 i = 0; i < configuration.numColorAttachments; i++) { diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 1fb871f45..b9b844199 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -256,7 +256,7 @@ class Graphics final : public love::graphics::Graphics RendererInfo getRendererInfo() const override; void draw(const DrawCommand &cmd) override; void draw(const DrawIndexedCommand &cmd) override; - void drawQuads(int start, int count, const VertexAttributes &attributes, const BufferBindings &buffers, graphics::Texture *texture) override; + void drawQuads(int start, int count, VertexAttributesID attributesID, const BufferBindings &buffers, graphics::Texture *texture) override; // internal functions. @@ -276,7 +276,7 @@ class Graphics final : public love::graphics::Graphics int getVsync() const; void mapLocalUniformData(void *data, size_t size, VkDescriptorBufferInfo &bufferInfo); - VkPipeline createGraphicsPipeline(Shader *shader, const GraphicsPipelineConfiguration &configuration); + VkPipeline createGraphicsPipeline(Shader *shader, const GraphicsPipelineConfigurationCore &configuration, const GraphicsPipelineConfigurationNoDynamicState *noDynamicStateConfiguration); uint32 getDeviceApiVersion() const { return deviceApiVersion; } @@ -332,7 +332,7 @@ class Graphics final : public love::graphics::Graphics std::vector &bindingDescriptions, std::vector &attributeDescriptions); void prepareDraw( - const VertexAttributes &attributes, + VertexAttributesID attributesID, const BufferBindings &buffers, graphics::Texture *texture, PrimitiveType, CullMode); void setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture); diff --git a/src/modules/graphics/vulkan/Shader.cpp b/src/modules/graphics/vulkan/Shader.cpp index edd290b57..286c92cf0 100644 --- a/src/modules/graphics/vulkan/Shader.cpp +++ b/src/modules/graphics/vulkan/Shader.cpp @@ -201,7 +201,8 @@ void Shader::unloadVolatile() return; vgfx->queueCleanUp([shaderModules = std::move(shaderModules), device = device, descriptorSetLayout = descriptorSetLayout, pipelineLayout = pipelineLayout, - descriptorPools = descriptorPools, computePipeline = computePipeline, graphicsPipelines = std::move(graphicsPipelines)](){ + descriptorPools = descriptorPools, computePipeline = computePipeline, + graphicsPipelinesCore = std::move(graphicsPipelinesDynamicState), graphicsPipelinesFull = std::move(graphicsPipelinesNoDynamicState)]() { for (const auto &pools : descriptorPools) { for (const auto pool : pools) @@ -213,7 +214,9 @@ void Shader::unloadVolatile() vkDestroyPipelineLayout(device, pipelineLayout, nullptr); if (computePipeline != VK_NULL_HANDLE) vkDestroyPipeline(device, computePipeline, nullptr); - for (const auto& kvp : graphicsPipelines) + for (const auto &kvp : graphicsPipelinesCore) + vkDestroyPipeline(device, kvp.second, nullptr); + for (const auto &kvp : graphicsPipelinesFull) vkDestroyPipeline(device, kvp.second, nullptr); }); @@ -594,6 +597,7 @@ void Shader::compileShaders() BindingMapper bindingMapper(spv::DecorationBinding); BindingMapper ioLocationMapper(spv::DecorationLocation); + BindingMapper vertexInputLocationMapper(spv::DecorationLocation); for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) { @@ -712,25 +716,21 @@ void Shader::compileShaders() if (shaderStage == SHADERSTAGE_VERTEX) { - int nextAttributeIndex = ATTRIB_MAX_ENUM; - - // Don't skip unused inputs, vulkan still needs to have valid - // bindings for them. + // Use the mapper on known used inputs first, so their bindings get + // put into the map without being changed. for (const auto &r : shaderResources.stage_inputs) { - int index; - - BuiltinVertexAttribute builtinAttribute; - if (graphics::getConstant(r.name.c_str(), builtinAttribute)) - index = (int)builtinAttribute; - else - index = nextAttributeIndex++; - - uint32_t locationOffset; - if (!comp.get_binary_offset_for_decoration(r.id, spv::DecorationLocation, locationOffset)) - throw love::Exception("could not get binary offset for vertex attribute %s location", r.name.c_str()); + auto it = reflection.vertexInputs.find(r.name); + if (it != reflection.vertexInputs.end() && it->second >= 0) + vertexInputLocationMapper(comp, spirv, r.name, 1, r.id); + } - spirv[locationOffset] = (uint32_t)index; + for (const auto &r : shaderResources.stage_inputs) + { + // Don't skip unused inputs, vulkan still needs to have valid + // bindings for them. This will also avoid shuffling intentional + // used bindings because of the earlier loop. + int index = (int)vertexInputLocationMapper(comp, spirv, r.name, 1, r.id); DataBaseType basetype = DATA_BASETYPE_FLOAT; @@ -1203,14 +1203,26 @@ VkDescriptorSet Shader::allocateDescriptorSet() } } -VkPipeline Shader::getCachedGraphicsPipeline(Graphics *vgfx, const GraphicsPipelineConfiguration &configuration) +VkPipeline Shader::getCachedGraphicsPipeline(Graphics *vgfx, const GraphicsPipelineConfigurationCore &configuration) +{ + auto it = graphicsPipelinesDynamicState.find(configuration); + if (it != graphicsPipelinesDynamicState.end()) + return it->second; + + VkPipeline pipeline = vgfx->createGraphicsPipeline(this, configuration, nullptr); + graphicsPipelinesDynamicState.insert({ configuration, pipeline }); + + return pipeline; +} + +VkPipeline Shader::getCachedGraphicsPipeline(Graphics *vgfx, const GraphicsPipelineConfigurationFull &configuration) { - auto it = graphicsPipelines.find(configuration); - if (it != graphicsPipelines.end()) + auto it = graphicsPipelinesNoDynamicState.find(configuration); + if (it != graphicsPipelinesNoDynamicState.end()) return it->second; - VkPipeline pipeline = vgfx->createGraphicsPipeline(this, configuration); - graphicsPipelines.insert({ configuration, pipeline }); + VkPipeline pipeline = vgfx->createGraphicsPipeline(this, configuration.core, &configuration.noDynamicState); + graphicsPipelinesNoDynamicState.insert({ configuration, pipeline }); return pipeline; } diff --git a/src/modules/graphics/vulkan/Shader.h b/src/modules/graphics/vulkan/Shader.h index 0477ac3a3..4d8c903d9 100644 --- a/src/modules/graphics/vulkan/Shader.h +++ b/src/modules/graphics/vulkan/Shader.h @@ -46,43 +46,67 @@ namespace graphics namespace vulkan { -struct GraphicsPipelineConfiguration +struct GraphicsPipelineConfigurationCore { VkRenderPass renderPass; - VertexAttributes vertexAttributes; + VertexAttributesID attributesID; bool wireFrame; - BlendState blendState; + uint32 blendStateKey; ColorChannelMask colorChannelMask; VkSampleCountFlagBits msaaSamples; uint32_t numColorAttachments; PrimitiveType primitiveType; uint64 packedColorAttachmentFormats; - struct DynamicState + GraphicsPipelineConfigurationCore() { - CullMode cullmode = CULL_NONE; - Winding winding = WINDING_MAX_ENUM; - StencilAction stencilAction = STENCIL_MAX_ENUM; - CompareMode stencilCompare = COMPARE_MAX_ENUM; - DepthState depthState{}; - } dynamicState; - - GraphicsPipelineConfiguration() + memset(this, 0, sizeof(GraphicsPipelineConfigurationCore)); + } + + bool operator==(const GraphicsPipelineConfigurationCore &other) const + { + return memcmp(this, &other, sizeof(GraphicsPipelineConfigurationCore)) == 0; + } +}; + +struct GraphicsPipelineConfigurationCoreHasher +{ + size_t operator() (const GraphicsPipelineConfigurationCore &configuration) const + { + return XXH32(&configuration, sizeof(GraphicsPipelineConfigurationCore), 0); + } +}; + +struct GraphicsPipelineConfigurationNoDynamicState +{ + CullMode cullmode = CULL_NONE; + Winding winding = WINDING_MAX_ENUM; + StencilAction stencilAction = STENCIL_MAX_ENUM; + CompareMode stencilCompare = COMPARE_MAX_ENUM; + DepthState depthState{}; +}; + +struct GraphicsPipelineConfigurationFull +{ + GraphicsPipelineConfigurationCore core; + GraphicsPipelineConfigurationNoDynamicState noDynamicState; + + GraphicsPipelineConfigurationFull() { - memset(this, 0, sizeof(GraphicsPipelineConfiguration)); + memset(this, 0, sizeof(GraphicsPipelineConfigurationFull)); } - bool operator==(const GraphicsPipelineConfiguration &other) const + bool operator==(const GraphicsPipelineConfigurationFull &other) const { - return memcmp(this, &other, sizeof(GraphicsPipelineConfiguration)) == 0; + return memcmp(this, &other, sizeof(GraphicsPipelineConfigurationFull)) == 0; } }; -struct GraphicsPipelineConfigurationHasher +struct GraphicsPipelineConfigurationFullHasher { - size_t operator() (const GraphicsPipelineConfiguration &configuration) const + size_t operator() (const GraphicsPipelineConfigurationFull &configuration) const { - return XXH32(&configuration, sizeof(GraphicsPipelineConfiguration), 0); + return XXH32(&configuration, sizeof(GraphicsPipelineConfigurationFull), 0); } }; @@ -136,7 +160,8 @@ class Shader final void setMainTex(graphics::Texture *texture); - VkPipeline getCachedGraphicsPipeline(Graphics *vgfx, const GraphicsPipelineConfiguration &configuration); + VkPipeline getCachedGraphicsPipeline(Graphics *vgfx, const GraphicsPipelineConfigurationCore &configuration); + VkPipeline getCachedGraphicsPipeline(Graphics *vgfx, const GraphicsPipelineConfigurationFull &configuration); private: void compileShaders(); @@ -183,7 +208,8 @@ class Shader final std::unordered_map attributes; - std::unordered_map graphicsPipelines; + std::unordered_map graphicsPipelinesDynamicState; + std::unordered_map graphicsPipelinesNoDynamicState; uint32_t currentFrame = 0; uint32_t currentDescriptorPool = 0; diff --git a/src/modules/graphics/wrap_Buffer.cpp b/src/modules/graphics/wrap_Buffer.cpp index 3f6d4960a..5894c9896 100644 --- a/src/modules/graphics/wrap_Buffer.cpp +++ b/src/modules/graphics/wrap_Buffer.cpp @@ -404,9 +404,15 @@ static int w_Buffer_getFormat(lua_State *L) lua_pushinteger(L, member.decl.arrayLength); lua_setfield(L, -2, "arraylength"); + lua_pushinteger(L, member.decl.bindingLocation); + lua_setfield(L, -2, "location"); + lua_pushinteger(L, member.offset); lua_setfield(L, -2, "offset"); + lua_pushinteger(L, member.size); + lua_setfield(L, -2, "size"); + lua_rawseti(L, -2, i + 1); } diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index fac1a339b..7ec650b09 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -56,7 +56,7 @@ namespace graphics static int luax_checkgraphicscreated(lua_State *L) { if (!instance()->isCreated()) - return luaL_error(L, "love.graphics cannot function without a window!"); + return luaL_error(L, "love.graphics cannot function without a window."); return 0; } @@ -1599,6 +1599,11 @@ int w_newShader(lua_State *L) luax_markdeprecated(L, 1, "texture2D() or textureCube() function calls in shader code", API_CUSTOM, DEPRECATED_REPLACED, "texture() function calls"); if (shader->isUsingDeprecatedTextureUniform()) luax_markdeprecated(L, 1, "'texture' uniform variable name in shader code", API_CUSTOM, DEPRECATED_NO_REPLACEMENT, ""); + if (!shader->getUnsetVertexInputLocationsString().empty()) + { + std::string str = "vertex input attribute(s) " + shader->getUnsetVertexInputLocationsString() + " without a 'location' layout qualifier in shader code"; + luax_markdeprecated(L, 1, str.c_str(), API_CUSTOM, DEPRECATED_REPLACED, "layout(location = #) qualifier for vertex inputs"); + } luax_pushtype(L, shader); shader->release(); } @@ -1632,6 +1637,11 @@ int w_newComputeShader(lua_State* L) luax_markdeprecated(L, 1, "texture2D() or textureCube() function calls in shader code", API_CUSTOM, DEPRECATED_REPLACED, "texture() function calls"); if (shader->isUsingDeprecatedTextureUniform()) luax_markdeprecated(L, 1, "'texture' uniform variable name in shader code", API_CUSTOM, DEPRECATED_NO_REPLACEMENT, ""); + if (!shader->getUnsetVertexInputLocationsString().empty()) + { + std::string str = "vertex input attribute(s) " + shader->getUnsetVertexInputLocationsString() + " without a 'location' layout qualifier in shader code"; + luax_markdeprecated(L, 1, str.c_str(), API_CUSTOM, DEPRECATED_REPLACED, "layout(location = #) qualifier for vertex inputs"); + } luax_pushtype(L, shader); shader->release(); } @@ -1709,7 +1719,7 @@ static void luax_optbuffersettings(lua_State *L, int idx, Buffer::Settings &sett lua_pop(L, 1); } -static Buffer::DataDeclaration luax_checkdatadeclaration(lua_State* L, int formattableidx, int arrayindex, int declindex, bool requirename) +static Buffer::DataDeclaration luax_checkdatadeclaration(lua_State* L, int formattableidx, int arrayindex, int declindex, bool requirename, bool requirelocation) { Buffer::DataDeclaration decl("", DATAFORMAT_MAX_ENUM); @@ -1744,10 +1754,24 @@ static Buffer::DataDeclaration luax_checkdatadeclaration(lua_State* L, int forma decl.arrayLength = luax_intflag(L, declindex, "arraylength", 0); + lua_getfield(L, declindex, "location"); + if (requirelocation && lua_type(L, -1) != LUA_TNUMBER) + { + std::ostringstream ss; + ss << "'location' field expected in array element #"; + ss << arrayindex; + ss << " of format table"; + std::string str = ss.str(); + luaL_argerror(L, formattableidx, str.c_str()); + } + else if (!lua_isnoneornil(L, -1)) + decl.bindingLocation = luaL_checkint(L, -1); + lua_pop(L, 1); + return decl; } -static void luax_checkbufferformat(lua_State *L, int idx, std::vector &format) +static void luax_checkbufferformat(lua_State *L, int idx, const Buffer::Settings &settings, std::vector &format) { if (lua_type(L, idx) == LUA_TSTRING) { @@ -1759,6 +1783,8 @@ static void luax_checkbufferformat(lua_State *L, int idx, std::vectorhasLegacyVertexBindings()) + { + std::string names; + + for (const auto &member : buffer->getDataMembers()) + { + if (member.decl.bindingLocation < 0) + { + if (names.empty()) + names = member.decl.name; + else + names += ", " + member.decl.name; + } + } + + luax_markdeprecated(L, 1, "vertex format 'name' fields in Meshes and Buffers", API_CUSTOM, DEPRECATED_REPLACED, "'location' field containing a binding location number value."); + } +} + static Buffer *luax_newbuffer(lua_State *L, int idx, Buffer::Settings settings, const std::vector &format) { size_t arraylength = 0; @@ -1821,6 +1868,8 @@ static Buffer *luax_newbuffer(lua_State *L, int idx, Buffer::Settings settings, Buffer *b = nullptr; luax_catchexcept(L, [&] { b = instance()->newBuffer(settings, format, initialdata, bytesize, arraylength); }); + luax_validatebuffervertexbindings(L, b); + if (lua_istable(L, idx)) { Buffer::Mapper mapper(*b); @@ -1896,7 +1945,7 @@ int w_newBuffer(lua_State *L) luax_optbuffersettings(L, 3, settings); std::vector format; - luax_checkbufferformat(L, 1, format); + luax_checkbufferformat(L, 1, settings, format); Buffer *b = luax_newbuffer(L, 2, settings, format); @@ -2005,7 +2054,7 @@ static Mesh *newCustomMesh(lua_State *L) lua_pop(L, 1); if (hasformatfield || luax_objlen(L, -1) == 0) - decl = luax_checkdatadeclaration(L, 1, i, -1, true); + decl = luax_checkdatadeclaration(L, 1, i, -1, false, true); else { // Legacy format arguments: {name, datatype, components} @@ -2013,7 +2062,7 @@ static Mesh *newCustomMesh(lua_State *L) lua_rawgeti(L, -j, j); decl.name = luaL_checkstring(L, -3); - const char* tname = luaL_checkstring(L, -2); + const char *tname = luaL_checkstring(L, -2); int components = (int)luaL_checkinteger(L, -1); // Check deprecated format names. @@ -2053,7 +2102,7 @@ static Mesh *newCustomMesh(lua_State *L) lua_pop(L, 3); - luax_markdeprecated(L, 1, "vertex format array values in love.graphics.newMesh", API_CUSTOM, DEPRECATED_REPLACED, "named table fields 'format' and 'name'"); + luax_markdeprecated(L, 1, "vertex format array values in love.graphics.newMesh", API_CUSTOM, DEPRECATED_REPLACED, "named table fields 'format' and 'location'"); } lua_pop(L, 1); @@ -2124,6 +2173,9 @@ static Mesh *newCustomMesh(lua_State *L) t->flush(); } + if (t->getVertexBuffer() != nullptr) + luax_validatebuffervertexbindings(L, t->getVertexBuffer()); + return t; } @@ -2147,7 +2199,7 @@ static bool luax_isbufferattributetable(lua_State* L, int idx) static Mesh::BufferAttribute luax_checkbufferattributetable(lua_State *L, int idx) { - Mesh::BufferAttribute attrib = {}; + Mesh::BufferAttribute attrib; attrib.step = STEP_PER_VERTEX; attrib.enabled = true; @@ -2156,8 +2208,13 @@ static Mesh::BufferAttribute luax_checkbufferattributetable(lua_State *L, int id attrib.buffer = luax_checkbuffer(L, -1); lua_pop(L, 1); + lua_getfield(L, idx, "location"); + attrib.bindingLocation = luaL_checkint(L, -1); + lua_pop(L, 1); + lua_getfield(L, idx, "name"); - attrib.name = luax_checkstring(L, -1); + if (!lua_isnoneornil(L, -1)) + attrib.name = luax_checkstring(L, -1); lua_pop(L, 1); lua_getfield(L, idx, "step"); @@ -2169,6 +2226,13 @@ static Mesh::BufferAttribute luax_checkbufferattributetable(lua_State *L, int id } lua_pop(L, 1); + lua_getfield(L, idx, "locationinbuffer"); + if (!lua_isnoneornil(L, -1)) + attrib.bindingLocationInBuffer = luaL_checkint(L, -1); + else + attrib.bindingLocationInBuffer = attrib.bindingLocation; + lua_pop(L, 1); + lua_getfield(L, idx, "nameinbuffer"); if (!lua_isnoneornil(L, -1)) attrib.nameInBuffer = luax_checkstring(L, -1); @@ -2197,6 +2261,10 @@ static Mesh* newMeshFromBuffers(lua_State *L) Mesh *t = nullptr; luax_catchexcept(L, [&]() { t = instance()->newMesh(attributes, drawmode); }); + + if (t->getVertexBuffer() != nullptr) + luax_validatebuffervertexbindings(L, t->getVertexBuffer()); + return t; } diff --git a/src/modules/graphics/wrap_Mesh.cpp b/src/modules/graphics/wrap_Mesh.cpp index e36e7a453..0cc96d29b 100644 --- a/src/modules/graphics/wrap_Mesh.cpp +++ b/src/modules/graphics/wrap_Mesh.cpp @@ -260,6 +260,9 @@ int w_Mesh_getVertexFormat(lua_State *L) lua_pushstring(L, member.decl.name.c_str()); lua_setfield(L, -2, "name"); + lua_pushnumber(L, member.decl.bindingLocation); + lua_setfield(L, -2, "location"); + const char *formatstr = "unknown"; getConstant(member.decl.format, formatstr); lua_pushstring(L, formatstr); @@ -280,18 +283,34 @@ int w_Mesh_getVertexFormat(lua_State *L) int w_Mesh_setAttributeEnabled(lua_State *L) { Mesh *t = luax_checkmesh(L, 1); - const char *name = luaL_checkstring(L, 2); bool enable = luax_checkboolean(L, 3); - luax_catchexcept(L, [&](){ t->setAttributeEnabled(name, enable); }); + if (lua_type(L, 2) == LUA_TSTRING) + { + const char *name = luaL_checkstring(L, 2); + luax_catchexcept(L, [&](){ t->setAttributeEnabled(name, enable); }); + } + else + { + int location = luaL_checkint(L, 2); + luax_catchexcept(L, [&](){ t->setAttributeEnabled(location, enable); }); + } return 0; } int w_Mesh_isAttributeEnabled(lua_State *L) { Mesh *t = luax_checkmesh(L, 1); - const char *name = luaL_checkstring(L, 2); bool enabled = false; - luax_catchexcept(L, [&](){ enabled = t->isAttributeEnabled(name); }); + if (lua_type(L, 2) == LUA_TSTRING) + { + const char *name = luaL_checkstring(L, 2); + luax_catchexcept(L, [&](){ enabled = t->isAttributeEnabled(name); }); + } + else + { + int location = luaL_checkint(L, 2); + luax_catchexcept(L, [&](){ enabled = t->isAttributeEnabled(location); }); + } lua_pushboolean(L, enabled); return 1; } @@ -299,7 +318,13 @@ int w_Mesh_isAttributeEnabled(lua_State *L) int w_Mesh_attachAttribute(lua_State *L) { Mesh *t = luax_checkmesh(L, 1); - const char *name = luaL_checkstring(L, 2); + + const char *name = nullptr; + int location = -1; + if (lua_type(L, 2) == LUA_TSTRING) + name = luaL_checkstring(L, 2); + else + location = luaL_checkint(L, 2); Buffer *buffer = nullptr; Mesh *mesh = nullptr; @@ -320,10 +345,19 @@ int w_Mesh_attachAttribute(lua_State *L) if (stepstr != nullptr && !getConstant(stepstr, step)) return luax_enumerror(L, "vertex attribute step", getConstants(step), stepstr); - const char *attachname = luaL_optstring(L, 5, name); + const char *attachname = name; + int attachlocation = location; + if (name != nullptr) + attachname = luaL_optstring(L, 5, name); + else + attachlocation = luaL_optint(L, 5, location); + int startindex = (int) luaL_optinteger(L, 6, 1) - 1; - luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, mesh, attachname, startindex, step); }); + if (name != nullptr) + luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, mesh, attachname, startindex, step); }); + else + luax_catchexcept(L, [&]() { t->attachAttribute(location, buffer, mesh, attachlocation, startindex, step); }); return 0; } @@ -348,26 +382,32 @@ int w_Mesh_getAttachedAttributes(lua_State *L) { const auto &attrib = attributes[i]; - lua_createtable(L, 5, 0); + lua_createtable(L, 0, 7); luax_pushstring(L, attrib.name); - lua_rawseti(L, -1, 1); + lua_setfield(L, -2, "name"); + + lua_pushnumber(L, attrib.bindingLocation); + lua_setfield(L, -2, "location"); luax_pushtype(L, attrib.buffer.get()); - lua_rawseti(L, -1, 2); + lua_setfield(L, -2, "buffer"); const char *stepstr = nullptr; if (!getConstant(attrib.step, stepstr)) return luaL_error(L, "Invalid vertex attribute step."); lua_pushstring(L, stepstr); - lua_rawseti(L, -1, 3); + lua_setfield(L, -2, "step"); const Buffer::DataMember &member = attrib.buffer->getDataMember(attrib.indexInBuffer); luax_pushstring(L, member.decl.name); - lua_rawseti(L, -1, 4); + lua_setfield(L, -2, "nameinbuffer"); + + lua_pushnumber(L, member.decl.bindingLocation); + lua_setfield(L, -2, "locationinbuffer"); lua_pushinteger(L, attrib.startArrayIndex + 1); - lua_rawseti(L, -1, 5); + lua_setfield(L, -2, "startindex"); lua_rawseti(L, -1, i + 1); } diff --git a/testing/tests/graphics.lua b/testing/tests/graphics.lua index 34d74410c..7a03de23e 100644 --- a/testing/tests/graphics.lua +++ b/testing/tests/graphics.lua @@ -13,9 +13,9 @@ love.test.graphics.Buffer = function(test) -- setup vertex data and create some buffers local vertexformat = { - {name="VertexPosition", format="floatvec2"}, - {name="VertexTexCoord", format="floatvec2"}, - {name="VertexColor", format="unorm8vec4"}, + {name="VertexPosition", format="floatvec2", location=0}, + {name="VertexTexCoord", format="floatvec2", location=1}, + {name="VertexColor", format="unorm8vec4", location=2}, } local vertexdata = { {0, 0, 0, 0, 1, 0, 1, 1}, @@ -529,11 +529,11 @@ love.test.graphics.Mesh = function(test) -- check using custom attributes local mesh2 = love.graphics.newMesh({ - { name = 'VertexPosition', format = 'floatvec2'}, - { name = 'VertexTexCoord', format = 'floatvec2'}, - { name = 'VertexColor', format = 'floatvec4'}, - { name = 'CustomValue1', format = 'floatvec2'}, - { name = 'CustomValue2', format = 'uint16'} + { name = 'VertexPosition', format = 'floatvec2', location = 0}, + { name = 'VertexTexCoord', format = 'floatvec2', location = 1}, + { name = 'VertexColor', format = 'floatvec4', location = 2}, + { name = 'CustomValue1', format = 'floatvec2', location = 3}, + { name = 'CustomValue2', format = 'uint16', location = 4} }, { { 0, 0, 0, 0, 1, 0, 0, 1, 2, 1, 1005 }, { image:getWidth(), 0, 1, 0, 0, 1, 0, 0, 2, 2, 2005 }, @@ -962,8 +962,8 @@ love.test.graphics.Shader = function(test) flat varying ivec4 VaryingInt; #ifdef VERTEX - in vec4 VertexPosition; - in ivec4 IntAttributeUnused; + layout(location = 0) in vec4 VertexPosition; + layout(location = 1) in ivec4 IntAttributeUnused; void vertexmain() {