diff --git a/CMakeLists.txt b/CMakeLists.txt index c9d29ddc8..c9cb24246 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,7 @@ set(LOVE_SRC_COMMON src/common/Optional.h src/common/pixelformat.cpp src/common/pixelformat.h + src/common/Range.h src/common/Reference.cpp src/common/Reference.h src/common/runtime.cpp diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index e7b11a9bb..bde7abb15 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -829,6 +829,7 @@ FAB2D5AA1AABDD8A008224A4 /* TrueTypeRasterizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */; }; FAB2D5AB1AABDD8A008224A4 /* TrueTypeRasterizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */; }; FAB2D5AC1AABDD8A008224A4 /* TrueTypeRasterizer.h in Headers */ = {isa = PBXBuildFile; fileRef = FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */; }; + FAB922C6257D99EF0035DAD6 /* Range.h in Headers */ = {isa = PBXBuildFile; fileRef = FAB922C3257D99EF0035DAD6 /* Range.h */; }; FABDA9762552448200B5C523 /* b2_joint.h in Headers */ = {isa = PBXBuildFile; fileRef = FABDA9112552448200B5C523 /* b2_joint.h */; }; FABDA9772552448200B5C523 /* b2_shape.h in Headers */ = {isa = PBXBuildFile; fileRef = FABDA9122552448200B5C523 /* b2_shape.h */; }; FABDA9782552448200B5C523 /* b2_block_allocator.h in Headers */ = {isa = PBXBuildFile; fileRef = FABDA9132552448200B5C523 /* b2_block_allocator.h */; }; @@ -1828,6 +1829,7 @@ FAB17BF41ABFC4B100F9BA27 /* lz4hc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lz4hc.h; sourceTree = ""; }; FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrueTypeRasterizer.cpp; sourceTree = ""; }; FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrueTypeRasterizer.h; sourceTree = ""; }; + FAB922C3257D99EF0035DAD6 /* Range.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Range.h; sourceTree = ""; }; FABDA9112552448200B5C523 /* b2_joint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = b2_joint.h; sourceTree = ""; }; FABDA9122552448200B5C523 /* b2_shape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = b2_shape.h; sourceTree = ""; }; FABDA9132552448200B5C523 /* b2_block_allocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = b2_block_allocator.h; sourceTree = ""; }; @@ -2223,6 +2225,7 @@ FAF1889C1E9DA834008C1479 /* Optional.h */, FA9D8DCF1DEB56C3002CD881 /* pixelformat.cpp */, FA9D8DD01DEB56C3002CD881 /* pixelformat.h */, + FAB922C3257D99EF0035DAD6 /* Range.h */, FA0B790C1A958E3B000E1D17 /* Reference.cpp */, FA0B790D1A958E3B000E1D17 /* Reference.h */, FA0B790E1A958E3B000E1D17 /* runtime.cpp */, @@ -4158,6 +4161,7 @@ FA41A3CA1C0A1F950084430C /* ASTCHandler.h in Headers */, FADF54041E3D77B500012CC0 /* wrap_Text.h in Headers */, FA0B7ED31A95902C000E1D17 /* wrap_ThreadModule.h in Headers */, + FAB922C6257D99EF0035DAD6 /* Range.h in Headers */, FAC756F61E4F99B400B91289 /* Effect.h in Headers */, FA0B7ADD1A958EA3000E1D17 /* gladfuncs.hpp in Headers */, FAF1405D1E20934C00F898D2 /* intermediate.h in Headers */, diff --git a/src/common/Range.h b/src/common/Range.h new file mode 100644 index 000000000..d150919f8 --- /dev/null +++ b/src/common/Range.h @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2006-2020 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#pragma once + +#include +#include +#include + +namespace love +{ + +struct Range +{ + size_t first; + size_t last; + + Range() + : first(std::numeric_limits::max()) + , last(0) + {} + + Range(size_t offset, size_t size) + : first(offset) + , last(offset + size - 1) + {} + + bool isValid() const { return first <= last; } + + void invalidate() + { + first = std::numeric_limits::max(); + last = 0; + } + + size_t getMin() const { return first; } + size_t getMax() const { return last; } + + size_t getOffset() const { return first; } + size_t getSize() const { return (last - first) + 1; } + + bool contains(const Range &other) const + { + return first <= other.first && last >= other.last; + } + + void encapsulate(size_t index) + { + first = std::min(first, index); + last = std::max(last, index); + } + + void encapsulate(size_t offset, size_t size) + { + first = std::min(first, offset); + last = std::max(last, offset + size - 1); + } + + void encapsulate(const Range &other) + { + first = std::min(first, other.first); + last = std::max(last, other.last); + } +}; + +} // love diff --git a/src/modules/graphics/Buffer.cpp b/src/modules/graphics/Buffer.cpp index 6124e515d..c9c3f8a5f 100644 --- a/src/modules/graphics/Buffer.cpp +++ b/src/modules/graphics/Buffer.cpp @@ -34,7 +34,6 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector Mesh::getDefaultVertexFormat() love::Type Mesh::type("Mesh", &Drawable::type); Mesh::Mesh(graphics::Graphics *gfx, const std::vector &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferUsage usage) - : vertexBuffer(nullptr) - , vertexCount(0) - , vertexStride(0) - , vertexScratchBuffer(nullptr) - , indexBuffer(nullptr) - , useIndexBuffer(false) - , indexCount(0) - , indexDataType(INDEX_UINT16) - , primitiveType(drawmode) - , rangeStart(-1) - , rangeCount(-1) + : primitiveType(drawmode) { - Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ, usage); - vertexBuffer.set(gfx->newBuffer(settings, vertexformat, data, datasize, 0), Acquire::NORETAIN); + try + { + vertexData = new uint8[datasize]; + } + catch (std::exception &) + { + throw love::Exception("Out of memory"); + } + + memcpy(vertexData, data, datasize); + + Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage); + vertexBuffer.set(gfx->newBuffer(settings, vertexformat, vertexData, datasize, 0), Acquire::NORETAIN); vertexCount = vertexBuffer->getArrayLength(); vertexStride = vertexBuffer->getArrayStride(); @@ -68,27 +69,17 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector & setupAttachedAttributes(); indexDataType = getIndexDataTypeFromMax(vertexCount); - - vertexScratchBuffer = new char[vertexStride]; } Mesh::Mesh(graphics::Graphics *gfx, const std::vector &vertexformat, int vertexcount, PrimitiveType drawmode, BufferUsage usage) - : vertexBuffer(nullptr) - , vertexCount((size_t) vertexcount) - , vertexStride(0) - , vertexScratchBuffer(nullptr) - , indexBuffer(nullptr) - , useIndexBuffer(false) - , indexCount(0) + : vertexCount((size_t) vertexcount) , indexDataType(getIndexDataTypeFromMax(vertexcount)) , primitiveType(drawmode) - , rangeStart(-1) - , rangeCount(-1) { if (vertexcount <= 0) throw love::Exception("Invalid number of vertices (%d).", vertexcount); - Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY | Buffer::MAP_READ, usage); + Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage); vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN); vertexStride = vertexBuffer->getArrayStride(); @@ -96,25 +87,21 @@ Mesh::Mesh(graphics::Graphics *gfx, const std::vector & setupAttachedAttributes(); - memset(vertexBuffer->map(), 0, vertexBuffer->getSize()); - vertexBuffer->setMappedRangeModified(0, vertexBuffer->getSize()); - vertexBuffer->unmap(); + try + { + vertexData = new uint8[vertexBuffer->getSize()]; + } + catch (std::exception &) + { + throw love::Exception("Out of memory"); + } - vertexScratchBuffer = new char[vertexStride]; + memset(vertexData, 0, vertexBuffer->getSize()); + vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData); } Mesh::Mesh(const std::vector &attributes, PrimitiveType drawmode) - : vertexBuffer(nullptr) - , vertexCount(0) - , vertexStride(0) - , vertexScratchBuffer(nullptr) - , indexBuffer(nullptr) - , useIndexBuffer(false) - , indexCount(0) - , indexDataType(INDEX_UINT16) - , primitiveType(drawmode) - , rangeStart(-1) - , rangeCount(-1) + : primitiveType(drawmode) { if (attributes.size() == 0) throw love::Exception("At least one buffer attribute must be specified in this constructor."); @@ -139,7 +126,9 @@ Mesh::Mesh(const std::vector &attributes, PrimitiveType d Mesh::~Mesh() { - delete vertexScratchBuffer; + delete vertexData; + if (indexData != nullptr) + free(indexData); } void Mesh::setupAttachedAttributes() @@ -151,7 +140,7 @@ void Mesh::setupAttachedAttributes() if (getAttachedAttributeIndex(name) != -1) throw love::Exception("Duplicate vertex attribute name: %s", name.c_str()); - attachedAttributes.push_back({name, vertexBuffer, (int) i, STEP_PER_VERTEX, true}); + attachedAttributes.push_back({name, vertexBuffer, nullptr, (int) i, STEP_PER_VERTEX, true}); } } @@ -166,89 +155,19 @@ int Mesh::getAttachedAttributeIndex(const std::string &name) const return -1; } -void Mesh::setVertex(size_t vertindex, const void *data, size_t datasize) +void *Mesh::checkVertexDataOffset(size_t vertindex, size_t *byteoffset) { if (vertindex >= vertexCount) throw love::Exception("Invalid vertex index: %ld", vertindex + 1); - if (vertexBuffer.get() == nullptr) - throw love::Exception("setVertex can only be called on a Mesh which owns its own vertex buffer."); + if (vertexData == nullptr) + throw love::Exception("Mesh must own its own vertex buffer."); size_t offset = vertindex * vertexStride; - size_t size = std::min(datasize, vertexStride); - uint8 *bufferdata = (uint8 *) vertexBuffer->map(); - memcpy(bufferdata + offset, data, size); - - vertexBuffer->setMappedRangeModified(offset, size); -} - -size_t Mesh::getVertex(size_t vertindex, void *data, size_t datasize) -{ - if (vertindex >= vertexCount) - throw love::Exception("Invalid vertex index: %ld", vertindex + 1); - - if (vertexBuffer.get() == nullptr) - throw love::Exception("getVertex can only be called on a Mesh which owns its own vertex buffer."); - - size_t offset = vertindex * vertexStride; - size_t size = std::min(datasize, vertexStride); - - // We're relying on map() returning read/write data... ew. - const uint8 *bufferdata = (const uint8 *) vertexBuffer->map(); - memcpy(data, bufferdata + offset, size); - - return size; -} - -void *Mesh::getVertexScratchBuffer() -{ - return vertexScratchBuffer; -} - -void Mesh::setVertexAttribute(size_t vertindex, int attribindex, const void *data, size_t datasize) -{ - if (vertindex >= vertexCount) - throw love::Exception("Invalid vertex index: %ld", vertindex + 1); - - if (attribindex >= (int) vertexFormat.size()) - throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1); - - if (vertexBuffer.get() == nullptr) - throw love::Exception("setVertexAttribute can only be called on a Mesh which owns its own vertex buffer."); - - const auto &member = vertexFormat[attribindex]; - - size_t offset = vertindex * vertexStride + member.offset; - size_t size = std::min(datasize, member.info.size); - - uint8 *bufferdata = (uint8 *) vertexBuffer->map(); - memcpy(bufferdata + offset, data, size); - - vertexBuffer->setMappedRangeModified(offset, size); -} - -size_t Mesh::getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize) -{ - if (vertindex >= vertexCount) - throw love::Exception("Invalid vertex index: %ld", vertindex + 1); - - if (attribindex >= (int) vertexFormat.size()) - throw love::Exception("Invalid vertex attribute index: %d", attribindex + 1); - - if (vertexBuffer.get() == nullptr) - throw love::Exception("getVertexAttribute can only be called on a Mesh which owns its own vertex buffer."); - - const auto &member = vertexFormat[attribindex]; - - size_t offset = vertindex * vertexStride + member.offset; - size_t size = std::min(datasize, member.info.size); - - // We're relying on map() returning read/write data... ew. - const uint8 *bufferdata = (const uint8 *) vertexBuffer->map(); - memcpy(data, bufferdata + offset, size); - - return size; + if (byteoffset != nullptr) + *byteoffset = offset; + return vertexData + offset; } size_t Mesh::getVertexCount() const @@ -289,7 +208,7 @@ bool Mesh::isAttributeEnabled(const std::string &name) const return attachedAttributes[index].enabled; } -void Mesh::attachAttribute(const std::string &name, Buffer *buffer, const std::string &attachname, AttributeStep step) +void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, AttributeStep step) { if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0) throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute."); @@ -309,6 +228,7 @@ void Mesh::attachAttribute(const std::string &name, Buffer *buffer, const std::s newattrib.name = name; newattrib.buffer = buffer; + newattrib.mesh = mesh; newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true; newattrib.indexInBuffer = buffer->getDataMemberIndex(attachname); newattrib.step = step; @@ -331,7 +251,7 @@ bool Mesh::detachAttribute(const std::string &name) attachedAttributes.erase(attachedAttributes.begin() + index); if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(name) != -1) - attachAttribute(name, vertexBuffer, name); + attachAttribute(name, vertexBuffer, nullptr, name); return true; } @@ -341,36 +261,49 @@ const std::vector &Mesh::getAttachedAttributes() const return attachedAttributes; } -void *Mesh::mapVertexData() +void *Mesh::getVertexData() const { - return vertexBuffer.get() != nullptr ? vertexBuffer->map() : nullptr; + return vertexData; } -void Mesh::unmapVertexData(size_t modifiedoffset, size_t modifiedsize) +void Mesh::setVertexDataModified(size_t offset, size_t size) { - if (!vertexBuffer.get()) - return; - - vertexBuffer->setMappedRangeModified(modifiedoffset, modifiedsize); - vertexBuffer->unmap(); + if (vertexData != nullptr) + modifiedVertexData.encapsulate(offset, size); } void Mesh::flush() { - if (vertexBuffer.get()) - vertexBuffer->unmap(); + if (vertexBuffer.get() && vertexData != nullptr && modifiedVertexData.isValid()) + { + if (vertexBuffer->getUsage() == BUFFERUSAGE_STREAM) + { + vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData); + } + else + { + size_t offset = modifiedVertexData.getOffset(); + size_t size = modifiedVertexData.getSize(); + vertexBuffer->fill(offset, size, vertexData + offset); + } - if (indexBuffer.get()) - indexBuffer->unmap(); + modifiedVertexData.invalidate(); + } + + if (indexDataModified && indexData != nullptr && indexBuffer != nullptr) + { + indexBuffer->fill(0, indexBuffer->getSize(), indexData); + indexDataModified = false; + } } /** * Copies index data from a vector to a mapped index buffer. **/ template -static void copyToIndexBuffer(const std::vector &indices, Buffer::Mapper &buffermap, size_t maxval) +static void copyToIndexBuffer(const std::vector &indices, void *data, size_t maxval) { - T *elems = (T *) buffermap.data; + T *elems = (T *) data; for (size_t i = 0; i < indices.size(); i++) { @@ -395,7 +328,7 @@ void Mesh::setVertexMap(const std::vector &map) { auto gfx = Module::getInstance(Module::M_GRAPHICS); auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC; - Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage); + Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, usage); indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN); } @@ -405,17 +338,15 @@ void Mesh::setVertexMap(const std::vector &map) if (!indexBuffer || indexCount == 0) return; - Buffer::Mapper ibomap(*indexBuffer); - // Fill the buffer with the index values from the vector. switch (datatype) { case INDEX_UINT16: - copyToIndexBuffer(map, ibomap, maxval); + copyToIndexBuffer(map, indexData, maxval); break; case INDEX_UINT32: default: - copyToIndexBuffer(map, ibomap, maxval); + copyToIndexBuffer(map, indexData, maxval); break; } @@ -430,7 +361,7 @@ void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasiz { auto gfx = Module::getInstance(Module::M_GRAPHICS); auto usage = vertexBuffer.get() ? vertexBuffer->getUsage() : BUFFERUSAGE_DYNAMIC; - Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_READ, usage); + Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, usage); indexBuffer.set(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN); } @@ -470,24 +401,18 @@ bool Mesh::getVertexMap(std::vector &map) const map.clear(); map.reserve(indexCount); - if (!indexBuffer || indexCount == 0) + if (indexData == nullptr || indexCount == 0) return true; - if ((indexBuffer->getMapFlags() & Buffer::MAP_READ) == 0) - return false; - - // We unmap the buffer in Mesh::draw, Mesh::setVertexMap, and Mesh::flush. - void *buffer = indexBuffer->map(); - // Fill the vector from the buffer. switch (indexDataType) { case INDEX_UINT16: - copyFromIndexBuffer(buffer, indexCount, map); + copyFromIndexBuffer(indexData, indexCount, map); break; case INDEX_UINT32: default: - copyFromIndexBuffer(buffer, indexCount, map); + copyFromIndexBuffer(indexData, indexCount, map); break; } @@ -507,6 +432,12 @@ void Mesh::setIndexBuffer(Buffer *buffer) if (buffer != nullptr) indexDataType = getIndexDataType(buffer->getDataMember(0).decl.format); + + if (indexData != nullptr) + { + free(indexData); + indexData = nullptr; + } } Buffer *Mesh::getIndexBuffer() const @@ -583,6 +514,8 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount) gfx->flushBatchedDraws(); + flush(); + if (Shader::isDefaultActive()) Shader::attachDefault(Shader::STANDARD_DEFAULT); @@ -612,8 +545,8 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount) if (attributeindex >= 0) { - // Make sure the buffer isn't mapped (sends data to GPU if needed.) - buffer->unmap(); + if (attrib.mesh.get()) + attrib.mesh->flush(); const auto &member = buffer->getDataMember(attrib.indexInBuffer); @@ -637,9 +570,6 @@ void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount) if (useIndexBuffer && indexBuffer != nullptr && indexCount > 0) { - // Make sure the index buffer isn't mapped (sends data to GPU if needed.) - indexBuffer->unmap(); - Graphics::DrawIndexedCommand cmd(&attributes, &buffers, indexBuffer); cmd.primitiveType = primitiveType; diff --git a/src/modules/graphics/Mesh.h b/src/modules/graphics/Mesh.h index 72994f051..b739238a4 100644 --- a/src/modules/graphics/Mesh.h +++ b/src/modules/graphics/Mesh.h @@ -25,6 +25,7 @@ #include "common/int.h" #include "common/math.h" #include "common/StringMap.h" +#include "common/Range.h" #include "Drawable.h" #include "Texture.h" #include "vertex.h" @@ -54,6 +55,7 @@ public: { std::string name; StrongRef buffer; + StrongRef mesh; int indexInBuffer; AttributeStep step; bool enabled; @@ -68,21 +70,10 @@ public: virtual ~Mesh(); /** - * Sets the values of all attributes at a specific vertex index in the Mesh. - * The size of the data must be less than or equal to the total size of all - * vertex attributes. + * Validates a vertex index and whether the Mesh has its own vertex buffer, + * and returns a pointer to the vertex data at the given vertex index. **/ - void setVertex(size_t vertindex, const void *data, size_t datasize); - size_t getVertex(size_t vertindex, void *data, size_t datasize); - void *getVertexScratchBuffer(); - - /** - * Sets the values for a single attribute at a specific vertex index in the - * Mesh. The size of the data must be less than or equal to the size of the - * attribute. - **/ - void setVertexAttribute(size_t vertindex, int attribindex, const void *data, size_t datasize); - size_t getVertexAttribute(size_t vertindex, int attribindex, void *data, size_t datasize); + void *checkVertexDataOffset(size_t vertindex, size_t *byteoffset); /** * Gets the total number of vertices that can be used when drawing the Mesh. @@ -114,13 +105,16 @@ public: /** * Attaches a vertex attribute from another vertex buffer to this Mesh. The * attribute will be used when drawing this Mesh. + * Attributes from other Meshes should also pass in the Mesh as an argument, + * to make sure this Mesh knows to flush the passed in Mesh's data to its + * buffer when drawing. **/ - void attachAttribute(const std::string &name, Buffer *buffer, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX); + void attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, AttributeStep step = STEP_PER_VERTEX); bool detachAttribute(const std::string &name); const std::vector &getAttachedAttributes() const; - void *mapVertexData(); - void unmapVertexData(size_t modifiedoffset = 0, size_t modifiedsize = -1); + void *getVertexData() const; + void setVertexDataModified(size_t offset, size_t size); /** * Flushes all modified data to the GPU. @@ -197,23 +191,24 @@ private: // Vertex buffer, for the vertex data. StrongRef vertexBuffer; - size_t vertexCount; - size_t vertexStride; + uint8 *vertexData = nullptr; + Range modifiedVertexData = Range(); - // Block of memory whose size is at least as large as a single vertex. Helps - // avoid memory allocations when using Mesh::setVertex etc. - char *vertexScratchBuffer; + size_t vertexCount = 0; + size_t vertexStride = 0; // Index buffer, for the vertex map. StrongRef indexBuffer; - bool useIndexBuffer; - size_t indexCount; - IndexDataType indexDataType; + uint8 *indexData = nullptr; + bool indexDataModified = false; + bool useIndexBuffer = false; + size_t indexCount = 0; + IndexDataType indexDataType = INDEX_UINT16; - PrimitiveType primitiveType; + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; - int rangeStart; - int rangeCount; + int rangeStart = -1; + int rangeCount = -1; StrongRef texture; diff --git a/src/modules/graphics/ParticleSystem.cpp b/src/modules/graphics/ParticleSystem.cpp index f074c5d7a..5039d4fdf 100644 --- a/src/modules/graphics/ParticleSystem.cpp +++ b/src/modules/graphics/ParticleSystem.cpp @@ -191,7 +191,7 @@ void ParticleSystem::createBuffers(size_t size) auto gfx = Module::getInstance(Module::M_GRAPHICS); size_t bytes = sizeof(Vertex) * size * 4; - Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, 0, BUFFERUSAGE_STREAM); + Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_STREAM); auto decl = Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub); buffer = gfx->newBuffer(settings, decl, nullptr, bytes, 0); } @@ -1043,7 +1043,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m) const Vector2 *positions = texture->getQuad()->getVertexPositions(); const Vector2 *texcoords = texture->getQuad()->getVertexTexCoords(); - Vertex *pVerts = (Vertex *) buffer->map(); + Vertex *pVerts = (Vertex *) buffer->map(Buffer::MAP_WRITE_INVALIDATE, 0, buffer->getSize()); Particle *p = pHead; bool useQuads = !quads.empty(); @@ -1079,7 +1079,7 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m) p = p->next; } - buffer->unmap(); + buffer->unmap(0, pCount * sizeof(Vertex) * 4); Graphics::TempTransform transform(gfx, m); diff --git a/src/modules/graphics/SpriteBatch.cpp b/src/modules/graphics/SpriteBatch.cpp index 285564f3e..6fd5eaefb 100644 --- a/src/modules/graphics/SpriteBatch.cpp +++ b/src/modules/graphics/SpriteBatch.cpp @@ -48,8 +48,7 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage , colorf(1.0f, 1.0f, 1.0f, 1.0f) , array_buf(nullptr) , vertex_data(nullptr) - , modified_sprite_first(LOVE_INT32_MAX) - , modified_sprite_last(0) + , modified_sprites() , range_start(-1) , range_count(-1) { @@ -74,7 +73,7 @@ SpriteBatch::SpriteBatch(Graphics *gfx, Texture *texture, int size, BufferUsage memset(vertex_data, 0, vertex_size); - Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, usage); + Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, usage); auto decl = Buffer::getCommonFormatDeclaration(vertex_format); array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN); @@ -118,8 +117,7 @@ int SpriteBatch::add(Quad *quad, const Matrix4 &m, int index /*= -1*/) verts[i].color = color; } - modified_sprite_first = std::min(modified_sprite_first, spriteindex); - modified_sprite_last = std::max(modified_sprite_last, spriteindex); + modified_sprites.encapsulate(spriteindex); // Increment counter. if (index == -1) @@ -165,8 +163,7 @@ int SpriteBatch::addLayer(int layer, Quad *quad, const Matrix4 &m, int index) verts[i].color = color; } - modified_sprite_first = std::min(modified_sprite_first, spriteindex); - modified_sprite_last = std::max(modified_sprite_last, spriteindex); + modified_sprites.encapsulate(spriteindex); // Increment counter. if (index == -1) @@ -183,19 +180,17 @@ void SpriteBatch::clear() void SpriteBatch::flush() { - if (modified_sprite_first <= modified_sprite_last) + if (modified_sprites.isValid()) { - size_t offset = modified_sprite_first * vertex_stride * 4; - size_t size = (modified_sprite_last - modified_sprite_first) * vertex_stride * 4; + size_t offset = modified_sprites.getOffset() * vertex_stride * 4; + size_t size = modified_sprites.getSize() * vertex_stride * 4; - // TODO: switch this to fill() once it gets cleaned up. - memcpy(((uint8 *)array_buf->map() + offset), vertex_data + offset, size); + if (array_buf->getUsage() == BUFFERUSAGE_STREAM) + array_buf->fill(0, array_buf->getSize(), vertex_data); + else + array_buf->fill(offset, size, vertex_data + offset); - array_buf->setMappedRangeModified(offset, size); - array_buf->unmap(); - - modified_sprite_first = LOVE_INT32_MAX; - modified_sprite_last = 0; + modified_sprites.invalidate(); } } @@ -249,7 +244,7 @@ void SpriteBatch::setBufferSize(int newsize) throw love::Exception("Out of memory."); auto gfx = Module::getInstance(Module::M_GRAPHICS); - Buffer::Settings settings(array_buf->getTypeFlags(), array_buf->getMapFlags(), array_buf->getUsage()); + Buffer::Settings settings(array_buf->getTypeFlags(), array_buf->getUsage()); auto decl = Buffer::getCommonFormatDeclaration(vertex_format); array_buf.set(gfx->newBuffer(settings, decl, nullptr, vertex_size, 0), Acquire::NORETAIN); @@ -267,7 +262,7 @@ int SpriteBatch::getBufferSize() const return size; } -void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer) +void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh) { if ((buffer->getTypeFlags() & Buffer::TYPEFLAG_VERTEX) == 0) throw love::Exception("GraphicsBuffer must be created with vertex buffer support to be used as a SpriteBatch vertex attribute."); @@ -288,6 +283,7 @@ void SpriteBatch::attachAttribute(const std::string &name, Buffer *buffer) throw love::Exception("The specified Buffer does not have a vertex attribute named '%s'", name.c_str()); newattrib.buffer = buffer; + newattrib.mesh = mesh; attached_attributes[name] = newattrib; } @@ -371,8 +367,8 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m) if (attributeindex >= 0) { - // Make sure the buffer isn't mapped (sends data to GPU if needed.) - buffer->unmap(); + if (it.second.mesh.get()) + it.second.mesh->flush(); const auto &member = buffer->getDataMember(it.second.index); diff --git a/src/modules/graphics/SpriteBatch.h b/src/modules/graphics/SpriteBatch.h index 0266f257f..813bf5d2f 100644 --- a/src/modules/graphics/SpriteBatch.h +++ b/src/modules/graphics/SpriteBatch.h @@ -30,6 +30,7 @@ #include "common/math.h" #include "common/Matrix.h" #include "common/Color.h" +#include "common/Range.h" #include "Drawable.h" #include "Mesh.h" #include "vertex.h" @@ -90,10 +91,13 @@ public: int getBufferSize() const; /** - * Attaches a specific vertex attribute from a Mesh to this SpriteBatch. + * Attaches a specific vertex attribute from a Buffer to this SpriteBatch. * The vertex attribute will be used when drawing the SpriteBatch. + * If the attribute comes from a Mesh, it should be given as an argument as + * well, to make sure the SpriteBatch flushes its data to its Buffer when + * the SpriteBatch is drawn. **/ - void attachAttribute(const std::string &name, Buffer *buffer); + void attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh); void setDrawRange(int start, int count); void setDrawRange(); @@ -107,6 +111,7 @@ private: struct AttachedAttribute { StrongRef buffer; + StrongRef mesh; int index; }; @@ -134,8 +139,7 @@ private: StrongRef array_buf; uint8 *vertex_data; - int modified_sprite_first; - int modified_sprite_last; + Range modified_sprites; std::unordered_map attached_attributes; diff --git a/src/modules/graphics/Text.cpp b/src/modules/graphics/Text.cpp index 8d3fa6dd5..3fa4d5ff3 100644 --- a/src/modules/graphics/Text.cpp +++ b/src/modules/graphics/Text.cpp @@ -33,14 +33,18 @@ love::Type Text::type("Text", &Drawable::type); Text::Text(Font *font, const std::vector &text) : font(font) , vertexAttributes(Font::vertexFormat, 0) - , vert_offset(0) - , texture_cache_id((uint32) -1) + , vertexData(nullptr) + , modifiedVertices() + , vertOffset(0) + , textureCacheID((uint32) -1) { set(text); } Text::~Text() { + if (vertexData != nullptr) + free(vertexData); } void Text::uploadVertices(const std::vector &vertices, size_t vertoffset) @@ -49,32 +53,40 @@ void Text::uploadVertices(const std::vector &vertices, size_t size_t datasize = vertices.size() * sizeof(Font::GlyphVertex); // If we haven't created a VBO or the vertices are too big, make a new one. - if (datasize > 0 && (!vertex_buffer || (offset + datasize) > vertex_buffer->getSize())) + if (datasize > 0 && (!vertexBuffer || (offset + datasize) > vertexBuffer->getSize())) { // Make it bigger than necessary to reduce potential future allocations. size_t newsize = size_t((offset + datasize) * 1.5); - if (vertex_buffer != nullptr) - newsize = std::max(size_t(vertex_buffer->getSize() * 1.5), newsize); + if (vertexBuffer != nullptr) + newsize = std::max(size_t(vertexBuffer->getSize() * 1.5), newsize); auto gfx = Module::getInstance(Module::M_GRAPHICS); - Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, 0, BUFFERUSAGE_DYNAMIC); + + Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_DYNAMIC); auto decl = Buffer::getCommonFormatDeclaration(Font::vertexFormat); - Buffer *new_buffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0); + Buffer *newbuffer = gfx->newBuffer(settings, decl, nullptr, newsize, 0); - if (vertex_buffer != nullptr) - vertex_buffer->copyTo(0, vertex_buffer->getSize(), new_buffer, 0); + void *newdata = nullptr; + if (vertexData != nullptr) + newdata = realloc(vertexData, newsize); + else + newdata = malloc(newsize); - vertex_buffer = new_buffer; + if (newdata == nullptr) + throw love::Exception("Out of memory."); + else + vertexData = (uint8 *) newdata; - vertexBuffers.set(0, vertex_buffer, 0); + vertexBuffer = newbuffer; + + vertexBuffers.set(0, vertexBuffer, 0); } - if (vertex_buffer != nullptr && datasize > 0) + if (vertexData != nullptr && datasize > 0) { - uint8 *bufferdata = (uint8 *) vertex_buffer->map(); - memcpy(bufferdata + offset, &vertices[0], datasize); - // We unmap when we draw, to avoid unnecessary full map()/unmap() calls. + memcpy(vertexData + offset, &vertices[0], datasize); + modifiedVertices.encapsulate(offset, datasize); } } @@ -82,85 +94,85 @@ void Text::regenerateVertices() { // If the font's texture cache was invalidated then we need to recreate the // text's vertices, since glyph texcoords might have changed. - if (font->getTextureCacheID() != texture_cache_id) + if (font->getTextureCacheID() != textureCacheID) { - std::vector textdata = text_data; + std::vector textdata = textData; clear(); for (const TextData &t : textdata) addTextData(t); - texture_cache_id = font->getTextureCacheID(); + textureCacheID = font->getTextureCacheID(); } } void Text::addTextData(const TextData &t) { std::vector vertices; - std::vector new_commands; + std::vector newcommands; - Font::TextInfo text_info; + Font::TextInfo textinfo; Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f); // We only have formatted text if the align mode is valid. if (t.align == Font::ALIGN_MAX_ENUM) - new_commands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &text_info); + newcommands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &textinfo); else - new_commands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &text_info); + newcommands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &textinfo); - size_t voffset = vert_offset; + size_t voffset = vertOffset; // Must be before the early exit below. - if (!t.append_vertices) + if (!t.appendVertices) { voffset = 0; - vert_offset = 0; - draw_commands.clear(); - text_data.clear(); + vertOffset = 0; + drawCommands.clear(); + textData.clear(); } if (vertices.empty()) return; - if (t.use_matrix) + if (t.useMatrix) t.matrix.transformXY(&vertices[0], &vertices[0], (int) vertices.size()); uploadVertices(vertices, voffset); - if (!new_commands.empty()) + if (!newcommands.empty()) { // The start vertex should be adjusted to account for the vertex offset. - for (Font::DrawCommand &cmd : new_commands) + for (Font::DrawCommand &cmd : newcommands) cmd.startvertex += (int) voffset; - auto firstcmd = new_commands.begin(); + auto firstcmd = newcommands.begin(); // If the first draw command in the new list has the same texture as the // last one in the existing list we're building and its vertices are // in-order, we can combine them (saving a draw call.) - if (!draw_commands.empty()) + if (!drawCommands.empty()) { - auto prevcmd = draw_commands.back(); + auto prevcmd = drawCommands.back(); if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex) { - draw_commands.back().vertexcount += firstcmd->vertexcount; + drawCommands.back().vertexcount += firstcmd->vertexcount; ++firstcmd; } } // Append the new draw commands to the list we're building. - draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end()); + drawCommands.insert(drawCommands.end(), firstcmd, newcommands.end()); } - vert_offset = voffset + vertices.size(); + vertOffset = voffset + vertices.size(); - text_data.push_back(t); - text_data.back().text_info = text_info; + textData.push_back(t); + textData.back().textInfo = textinfo; // Font::generateVertices can invalidate the font's texture cache. - if (font->getTextureCacheID() != texture_cache_id) + if (font->getTextureCacheID() != textureCacheID) regenerateVertices(); } @@ -192,15 +204,15 @@ int Text::addf(const std::vector &text, float wrap, Font::A addTextData({codepoints, wrap, align, {}, true, true, m}); - return (int) text_data.size() - 1; + return (int) textData.size() - 1; } void Text::clear() { - text_data.clear(); - draw_commands.clear(); - texture_cache_id = font->getTextureCacheID(); - vert_offset = 0; + textData.clear(); + drawCommands.clear(); + textureCacheID = font->getTextureCacheID(); + vertOffset = 0; } void Text::setFont(Font *f) @@ -209,7 +221,7 @@ void Text::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. - texture_cache_id = (uint32) -1; + textureCacheID = (uint32) -1; regenerateVertices(); } @@ -221,28 +233,28 @@ Font *Text::getFont() const int Text::getWidth(int index) const { if (index < 0) - index = std::max((int) text_data.size() - 1, 0); + index = std::max((int) textData.size() - 1, 0); - if (index >= (int) text_data.size()) + if (index >= (int) textData.size()) return 0; - return text_data[index].text_info.width; + return textData[index].textInfo.width; } int Text::getHeight(int index) const { if (index < 0) - index = std::max((int) text_data.size() - 1, 0); + index = std::max((int) textData.size() - 1, 0); - if (index >= (int) text_data.size()) + if (index >= (int) textData.size()) return 0; - return text_data[index].text_info.height; + return textData[index].textInfo.height; } void Text::draw(Graphics *gfx, const Matrix4 &m) { - if (vertex_buffer == nullptr || draw_commands.empty()) + if (vertexBuffer == nullptr || vertexData == nullptr || drawCommands.empty()) return; gfx->flushBatchedDraws(); @@ -254,18 +266,30 @@ void Text::draw(Graphics *gfx, const Matrix4 &m) Shader::current->checkMainTextureType(TEXTURE_2D, false); // Re-generate the text if the Font's texture cache was invalidated. - if (font->getTextureCacheID() != texture_cache_id) + if (font->getTextureCacheID() != textureCacheID) regenerateVertices(); int totalverts = 0; - for (const Font::DrawCommand &cmd : draw_commands) + for (const Font::DrawCommand &cmd : drawCommands) totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts); - vertex_buffer->unmap(); // Make sure all pending data is flushed to the GPU. + // Make sure all pending data is uploaded to the GPU. + if (modifiedVertices.isValid()) + { + size_t offset = modifiedVertices.getOffset(); + size_t size = modifiedVertices.getSize(); + + if (vertexBuffer->getUsage() == BUFFERUSAGE_STREAM) + vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData); + else + vertexBuffer->fill(offset, size, vertexData + offset); + + modifiedVertices.invalidate(); + } Graphics::TempTransform transform(gfx, m); - for (const Font::DrawCommand &cmd : draw_commands) + for (const Font::DrawCommand &cmd : drawCommands) gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture); } diff --git a/src/modules/graphics/Text.h b/src/modules/graphics/Text.h index ca55ec44e..424bb0859 100644 --- a/src/modules/graphics/Text.h +++ b/src/modules/graphics/Text.h @@ -22,6 +22,7 @@ // LOVE #include "common/config.h" +#include "common/Range.h" #include "Drawable.h" #include "Font.h" #include "Buffer.h" @@ -73,9 +74,9 @@ private: Font::ColoredCodepoints codepoints; float wrap; Font::AlignMode align; - Font::TextInfo text_info; - bool use_matrix; - bool append_vertices; + Font::TextInfo textInfo; + bool useMatrix; + bool appendVertices; Matrix4 matrix; }; @@ -88,16 +89,18 @@ private: VertexAttributes vertexAttributes; BufferBindings vertexBuffers; - StrongRef vertex_buffer; + StrongRef vertexBuffer; + uint8 *vertexData; + Range modifiedVertices; - std::vector draw_commands; + std::vector drawCommands; - std::vector text_data; + std::vector textData; - size_t vert_offset; + size_t vertOffset; // Used so we know when the font's texture cache is invalidated. - uint32 texture_cache_id; + uint32 textureCacheID; }; // Text diff --git a/src/modules/graphics/opengl/Buffer.cpp b/src/modules/graphics/opengl/Buffer.cpp index 61eb6e61b..43276e59f 100644 --- a/src/modules/graphics/opengl/Buffer.cpp +++ b/src/modules/graphics/opengl/Buffer.cpp @@ -22,6 +22,7 @@ #include "common/Exception.h" #include "graphics/vertex.h" +#include "Graphics.h" #include #include @@ -80,22 +81,26 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st target = OpenGL::getGLBufferType(mapType); - try + if (usage == BUFFERUSAGE_STREAM) + ownsMemoryMap = true; + + std::vector emptydata; + if (settings.zeroInitialize && data == nullptr) { - memoryMap = new char[size]; - } - catch (std::bad_alloc &) - { - throw love::Exception("Out of memory."); + try + { + emptydata.resize(getSize()); + data = emptydata.data(); + } + catch (std::exception &) + { + data = nullptr; + } } - if (data != nullptr) - memcpy(memoryMap, data, size); - - if (!load(data != nullptr)) + if (!load(data)) { unloadVolatile(); - delete[] memoryMap; throw love::Exception("Could not create buffer (out of VRAM?)"); } } @@ -103,7 +108,8 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st Buffer::~Buffer() { unloadVolatile(); - delete[] memoryMap; + if (memoryMap != nullptr && ownsMemoryMap) + free(memoryMap); } bool Buffer::loadVolatile() @@ -111,7 +117,7 @@ bool Buffer::loadVolatile() if (buffer != 0) return true; - return load(true); + return load(nullptr); } void Buffer::unloadVolatile() @@ -125,7 +131,7 @@ void Buffer::unloadVolatile() texture = 0; } -bool Buffer::load(bool restore) +bool Buffer::load(const void *initialdata) { while (glGetError() != GL_NO_ERROR) /* Clear the error buffer. */; @@ -133,11 +139,8 @@ bool Buffer::load(bool restore) glGenBuffers(1, &buffer); gl.bindBuffer(mapType, buffer); - // Copy the old buffer only if 'restore' was requested. - const GLvoid *src = restore ? memoryMap : nullptr; - - // Note that if 'src' is '0', no data will be copied. - glBufferData(target, (GLsizeiptr) getSize(), src, OpenGL::getGLBufferUsage(getUsage())); + // initialdata can be null. + glBufferData(target, (GLsizeiptr) getSize(), initialdata, OpenGL::getGLBufferUsage(getUsage())); if (getTypeFlags() & TYPEFLAG_TEXEL) { @@ -150,137 +153,104 @@ bool Buffer::load(bool restore) return (glGetError() == GL_NO_ERROR); } -void *Buffer::map() -{ - if (mapped) - return memoryMap; - - mapped = true; - - modifiedOffset = 0; - modifiedSize = 0; - isMappedDataModified = false; - - return memoryMap; -} - -void Buffer::unmapStatic(size_t offset, size_t size) +void *Buffer::map(MapType /*map*/, size_t offset, size_t size) { if (size == 0) - return; + return nullptr; - // Upload the mapped data to the buffer. - gl.bindBuffer(mapType, buffer); - glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memoryMap + offset); -} + Range r(offset, size); -void Buffer::unmapStream() -{ - GLenum glusage = OpenGL::getGLBufferUsage(getUsage()); + if (!Range(0, getSize()).contains(r)) + return nullptr; - // "orphan" current buffer to avoid implicit synchronisation on the GPU: - // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf - gl.bindBuffer(mapType, buffer); - glBufferData(target, (GLsizeiptr) getSize(), nullptr, glusage); + char *data = nullptr; -#if LOVE_WINDOWS - // TODO: Verify that this codepath is a useful optimization. - if (gl.getVendor() == OpenGL::VENDOR_INTEL) - glBufferData(target, (GLsizeiptr) getSize(), memoryMap, glusage); + if (ownsMemoryMap) + { + if (memoryMap == nullptr) + memoryMap = (char *) malloc(getSize()); + data = memoryMap; + } else -#endif - glBufferSubData(target, 0, (GLsizeiptr) getSize(), memoryMap); + { + auto gfx = Module::getInstance(Module::M_GRAPHICS); + data = (char *) gfx->getBufferMapMemory(size); + } + + if (data != nullptr) + { + mapped = true; + mappedRange = r; + if (!ownsMemoryMap) + memoryMap = data; + } + + return data; } -void Buffer::unmap() +void Buffer::unmap(size_t usedoffset, size_t usedsize) { - if (!mapped) + Range r(usedoffset, usedsize); + + if (!mapped || !mappedRange.contains(r)) return; mapped = false; - if ((mapFlags & MAP_EXPLICIT_RANGE_MODIFY) != 0) + // Orphan optimization - see fill(). + if (usage != BUFFERUSAGE_STATIC && mappedRange.first == 0 && mappedRange.getSize() == getSize()) { - if (!isMappedDataModified) - return; - - modifiedOffset = std::min(modifiedOffset, getSize() - 1); - modifiedSize = std::min(modifiedSize, getSize() - modifiedOffset); - } - else - { - modifiedOffset = 0; - modifiedSize = getSize(); + usedoffset = 0; + usedsize = getSize(); } - if (modifiedSize > 0) + char *data = memoryMap + (usedoffset - mappedRange.getOffset()); + + fill(usedoffset, usedsize, data); + + if (!ownsMemoryMap) { - switch (getUsage()) - { - case BUFFERUSAGE_STATIC: - unmapStatic(modifiedOffset, modifiedSize); - break; - case BUFFERUSAGE_STREAM: - unmapStream(); - break; - case BUFFERUSAGE_DYNAMIC: - default: - // It's probably more efficient to treat it like a streaming buffer if - // at least a third of its contents have been modified during the map(). - if (modifiedSize >= getSize() / 3) - unmapStream(); - else - unmapStatic(modifiedOffset, modifiedSize); - break; - } + auto gfx = Module::getInstance(Module::M_GRAPHICS); + gfx->releaseBufferMapMemory(memoryMap); + memoryMap = nullptr; } - - modifiedOffset = 0; - modifiedSize = 0; -} - -void Buffer::setMappedRangeModified(size_t offset, size_t modifiedsize) -{ - if (!mapped || !(mapFlags & MAP_EXPLICIT_RANGE_MODIFY)) - return; - - if (!isMappedDataModified) - { - modifiedOffset = offset; - modifiedSize = modifiedsize; - isMappedDataModified = true; - return; - } - - // We're being conservative right now by internally marking the whole range - // from the start of section a to the end of section b as modified if both - // a and b are marked as modified. - - size_t oldrangeend = modifiedOffset + modifiedSize; - modifiedOffset = std::min(modifiedOffset, offset); - - size_t newrangeend = std::max(offset + modifiedsize, oldrangeend); - modifiedSize = newrangeend - modifiedOffset; } void Buffer::fill(size_t offset, size_t size, const void *data) { - memcpy(memoryMap + offset, data, size); + if (size == 0) + return; - if (mapped) - setMappedRangeModified(offset, size); + size_t buffersize = getSize(); + + if (!Range(0, buffersize).contains(Range(offset, size))) + return; + + GLenum glusage = OpenGL::getGLBufferUsage(usage); + + gl.bindBuffer(mapType, buffer); + + if (usage != BUFFERUSAGE_STATIC && size == buffersize) + { + // "orphan" current buffer to avoid implicit synchronisation on the GPU: + // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf + gl.bindBuffer(mapType, buffer); + glBufferData(target, (GLsizeiptr) buffersize, nullptr, glusage); + +#if LOVE_WINDOWS + // TODO: Verify that this codepath is a useful optimization. + if (gl.getVendor() == OpenGL::VENDOR_INTEL) + glBufferData(target, (GLsizeiptr) buffersize, data, glusage); + else +#endif + glBufferSubData(target, 0, (GLsizeiptr) buffersize, data); + } else { - gl.bindBuffer(mapType, buffer); glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data); } } -void Buffer::copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) -{ - other->fill(otheroffset, size, memoryMap + offset); -} - } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/Buffer.h b/src/modules/graphics/opengl/Buffer.h index b0a96bb5f..db0ae85fe 100644 --- a/src/modules/graphics/opengl/Buffer.h +++ b/src/modules/graphics/opengl/Buffer.h @@ -22,6 +22,7 @@ // LOVE #include "common/config.h" +#include "common/Range.h" #include "graphics/Buffer.h" #include "graphics/Volatile.h" @@ -49,19 +50,16 @@ public: bool loadVolatile() override; void unloadVolatile() override; - void *map() override; - void unmap() override; - void setMappedRangeModified(size_t offset, size_t size) override; + void *map(MapType map, size_t offset, size_t size) override; + void unmap(size_t usedoffset, size_t usedsize) override; void fill(size_t offset, size_t size, const void *data) override; ptrdiff_t getHandle() const override { return buffer; }; ptrdiff_t getTexelBufferHandle() const override { return texture; }; - void copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) override; - private: - bool load(bool restore); + bool load(const void *initialdata); void unmapStatic(size_t offset, size_t size); void unmapStream(); @@ -77,10 +75,9 @@ private: // A pointer to mapped memory. char *memoryMap = nullptr; + bool ownsMemoryMap = false; - size_t modifiedOffset = 0; - size_t modifiedSize = 0; - bool isMappedDataModified = false; + Range mappedRange; }; // Buffer diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 56ff3aa60..08ad88b20 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -93,11 +93,22 @@ Graphics::Graphics() , mainVAO(0) , internalBackbufferFBO(0) , requestedBackbufferMSAA(0) + , bufferMapMemory(nullptr) + , bufferMapMemorySize(2 * 1024 * 1024) , defaultBuffers() , supportedFormats() { gl = OpenGL(); + try + { + bufferMapMemory = new char[bufferMapMemorySize]; + } + catch (std::exception &) + { + // Handled in getBufferMapMemory. + } + auto window = getInstance(M_WINDOW); if (window != nullptr) @@ -121,6 +132,7 @@ Graphics::Graphics() Graphics::~Graphics() { + delete[] bufferMapMemory; } const char *Graphics::getName() const @@ -328,7 +340,7 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b if (capabilities.features[FEATURE_TEXEL_BUFFER] && defaultBuffers[BUFFERTYPE_TEXEL].get() == nullptr) { - Buffer::Settings settings(Buffer::TYPEFLAG_TEXEL, 0, BUFFERUSAGE_STATIC); + Buffer::Settings settings(Buffer::TYPEFLAG_TEXEL, BUFFERUSAGE_STATIC); std::vector format = {{"", DATAFORMAT_FLOAT_VEC4, 0}}; const float texel[] = {0.0f, 0.0f, 0.0f, 1.0f}; @@ -1436,6 +1448,21 @@ void Graphics::setWireframe(bool enable) states.back().wireframe = enable; } +void *Graphics::getBufferMapMemory(size_t size) +{ + // We don't need anything more complicated because get/release calls are + // never interleaved (as of when this comment was written.) + if (bufferMapMemory == nullptr || size > bufferMapMemorySize) + return malloc(size); + return bufferMapMemory; +} + +void Graphics::releaseBufferMapMemory(void *mem) +{ + if (mem != bufferMapMemory) + free(mem); +} + Graphics::Renderer Graphics::getRenderer() const { return GLAD_ES_VERSION_2_0 ? RENDERER_OPENGLES : RENDERER_OPENGL; diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 40473327c..7f9ef56fd 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -112,6 +112,9 @@ public: // Internal use. void cleanupRenderTexture(love::graphics::Texture *texture); + void *getBufferMapMemory(size_t size); + void releaseBufferMapMemory(void *mem); + private: struct CachedFBOHasher @@ -159,6 +162,9 @@ private: GLuint internalBackbufferFBO; int requestedBackbufferMSAA; + char *bufferMapMemory; + size_t bufferMapMemorySize; + // Only needed for buffer types that can be bound to shaders. StrongRef defaultBuffers[BUFFERTYPE_MAX_ENUM]; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index ed5256f10..9f518e204 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -761,30 +761,8 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe buffer->retain(); } - bool addbuffertoarray = true; - if (info->buffers[i] != nullptr) - { - Buffer *oldbuffer = info->buffers[i]; - auto it = std::find(buffersToUnmap.begin(), buffersToUnmap.end(), oldbuffer); - if (it != buffersToUnmap.end()) - { - addbuffertoarray = false; - if (buffer != nullptr) - *it = buffer; - else - { - auto last = buffersToUnmap.end() - 1; - *it = *last; - buffersToUnmap.erase(last); - } - } - - oldbuffer->release(); - } - - if (addbuffertoarray && buffer != nullptr) - buffersToUnmap.push_back(buffer); + info->buffers[i]->release(); info->buffers[i] = buffer; @@ -916,13 +894,6 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, GLint location = builtinUniforms[BUILTIN_UNIFORMS_PER_DRAW]; if (location >= 0) glUniform4fv(location, 13, (const GLfloat *) &data); - - // TODO: Find a better place to put this. - // Buffers used in this shader can be mapped by external code without - // unmapping. We need to make sure the data on the GPU is up to date, - // otherwise the shader can read from old data. - for (Buffer *buffer : buffersToUnmap) - buffer->unmap(); } int Shader::getUniformTypeComponents(GLenum type) const diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 10591744e..393c7fb6c 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -119,8 +119,6 @@ private: std::vector> pendingUniformUpdates; - std::vector buffersToUnmap; - float lastPointSize; }; // Shader diff --git a/src/modules/graphics/wrap_Buffer.cpp b/src/modules/graphics/wrap_Buffer.cpp index 9289134f5..e15ccb01b 100644 --- a/src/modules/graphics/wrap_Buffer.cpp +++ b/src/modules/graphics/wrap_Buffer.cpp @@ -186,13 +186,6 @@ Buffer *luax_checkbuffer(lua_State *L, int idx) return luax_checktype(L, idx); } -static int w_Buffer_flush(lua_State *L) -{ - Buffer *t = luax_checkbuffer(L, 1); - t->unmap(); - return 0; -} - static int w_Buffer_setArrayData(lua_State *L) { Buffer *t = luax_checkbuffer(L, 1); @@ -223,12 +216,8 @@ static int w_Buffer_setArrayData(lua_State *L) return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - startindex, count); size_t datasize = std::min(d->getSize(), count * stride); - char *bytedata = (char *) t->map() + offset; - memcpy(bytedata, d->getData(), datasize); - - t->setMappedRangeModified(offset, datasize); - t->unmap(); + t->fill(offset, datasize, d->getData()); return 0; } @@ -256,7 +245,7 @@ static int w_Buffer_setArrayData(lua_State *L) if (startindex + count > arraylength) return luaL_error(L, "Too many array elements (expected at most %d, got %d)", arraylength - startindex, count); - char *data = (char *) t->map() + offset; + char *data = (char *) t->map(Buffer::MAP_WRITE_INVALIDATE, offset, count * stride); if (tableoftables) { @@ -303,8 +292,7 @@ static int w_Buffer_setArrayData(lua_State *L) } } - t->setMappedRangeModified(offset, count * stride); - t->unmap(); + t->unmap(offset, count * stride); return 0; } @@ -376,7 +364,6 @@ static int w_Buffer_isBufferType(lua_State *L) static const luaL_Reg w_Buffer_functions[] = { - { "flush", w_Buffer_flush }, { "setArrayData", w_Buffer_setArrayData }, { "getElementCount", w_Buffer_getElementCount }, { "getElementStride", w_Buffer_getElementStride }, diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index cd7c64499..713e5b5dd 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -1559,7 +1559,7 @@ static void luax_checkbufferformat(lua_State *L, int idx, std::vector &format) +static Buffer *luax_newbuffer(lua_State *L, int idx, Buffer::Settings settings, const std::vector &format) { size_t arraylength = 0; size_t bytesize = 0; @@ -1600,6 +1600,7 @@ static Buffer *luax_newbuffer(lua_State *L, int idx, const Buffer::Settings &set if (len <= 0) luaL_argerror(L, idx, "number of elements must be greater than 0"); arraylength = (size_t) len; + settings.zeroInitialize = true; } Buffer *b = nullptr; @@ -1663,7 +1664,7 @@ static Buffer *luax_newbuffer(lua_State *L, int idx, const Buffer::Settings &set int w_newBuffer(lua_State *L) { - Buffer::Settings settings(0, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC); + Buffer::Settings settings(0, BUFFERUSAGE_DYNAMIC); luaL_checktype(L, 3, LUA_TTABLE); @@ -1691,7 +1692,7 @@ int w_newBuffer(lua_State *L) int w_newVertexBuffer(lua_State *L) { - Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC); + Buffer::Settings settings(Buffer::TYPEFLAG_VERTEX, BUFFERUSAGE_DYNAMIC); luax_optbuffersettings(L, 3, settings); std::vector format; @@ -1706,7 +1707,7 @@ int w_newVertexBuffer(lua_State *L) int w_newIndexBuffer(lua_State *L) { - Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, Buffer::MAP_EXPLICIT_RANGE_MODIFY, BUFFERUSAGE_DYNAMIC); + Buffer::Settings settings(Buffer::TYPEFLAG_INDEX, BUFFERUSAGE_DYNAMIC); luax_optbuffersettings(L, 3, settings); size_t arraylength = 0; @@ -1747,6 +1748,7 @@ int w_newIndexBuffer(lua_State *L) if (len <= 0) return luaL_argerror(L, 1, "number of elements must be greater than 0"); arraylength = (size_t) len; + settings.zeroInitialize = true; } if (data != nullptr || !lua_isnoneornil(L, 2)) @@ -1953,8 +1955,9 @@ static Mesh *newCustomMesh(lua_State *L) luax_catchexcept(L, [&](){ t = instance()->newMesh(vertexformat, numvertices, drawmode, usage); }); - // Maximum possible data size for a single vertex attribute. - char data[sizeof(float) * 4]; + char *data = (char *) t->getVertexData(); + size_t stride = t->getVertexStride(); + const auto &members = t->getVertexFormat(); for (size_t vertindex = 0; vertindex < numvertices; vertindex++) { @@ -1965,7 +1968,8 @@ static Mesh *newCustomMesh(lua_State *L) int n = 0; for (size_t i = 0; i < vertexformat.size(); i++) { - const auto &info = getDataFormatInfo(vertexformat[i].format); + const auto &member = members[i]; + const auto &info = getDataFormatInfo(member.decl.format); // get vertices[vertindex][n] for (int c = 0; c < info.components; c++) @@ -1974,20 +1978,18 @@ static Mesh *newCustomMesh(lua_State *L) lua_rawgeti(L, -(c + 1), n); } + size_t offset = vertindex * stride + member.offset; + // Fetch the values from Lua and store them in data buffer. - luax_writebufferdata(L, -info.components, vertexformat[i].format, data); + luax_writebufferdata(L, -info.components, member.decl.format, data + offset); lua_pop(L, info.components); - - luax_catchexcept(L, - [&](){ t->setVertexAttribute(vertindex, i, data, sizeof(float) * 4); }, - [&](bool diderror){ if (diderror) t->release(); } - ); } lua_pop(L, 1); // pop vertices[vertindex] } + t->setVertexDataModified(0, stride * numvertices); t->flush(); } diff --git a/src/modules/graphics/wrap_Mesh.cpp b/src/modules/graphics/wrap_Mesh.cpp index 211463033..97c46f240 100644 --- a/src/modules/graphics/wrap_Mesh.cpp +++ b/src/modules/graphics/wrap_Mesh.cpp @@ -67,11 +67,13 @@ int w_Mesh_setVertices(lua_State *L) return luaL_error(L, "Too many vertices (expected at most %d, got %d)", totalverts - vertstart, vertcount); size_t datasize = std::min(d->getSize(), vertcount * stride); - char *bytedata = (char *) t->mapVertexData() + byteoffset; + char *bytedata = (char *) t->getVertexData() + byteoffset; memcpy(bytedata, d->getData(), datasize); - t->unmapVertexData(byteoffset, datasize); + t->setVertexDataModified(byteoffset, datasize); + t->flush(); + return 0; } @@ -88,7 +90,7 @@ int w_Mesh_setVertices(lua_State *L) for (const Buffer::DataMember &member : vertexformat) ncomponents += member.info.components; - char *data = (char *) t->mapVertexData() + byteoffset; + char *data = (char *) t->getVertexData() + byteoffset; for (int i = 0; i < vertcount; i++) { @@ -114,7 +116,9 @@ int w_Mesh_setVertices(lua_State *L) data += stride; } - t->unmapVertexData(byteoffset, vertcount * stride); + t->setVertexDataModified(byteoffset, vertcount * stride); + t->flush(); + return 0; } @@ -126,7 +130,10 @@ int w_Mesh_setVertex(lua_State *L) bool istable = lua_istable(L, 3); const std::vector &vertexformat = t->getVertexFormat(); - char *data = (char *) t->getVertexScratchBuffer(); + + char *data = nullptr; + size_t offset = 0; + luax_catchexcept(L, [&](){ data = (char *) t->checkVertexDataOffset(index, &offset); }); int idx = istable ? 1 : 3; @@ -156,7 +163,7 @@ int w_Mesh_setVertex(lua_State *L) } } - luax_catchexcept(L, [&](){ t->setVertex(index, data, t->getVertexStride()); }); + t->setVertexDataModified(offset, t->getVertexStride()); return 0; } @@ -167,9 +174,8 @@ int w_Mesh_getVertex(lua_State *L) const std::vector &vertexformat = t->getVertexFormat(); - char *data = (char *) t->getVertexScratchBuffer(); - - luax_catchexcept(L, [&](){ t->getVertex(index, data, t->getVertexStride()); }); + const char *data = nullptr; + luax_catchexcept(L, [&](){ data = (const char *) t->checkVertexDataOffset(index, nullptr); }); int n = 0; @@ -195,13 +201,14 @@ int w_Mesh_setVertexAttribute(lua_State *L) const Buffer::DataMember &member = vertexformat[attribindex]; - // Maximum possible size for a single vertex attribute. - char data[sizeof(float) * 4]; + char *data = nullptr; + size_t offset = 0; + luax_catchexcept(L, [&](){ data = (char *) t->checkVertexDataOffset(vertindex, &offset); }); // Fetch the values from Lua and store them in the data buffer. - luax_writebufferdata(L, 4, member.decl.format, data); + luax_writebufferdata(L, 4, member.decl.format, data + member.offset); - luax_catchexcept(L, [&](){ t->setVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); }); + t->setVertexDataModified(offset + member.offset, member.size); return 0; } @@ -218,12 +225,10 @@ int w_Mesh_getVertexAttribute(lua_State *L) const Buffer::DataMember &member = vertexformat[attribindex]; - // Maximum possible size for a single vertex attribute. - char data[sizeof(float) * 4]; + const char *data = nullptr; + luax_catchexcept(L, [&](){ data = (const char *) t->checkVertexDataOffset(vertindex, nullptr); }); - luax_catchexcept(L, [&](){ t->getVertexAttribute(vertindex, attribindex, data, sizeof(float) * 4); }); - - luax_readbufferdata(L, member.decl.format, data); + luax_readbufferdata(L, member.decl.format, data + member.offset); return member.info.components; } @@ -290,17 +295,17 @@ int w_Mesh_attachAttribute(lua_State *L) const char *name = luaL_checkstring(L, 2); Buffer *buffer = nullptr; + Mesh *mesh = nullptr; if (luax_istype(L, 3, Buffer::type)) { buffer = luax_checktype(L, 3); } else { - Mesh *mesh = luax_checkmesh(L, 3); + mesh = luax_checkmesh(L, 3); buffer = mesh->getVertexBuffer(); if (buffer == nullptr) return luaL_error(L, "Mesh does not have its own vertex buffer."); - luax_markdeprecated(L, "Mesh:attachAttribute(name, mesh, ...)", API_METHOD, DEPRECATED_REPLACED, "Mesh:attachAttribute(name, buffer, ...)"); } AttributeStep step = STEP_PER_VERTEX; @@ -310,7 +315,7 @@ int w_Mesh_attachAttribute(lua_State *L) const char *attachname = luaL_optstring(L, 5, name); - luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, attachname, step); }); + luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, mesh, attachname, step); }); return 0; } diff --git a/src/modules/graphics/wrap_SpriteBatch.cpp b/src/modules/graphics/wrap_SpriteBatch.cpp index 981bcb6dc..1c7aa085b 100644 --- a/src/modules/graphics/wrap_SpriteBatch.cpp +++ b/src/modules/graphics/wrap_SpriteBatch.cpp @@ -218,20 +218,20 @@ int w_SpriteBatch_attachAttribute(lua_State *L) const char *name = luaL_checkstring(L, 2); Buffer *buffer = nullptr; + Mesh *mesh = nullptr; if (luax_istype(L, 3, Buffer::type)) { buffer = luax_checktype(L, 3); } else { - Mesh *mesh = luax_checktype(L, 3); + mesh = luax_checktype(L, 3); buffer = mesh->getVertexBuffer(); if (buffer == nullptr) return luaL_error(L, "Mesh does not have its own vertex buffer."); - luax_markdeprecated(L, "SpriteBatch:attachAttribute(name, mesh)", API_METHOD, DEPRECATED_REPLACED, "SpriteBatch:attachAttribute(name, buffer)"); } - luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer); }); + luax_catchexcept(L, [&](){ t->attachAttribute(name, buffer, mesh); }); return 0; }