123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- /**
- * Copyright (c) 2006-2023 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.
- **/
- // LOVE
- #include "Mesh.h"
- #include "common/Matrix.h"
- #include "common/Exception.h"
- #include "Shader.h"
- #include "Graphics.h"
- // C++
- #include <algorithm>
- #include <limits>
- namespace love
- {
- namespace graphics
- {
- static_assert(offsetof(Vertex, x) == sizeof(float) * 0, "Incorrect position offset in Vertex struct");
- static_assert(offsetof(Vertex, s) == sizeof(float) * 2, "Incorrect texture coordinate offset in Vertex struct");
- static_assert(offsetof(Vertex, color.r) == sizeof(float) * 4, "Incorrect color offset in Vertex struct");
- std::vector<Buffer::DataDeclaration> Mesh::getDefaultVertexFormat()
- {
- return Buffer::getCommonFormatDeclaration(CommonFormat::XYf_STf_RGBAub);
- }
- love::Type Mesh::type("Mesh", &Drawable::type);
- Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, const void *data, size_t datasize, PrimitiveType drawmode, BufferDataUsage usage)
- : primitiveType(drawmode)
- {
- try
- {
- vertexData = new uint8[datasize];
- }
- catch (std::exception &)
- {
- throw love::Exception("Out of memory");
- }
- memcpy(vertexData, data, datasize);
- Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, usage);
- vertexBuffer.set(gfx->newBuffer(settings, vertexformat, vertexData, datasize, 0), Acquire::NORETAIN);
- vertexCount = vertexBuffer->getArrayLength();
- vertexStride = vertexBuffer->getArrayStride();
- vertexFormat = vertexBuffer->getDataMembers();
- setupAttachedAttributes();
- indexDataType = getIndexDataTypeFromMax(vertexCount);
- }
- Mesh::Mesh(graphics::Graphics *gfx, const std::vector<Buffer::DataDeclaration> &vertexformat, int vertexcount, PrimitiveType drawmode, BufferDataUsage usage)
- : vertexCount((size_t) vertexcount)
- , indexDataType(getIndexDataTypeFromMax(vertexcount))
- , primitiveType(drawmode)
- {
- if (vertexcount <= 0)
- throw love::Exception("Invalid number of vertices (%d).", vertexcount);
- Buffer::Settings settings(BUFFERUSAGEFLAG_VERTEX, usage);
- vertexBuffer.set(gfx->newBuffer(settings, vertexformat, nullptr, 0, vertexcount), Acquire::NORETAIN);
- vertexStride = vertexBuffer->getArrayStride();
- vertexFormat = vertexBuffer->getDataMembers();
- setupAttachedAttributes();
- try
- {
- vertexData = new uint8[vertexBuffer->getSize()];
- }
- catch (std::exception &)
- {
- throw love::Exception("Out of memory");
- }
- memset(vertexData, 0, vertexBuffer->getSize());
- vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
- }
- Mesh::Mesh(const std::vector<Mesh::BufferAttribute> &attributes, PrimitiveType drawmode)
- : primitiveType(drawmode)
- {
- if (attributes.size() == 0)
- throw love::Exception("At least one buffer attribute must be specified in this constructor.");
- attachedAttributes = attributes;
- vertexCount = attachedAttributes.size() > 0 ? LOVE_UINT32_MAX : 0;
- for (const auto &attrib : attachedAttributes)
- {
- if ((attrib.buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0)
- throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
- if (getAttachedAttributeIndex(attrib.name) != -1)
- throw love::Exception("Duplicate vertex attribute name: %s", attrib.name.c_str());
- vertexCount = std::min(vertexCount, attrib.buffer->getArrayLength());
- }
- indexDataType = getIndexDataTypeFromMax(vertexCount);
- }
- Mesh::~Mesh()
- {
- delete vertexData;
- if (indexData != nullptr)
- free(indexData);
- }
- void Mesh::setupAttachedAttributes()
- {
- for (size_t i = 0; i < vertexFormat.size(); i++)
- {
- const std::string &name = vertexFormat[i].decl.name;
- if (getAttachedAttributeIndex(name) != -1)
- throw love::Exception("Duplicate vertex attribute name: %s", name.c_str());
- attachedAttributes.push_back({name, vertexBuffer, nullptr, (int) i, 0, STEP_PER_VERTEX, true});
- }
- }
- int Mesh::getAttachedAttributeIndex(const std::string &name) const
- {
- for (int i = 0; i < (int) attachedAttributes.size(); i++)
- {
- if (attachedAttributes[i].name == name)
- return i;
- }
- return -1;
- }
- void *Mesh::checkVertexDataOffset(size_t vertindex, size_t *byteoffset)
- {
- if (vertindex >= vertexCount)
- throw love::Exception("Invalid vertex index: %ld", vertindex + 1);
- if (vertexData == nullptr)
- throw love::Exception("Mesh must own its own vertex buffer.");
- size_t offset = vertindex * vertexStride;
- if (byteoffset != nullptr)
- *byteoffset = offset;
- return vertexData + offset;
- }
- size_t Mesh::getVertexCount() const
- {
- return vertexCount;
- }
- size_t Mesh::getVertexStride() const
- {
- return vertexStride;
- }
- Buffer *Mesh::getVertexBuffer() const
- {
- return vertexBuffer;
- }
- const std::vector<Buffer::DataMember> &Mesh::getVertexFormat() const
- {
- return vertexFormat;
- }
- void Mesh::setAttributeEnabled(const std::string &name, bool enable)
- {
- int index = getAttachedAttributeIndex(name);
- if (index == -1)
- throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
- attachedAttributes[index].enabled = enable;
- }
- bool Mesh::isAttributeEnabled(const std::string &name) const
- {
- int index = getAttachedAttributeIndex(name);
- if (index == -1)
- throw love::Exception("Mesh does not have an attached vertex attribute named '%s'", name.c_str());
- return attachedAttributes[index].enabled;
- }
- void Mesh::attachAttribute(const std::string &name, Buffer *buffer, Mesh *mesh, const std::string &attachname, int startindex, AttributeStep step)
- {
- if ((buffer->getUsageFlags() & BUFFERUSAGEFLAG_VERTEX) == 0)
- throw love::Exception("Buffer must be created with vertex buffer support to be used as a Mesh vertex attribute.");
- auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
- if (step == STEP_PER_INSTANCE && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
- throw love::Exception("Vertex attribute instancing is not supported on this system.");
- if (startindex < 0 || startindex >= (int) buffer->getArrayLength())
- throw love::Exception("Invalid start array index %d.", startindex + 1);
- BufferAttribute oldattrib = {};
- BufferAttribute newattrib = {};
- int oldindex = getAttachedAttributeIndex(name);
- 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.name = name;
- newattrib.buffer = buffer;
- newattrib.mesh = mesh;
- newattrib.enabled = oldattrib.buffer.get() ? oldattrib.enabled : true;
- newattrib.indexInBuffer = buffer->getDataMemberIndex(attachname);
- newattrib.startArrayIndex = startindex;
- newattrib.step = step;
- 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);
- }
- bool Mesh::detachAttribute(const std::string &name)
- {
- int index = getAttachedAttributeIndex(name);
- if (index == -1)
- return false;
- attachedAttributes.erase(attachedAttributes.begin() + index);
- if (vertexBuffer.get() && vertexBuffer->getDataMemberIndex(name) != -1)
- attachAttribute(name, vertexBuffer, nullptr, name);
- return true;
- }
- const std::vector<Mesh::BufferAttribute> &Mesh::getAttachedAttributes() const
- {
- return attachedAttributes;
- }
- void *Mesh::getVertexData() const
- {
- return vertexData;
- }
- void Mesh::setVertexDataModified(size_t offset, size_t size)
- {
- if (vertexData != nullptr)
- modifiedVertexData.encapsulate(offset, size);
- }
- void Mesh::flush()
- {
- if (vertexBuffer.get() && vertexData != nullptr && modifiedVertexData.isValid())
- {
- if (vertexBuffer->getDataUsage() == BUFFERDATAUSAGE_STREAM)
- {
- vertexBuffer->fill(0, vertexBuffer->getSize(), vertexData);
- }
- else
- {
- size_t offset = modifiedVertexData.getOffset();
- size_t size = modifiedVertexData.getSize();
- vertexBuffer->fill(offset, size, vertexData + offset);
- }
- 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 <typename T>
- static void copyToIndexBuffer(const std::vector<uint32> &indices, void *data, size_t maxval)
- {
- T *elems = (T *) data;
- for (size_t i = 0; i < indices.size(); i++)
- {
- if (indices[i] >= maxval)
- throw love::Exception("Invalid vertex map value: %d", indices[i] + 1);
- elems[i] = (T) indices[i];
- }
- }
- void Mesh::setVertexMap(const std::vector<uint32> &map)
- {
- size_t maxval = getVertexCount();
- IndexDataType datatype = getIndexDataTypeFromMax(maxval);
- DataFormat dataformat = getIndexDataFormat(datatype);
- // Calculate the size in bytes of the index buffer data.
- size_t size = map.size() * getIndexDataSize(datatype);
- bool recreate = indexData == nullptr || indexBuffer.get() == nullptr
- || size > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat;
- if (recreate)
- {
- auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
- auto usage = vertexBuffer.get() ? vertexBuffer->getDataUsage() : BUFFERDATAUSAGE_DYNAMIC;
- Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, usage);
- auto buffer = StrongRef<Buffer>(gfx->newBuffer(settings, dataformat, nullptr, size, 0), Acquire::NORETAIN);
- auto data = (uint8 *) realloc(indexData, size);
- if (data == nullptr)
- throw love::Exception("Out of memory.");
- indexData = data;
- indexBuffer = buffer;
- }
- indexCount = map.size();
- useIndexBuffer = true;
- indexDataType = datatype;
- if (indexCount == 0)
- return;
- // Fill the buffer with the index values from the vector.
- switch (datatype)
- {
- case INDEX_UINT16:
- copyToIndexBuffer<uint16>(map, indexData, maxval);
- break;
- case INDEX_UINT32:
- default:
- copyToIndexBuffer<uint32>(map, indexData, maxval);
- break;
- }
- indexDataModified = true;
- }
- void Mesh::setVertexMap(IndexDataType datatype, const void *data, size_t datasize)
- {
- DataFormat dataformat = getIndexDataFormat(datatype);
- bool recreate = indexData == nullptr || indexBuffer.get() == nullptr
- || datasize > indexBuffer->getSize() || indexBuffer->getDataMember(0).decl.format != dataformat;
- if (recreate)
- {
- auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
- auto usage = vertexBuffer.get() ? vertexBuffer->getDataUsage() : BUFFERDATAUSAGE_DYNAMIC;
- Buffer::Settings settings(BUFFERUSAGEFLAG_INDEX, usage);
- auto buffer = StrongRef<Buffer>(gfx->newBuffer(settings, dataformat, nullptr, datasize, 0), Acquire::NORETAIN);
- auto data = (uint8 *) realloc(indexData, datasize);
- if (data == nullptr)
- throw love::Exception("Out of memory.");
- indexData = data;
- indexBuffer = buffer;
- }
- indexCount = datasize / getIndexDataSize(datatype);
- useIndexBuffer = true;
- indexDataType = datatype;
- if (indexCount == 0)
- return;
- memcpy(indexData, data, datasize);
- indexDataModified = true;
- }
- void Mesh::setVertexMap()
- {
- useIndexBuffer = false;
- }
- /**
- * Copies index data from a mapped buffer to a vector.
- **/
- template <typename T>
- static void copyFromIndexBuffer(const void *buffer, size_t count, std::vector<uint32> &indices)
- {
- const T *elems = (const T *) buffer;
- for (size_t i = 0; i < count; i++)
- indices.push_back((uint32) elems[i]);
- }
- bool Mesh::getVertexMap(std::vector<uint32> &map) const
- {
- if (!useIndexBuffer)
- return false;
- map.clear();
- if (indexData == nullptr || indexCount == 0)
- return true;
- map.reserve(indexCount);
- // Fill the vector from the buffer.
- switch (indexDataType)
- {
- case INDEX_UINT16:
- copyFromIndexBuffer<uint16>(indexData, indexCount, map);
- break;
- case INDEX_UINT32:
- default:
- copyFromIndexBuffer<uint32>(indexData, indexCount, map);
- break;
- }
- return true;
- }
- void Mesh::setIndexBuffer(Buffer *buffer)
- {
- // Buffer constructor does the rest of the validation for index buffers
- // (data member formats, etc.)
- if (buffer != nullptr && (buffer->getUsageFlags() & BUFFERUSAGEFLAG_INDEX) == 0)
- throw love::Exception("setIndexBuffer requires a Buffer created as an index buffer.");
- indexBuffer.set(buffer);
- useIndexBuffer = buffer != nullptr;
- indexCount = buffer != nullptr ? buffer->getArrayLength() : 0;
- if (buffer != nullptr)
- indexDataType = getIndexDataType(buffer->getDataMember(0).decl.format);
- if (indexData != nullptr)
- {
- free(indexData);
- indexData = nullptr;
- }
- }
- Buffer *Mesh::getIndexBuffer() const
- {
- return indexBuffer;
- }
- size_t Mesh::getIndexCount() const
- {
- return indexCount;
- }
- void Mesh::setTexture(Texture *tex)
- {
- texture.set(tex);
- }
- void Mesh::setTexture()
- {
- texture.set(nullptr);
- }
- Texture *Mesh::getTexture() const
- {
- return texture.get();
- }
- void Mesh::setDrawMode(PrimitiveType mode)
- {
- primitiveType = mode;
- }
- PrimitiveType Mesh::getDrawMode() const
- {
- return primitiveType;
- }
- void Mesh::setDrawRange(int start, int count)
- {
- if (start < 0 || count <= 0)
- throw love::Exception("Invalid draw range.");
- drawRange = Range(start, count);
- }
- void Mesh::setDrawRange()
- {
- drawRange.invalidate();
- }
- bool Mesh::getDrawRange(int &start, int &count) const
- {
- if (!drawRange.isValid())
- return false;
- start = (int) drawRange.getOffset();
- count = (int) drawRange.getSize();
- return true;
- }
- void Mesh::draw(Graphics *gfx, const love::Matrix4 &m)
- {
- drawInternal(gfx, m, 1, nullptr, 0);
- }
- void Mesh::drawInstanced(Graphics *gfx, const Matrix4 &m, int instancecount)
- {
- drawInternal(gfx, m, instancecount, nullptr, 0);
- }
- void Mesh::drawIndirect(Graphics *gfx, const Matrix4 &m, Buffer *indirectargs, int argsindex)
- {
- drawInternal(gfx, m, 0, indirectargs, argsindex);
- }
- void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buffer *indirectargs, int argsindex)
- {
- if (vertexCount <= 0 || (instancecount <= 0 && indirectargs == nullptr))
- return;
- if (instancecount > 1 && !gfx->getCapabilities().features[Graphics::FEATURE_INSTANCING])
- throw love::Exception("Instancing is not supported on this system.");
- if (indirectargs != nullptr)
- {
- if (primitiveType == PRIMITIVE_TRIANGLE_FAN)
- throw love::Exception("The fan draw mode is not supported in indirect draws.");
- if (useIndexBuffer && indexBuffer != nullptr)
- gfx->validateIndirectArgsBuffer(Graphics::INDIRECT_ARGS_DRAW_INDICES, indirectargs, argsindex);
- else
- gfx->validateIndirectArgsBuffer(Graphics::INDIRECT_ARGS_DRAW_VERTICES, indirectargs, argsindex);
- }
- // Some graphics backends don't natively support triangle fans. So we'd
- // have to emulate them with triangles plus an index buffer... which doesn't
- // work so well when there's already a custom index buffer.
- if (primitiveType == PRIMITIVE_TRIANGLE_FAN && useIndexBuffer && indexBuffer != nullptr)
- throw love::Exception("The 'fan' Mesh draw mode cannot be used with an index buffer / vertex map.");
- gfx->flushBatchedDraws();
- flush();
- if (Shader::isDefaultActive())
- Shader::attachDefault(Shader::STANDARD_DEFAULT);
- if (Shader::current)
- Shader::current->validateDrawState(primitiveType, texture);
- VertexAttributes attributes;
- BufferBindings buffers;
- int activebuffers = 0;
- for (const auto &attrib : attachedAttributes)
- {
- if (!attrib.enabled)
- continue;
- Buffer *buffer = attrib.buffer.get();
- int attributeindex = -1;
- // If the attribute is one of the LOVE-defined ones, use the constant
- // attribute index for it, otherwise query the index from the shader.
- BuiltinVertexAttribute builtinattrib;
- if (getConstant(attrib.name.c_str(), builtinattrib))
- attributeindex = (int) builtinattrib;
- else if (Shader::current)
- attributeindex = Shader::current->getVertexAttributeIndex(attrib.name);
- if (attributeindex >= 0)
- {
- if (attrib.mesh.get())
- attrib.mesh->flush();
- const auto &member = buffer->getDataMember(attrib.indexInBuffer);
- uint16 offset = (uint16) member.offset;
- uint16 stride = (uint16) buffer->getArrayStride();
- size_t bufferoffset = (size_t) stride * attrib.startArrayIndex;
- attributes.set(attributeindex, member.decl.format, offset, activebuffers);
- attributes.setBufferLayout(activebuffers, stride, attrib.step);
- // TODO: Ideally we want to reuse buffers with the same stride+step.
- buffers.set(activebuffers, buffer, bufferoffset);
- activebuffers++;
- }
- }
- // Not supported on all platforms or GL versions, I believe.
- if (!attributes.isEnabled(ATTRIB_POS))
- throw love::Exception("Mesh must have an enabled VertexPosition attribute to be drawn.");
- Graphics::TempTransform transform(gfx, m);
- Buffer *indexbuffer = useIndexBuffer ? indexBuffer : nullptr;
- int indexcount = (int) indexCount;
- Range range = drawRange;
- // Emulated triangle fan via an index buffer.
- if (primitiveType == PRIMITIVE_TRIANGLE_FAN && indexbuffer == nullptr && gfx->getFanIndexBuffer())
- {
- indexbuffer = gfx->getFanIndexBuffer();
- indexcount = graphics::getIndexCount(TRIANGLEINDEX_FAN, vertexCount);
- if (range.isValid())
- {
- int start = graphics::getIndexCount(TRIANGLEINDEX_FAN, (int) range.getOffset());
- int count = graphics::getIndexCount(TRIANGLEINDEX_FAN, (int) range.getSize());
- range = Range(start, count);
- }
- }
- if (indexbuffer != nullptr && (indexcount > 0 || indirectargs != nullptr))
- {
- Range r(0, indexcount);
- if (range.isValid())
- r.intersect(range);
- Graphics::DrawIndexedCommand cmd(&attributes, &buffers, indexbuffer);
- cmd.primitiveType = primitiveType;
- cmd.indexType = indexDataType;
- cmd.instanceCount = instancecount;
- cmd.texture = texture;
- cmd.cullMode = gfx->getMeshCullMode();
- cmd.indexBufferOffset = r.getOffset() * indexbuffer->getArrayStride();
- cmd.indexCount = (int) r.getSize();
- cmd.indirectBuffer = indirectargs;
- cmd.indirectBufferOffset = argsindex * (indirectargs != nullptr ? indirectargs->getArrayStride() : 0);
- if (cmd.indexCount > 0)
- gfx->draw(cmd);
- }
- else if (vertexCount > 0 || indirectargs != nullptr)
- {
- Range r(0, vertexCount);
- if (range.isValid())
- r.intersect(range);
- Graphics::DrawCommand cmd(&attributes, &buffers);
- cmd.primitiveType = primitiveType;
- cmd.vertexStart = (int) r.getOffset();
- cmd.vertexCount = (int) r.getSize();
- cmd.instanceCount = instancecount;
- cmd.texture = texture;
- cmd.cullMode = gfx->getMeshCullMode();
- cmd.indirectBuffer = indirectargs;
- cmd.indirectBufferOffset = argsindex * (indirectargs != nullptr ? indirectargs->getArrayStride() : 0);
- if (cmd.vertexCount > 0)
- gfx->draw(cmd);
- }
- }
- } // graphics
- } // love
|