diff --git a/CMakeLists.txt b/CMakeLists.txt index 59cd028a..6c17c204 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -376,6 +376,7 @@ target_sources(${PROJECT_NAME} PRIVATE source/common/type.cpp source/common/variant.cpp source/common/vector.cpp + source/common/volatile.cpp source/main.cpp source/modules/audio/audio.cpp source/modules/audio/wrap_audio.cpp @@ -516,6 +517,7 @@ target_sources(${PROJECT_NAME} PRIVATE source/utilities/driver/renderer/polyline/types/nonejoin.cpp source/utilities/driver/renderer/renderstate.cpp source/utilities/driver/renderer/samplerstate.cpp + source/utilities/driver/renderer/vertex.cpp source/utilities/formathandler/formathandler.cpp source/utilities/formathandler/types/astchandler.cpp source/utilities/formathandler/types/ddshandler.cpp diff --git a/include/common/matrix.hpp b/include/common/matrix.hpp index fd0990a1..ebf21aaf 100644 --- a/include/common/matrix.hpp +++ b/include/common/matrix.hpp @@ -145,6 +145,42 @@ namespace love } } + template + /* transform Vector2 src into Vector2 dst */ + void TransformXY(Vdst&& dst, Vsrc&& src, size_t count) const + { + for (size_t i = 0; i < count; i++) + { + float x = (this->matrix[0] * src[i].x) + (this->matrix[4] * src[i].y) + (0) + + (this->matrix[12]); + + float y = (this->matrix[1] * src[i].x) + (this->matrix[5] * src[i].y) + (0) + + (this->matrix[13]); + + dst[i].x = x; + dst[i].y = y; + } + } + + template + void TransformXY0(Vdst* dst, const Vsrc* src, int size) const + { + for (int i = 0; i < size; i++) + { + // Store in temp variables in case src = dst + float x = (this->matrix[0] * src[i].x) + (this->matrix[4] * src[i].y) + (0) + + (this->matrix[12]); + float y = (this->matrix[1] * src[i].x) + (this->matrix[5] * src[i].y) + (0) + + (this->matrix[13]); + float z = (this->matrix[2] * src[i].x) + (this->matrix[6] * src[i].y) + (0) + + (this->matrix[14]); + + dst[i].x = x; + dst[i].y = y; + dst[i].z = z; + } + } + template /* transform Vector3 src into Vector3 dst */ void TransformXYZ(Vdst&& dst, Vsrc&& src) const diff --git a/include/common/resource.hpp b/include/common/resource.hpp new file mode 100644 index 00000000..ef1ffe27 --- /dev/null +++ b/include/common/resource.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace love +{ + class Resource + { + public: + virtual ~Resource() + {} + + virtual ptrdiff_t GetHandle() const = 0; + }; +} // namespace love diff --git a/include/common/volatile.hpp b/include/common/volatile.hpp new file mode 100644 index 00000000..1f91d921 --- /dev/null +++ b/include/common/volatile.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace love +{ + class Volatile + { + private: + static std::list all; + + public: + Volatile(); + + virtual ~Volatile(); + + virtual bool LoadVolatile() = 0; + + virtual void UnloadVolatile() = 0; + + static bool LoadAll(); + + static void UnloadAll(); + }; +} // namespace love diff --git a/include/modules/graphics/graphics.tcc b/include/modules/graphics/graphics.tcc index 2fd1f722..6806fcb3 100644 --- a/include/modules/graphics/graphics.tcc +++ b/include/modules/graphics/graphics.tcc @@ -35,10 +35,16 @@ #include #include +#include +#include + #include +#include + namespace love { + using namespace love::vertex; using OptionalColor = Optional; template @@ -242,6 +248,73 @@ namespace love SamplerState defaultSamplerState = SamplerState {}; }; + struct BatchedDrawState + { + StreamBuffer* vertexBuffer[0x02]; + StreamBuffer* indexBuffer = nullptr; + + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + CommonFormat formats[0x02]; + StrongReference> texture; + Shader<>::StandardShader shaderType = Shader<>::STANDARD_DEFAULT; + + int vertexCount = 0; + int indexCount = 0; + + StreamBuffer::MapInfo vertexMap[0x02]; + StreamBuffer::MapInfo indexMap = StreamBuffer::MapInfo(); + + BatchedDrawState() + { + this->vertexBuffer[0] = this->vertexBuffer[1] = nullptr; + this->formats[0] = this->formats[1] = CommonFormat::NONE; + this->vertexMap[0] = this->vertexMap[1] = StreamBuffer::MapInfo(); + } + }; + + struct BatchedDrawCommand + { + PrimitiveType primitiveMode = PRIMITIVE_TRIANGLES; + CommonFormat formats[0x02] = { CommonFormat::NONE }; + TriangleIndexMode indexMode = TRIANGLEINDEX_NONE; + int vertexCount = 0; + Texture* texture = nullptr; + Shader<>::StandardShader shaderType = Shader<>::STANDARD_DEFAULT; + }; + + struct DrawIndexedCommand + { + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; + + const VertexAttributes* attributes; + const BufferBindings* buffers; + + int indexCount = 0; + int instanceCount = 1; + + IndexDataType indexType = INDEX_UINT16; + Resource* indexBuffer; + size_t indexBufferOffset = 0; + + Buffer* indirectBuffer = nullptr; + size_t indirectBufferOffset = 0; + + Texture* texture = nullptr; + CullMode cullMode = CULL_NONE; + + DrawIndexedCommand(const VertexAttributes* attributes, const BufferBindings* buffers, + Resource* indexBuffer) : + attributes(attributes), + buffers(buffers), + indexBuffer(indexBuffer) + {} + }; + + struct BatchedVertexData + { + void* stream[0x02]; + }; + static inline bool gammaCorrectColor = false; static void SetGammaCorrect(bool enable) @@ -297,7 +370,8 @@ namespace love pixelHeight(0), created(false), active(true), - renderTargetSwitchCount(0) + renderTargetSwitchCount(0), + batchedDrawState {} { this->transformStack.reserve(16); this->transformStack.push_back(Matrix4()); @@ -335,6 +409,9 @@ namespace love Renderer::Instance().Clear(color.value); } + if (color.hasValue || stencil.hasValue || depth.hasValue) + this->FlushBatchedDraws(); + if (stencil.hasValue && depth.hasValue) Renderer::Instance().ClearDepthStencil(stencil.value, 0xFF, depth.value); @@ -344,8 +421,13 @@ namespace love { int colorCount = colors.size(); - if (colorCount == 0 || !stencil.hasValue || !depth.hasValue) + if (colorCount == 0 && !stencil.hasValue && !depth.hasValue) + return; + + if (colorCount <= 1) this->Clear(colorCount > 0 ? colors[0] : Color {}, stencil, depth); + + this->FlushBatchedDraws(); } void Present() @@ -357,6 +439,276 @@ namespace love Shader::shaderSwitches = 0; } + struct DrawCommand + { + PrimitiveType primitiveType = PRIMITIVE_TRIANGLES; + + const VertexAttributes* attributes; + const BufferBindings* buffers; + + int vertexStart = 0; + int vertexCount = 0; + int instanceCount = 1; + + Buffer* indirectBuffer = nullptr; + size_t indirectBufferOffset = 0; + + Texture* texture = nullptr; + CullMode cullMode = CULL_NONE; + + DrawCommand(const VertexAttributes* attributes, const BufferBindings* buffers) : + attributes(attributes), + buffers(buffers) + {} + }; + + BatchedVertexData RequestBatchedDraw(const BatchedDrawCommand& cmd) + { + BatchedDrawState& state = this->batchedDrawState; + + bool shouldFlush = false; + bool shouldResize = false; + + if (cmd.primitiveMode != state.primitiveMode || cmd.formats[0] != state.formats[0] || + cmd.formats[1] != state.formats[1] || + ((cmd.indexMode != TRIANGLEINDEX_NONE) != (state.indexCount > 0)) || + cmd.texture != state.texture || cmd.shaderType != state.shaderType) + { + shouldFlush = true; + } + + int totalVertices = state.vertexCount + cmd.vertexCount; + + if (totalVertices > LOVE_UINT16_MAX && cmd.indexMode != TRIANGLEINDEX_NONE) + shouldFlush = true; + + int requestIndexCount = vertex::GetIndexCount(cmd.indexMode, cmd.vertexCount); + int requestIndexSize = requestIndexCount * sizeof(uint16_t); + + size_t newDataSizes[0x02] { 0, 0 }; + size_t bufferSizes[0x03] { 0, 0, 0 }; + + for (int index = 0; index < 2; index++) + { + if (cmd.formats[index] == CommonFormat::NONE) + continue; + + size_t stride = vertex::GetFormatStride(cmd.formats[index]); + size_t dataSize = stride * totalVertices; + + if (state.vertexMap[index].data != nullptr && + dataSize > state.vertexMap[index].size) + { + shouldResize = true; + } + + if (dataSize > state.vertexBuffer[index]->GetUsableSize()) + { + bufferSizes[index] = + std::max(dataSize, state.vertexBuffer[index]->GetSize() * 2); + shouldResize = true; + } + + newDataSizes[index] = stride * cmd.vertexCount; + } + + if (cmd.indexMode != TRIANGLEINDEX_NONE) + { + size_t dataSize = (state.indexCount + requestIndexCount) * sizeof(uint16_t); + + if (state.indexMap.data != nullptr && dataSize > state.indexMap.size) + shouldFlush = true; + + if (dataSize > state.indexBuffer->GetUsableSize()) + { + bufferSizes[2] = std::max(dataSize, state.indexBuffer->GetSize() * 2); + shouldResize = true; + } + } + + if (shouldFlush || shouldResize) + { + this->FlushBatchedDraws(); + state.primitiveMode = cmd.primitiveMode; + state.formats[0] = cmd.formats[0]; + state.formats[1] = cmd.formats[1]; + state.texture = cmd.texture; + state.shaderType = cmd.shaderType; + } + + if (state.vertexCount == 0) + { + if (Shader::IsDefaultActive()) + Shader::AttachDefault(state.shaderType); + + // if (Shader::current != nullptr) + // Shader::current->ValidateDrawState?? + } + + if (shouldResize) + { + for (int index = 0; index < 2; index++) + { + if (state.vertexBuffer[index]->GetSize() < bufferSizes[index]) + { + state.vertexBuffer[index]->Release(); + state.vertexBuffer[index] = + CreateStreamBuffer(vertex::BUFFERUSAGE_VERTEX, bufferSizes[index]); + } + } + + if (state.indexBuffer->GetSize() < bufferSizes[2]) + { + state.indexBuffer->Release(); + state.indexBuffer = + CreateStreamBuffer(vertex::BUFFERUSAGE_INDEX, bufferSizes[2]); + } + } + + if (cmd.indexMode != TRIANGLEINDEX_NONE) + { + if (state.indexMap.data == nullptr) + state.indexMap = state.indexBuffer->Map(requestIndexSize); + + uint16_t* indices = (uint16_t*)state.indexMap.data; + vertex::FillIndices(cmd.indexMode, state.vertexCount, cmd.vertexCount, indices); + + state.indexMap.data += requestIndexSize; + } + + BatchedVertexData data {}; + + for (int index = 0; index < 2; index++) + { + if (newDataSizes[index] > 0) + { + if (state.vertexMap[index].data == nullptr) + { + state.vertexMap[index] = + state.vertexBuffer[index]->Map(newDataSizes[index]); + } + + data.stream[index] = state.vertexMap[index].data; + state.vertexMap[index].data += newDataSizes[index]; + } + } + + if (state.vertexCount > 0) + Renderer<>::drawCallsBatched++; + + state.vertexCount += cmd.vertexCount; + state.indexCount += requestIndexCount; + + return data; + } + + void FlushBatchedDraws() + { + auto& state = this->batchedDrawState; + + if (state.vertexCount == 0 && state.indexCount == 0) + return; + + VertexAttributes attributes {}; + BufferBindings buffers {}; + + size_t usedSizes[0x03] { 0 }; + + for (int index = 0; index < 2; index++) + { + if (state.formats[index] == CommonFormat::NONE) + continue; + + attributes.SetCommonFormat(state.formats[index], (uint8_t)index); + usedSizes[index] = VERTEX_SIZE * state.vertexCount; + + size_t offset = state.vertexBuffer[index]->Unmap(usedSizes[index]); + buffers.Set(index, state.vertexBuffer[index], offset); + + state.vertexMap[index] = StreamBuffer::MapInfo(); + } + + if (attributes.enableBits == 0) + return; + + Color color = this->GetColor(); + if (attributes.IsEnabled(vertex::ATTRIB_COLOR)) + this->SetColor(Color::WHITE); + + this->PushIdentityTransform(); + + if (state.indexCount > 0) + { + usedSizes[2] = sizeof(uint16_t) * state.indexCount; + + DrawIndexedCommand command(&attributes, &buffers, state.indexBuffer); + command.primitiveType = state.primitiveMode; + command.indexCount = state.indexCount; + command.indexType = INDEX_UINT16; + command.indexBufferOffset = state.indexBuffer->Unmap(usedSizes[2]); + command.texture = state.texture; + + this->Draw(command); + + state.indexMap = StreamBuffer::MapInfo(); + } + else + { + DrawCommand command(&attributes, &buffers); + command.primitiveType = state.primitiveMode; + command.vertexStart = 0; + command.vertexCount = state.vertexCount; + command.texture = state.texture; + + // this->Draw(command); + } + + for (int index = 0; index < 2; index++) + { + if (usedSizes[index] > 0) + state.vertexBuffer[index]->MarkUsed(usedSizes[index]); + } + + if (usedSizes[2] > 0) + state.indexBuffer->MarkUsed(usedSizes[2]); + + this->PopTransform(); + + if (attributes.IsEnabled(vertex::ATTRIB_COLOR)) + this->SetColor(color); + + state.vertexCount = 0; + state.indexCount = 0; + } + + static void FlushBatchedDrawsGlobal() + { + Graphics* instance = Module::GetInstance>(M_GRAPHICS); + if (instance != nullptr) + instance->FlushBatchedDraws(); + } + + void Draw(const DrawCommand& cmd) + { + Renderer::Instance().PrepareDraw(this); + Renderer::Instance().SetVertexAttributes(*cmd.attributes, *cmd.buffers); + Renderer::Instance().BindTextureToUnit(cmd.texture, 0); + Renderer::Instance().SetMeshCullMode(cmd.cullMode); + } + + void Draw(const DrawIndexedCommand& cmd) + { + BatchedDrawState& state = this->batchedDrawState; + + Renderer::Instance().PrepareDraw(this); + Renderer::Instance().SetVertexAttributes(*cmd.attributes, *cmd.buffers); + Renderer::Instance().BindTextureToUnit(cmd.texture, 0); + Renderer::Instance().SetMeshCullMode(cmd.cullMode); + + Renderer::Instance().DrawElements( + cmd.primitiveType, cmd.indexCount, (uint16_t*)state.vertexMap[0].data, 0); + } + /* graphics state */ void Reset() @@ -778,6 +1130,7 @@ namespace love void SetActive(bool active) { + this->FlushBatchedDraws(); this->active = active; } @@ -1166,15 +1519,15 @@ namespace love bool is2D = transform.IsAffine2DTransform(); const int count = points.size() - (skipLastVertex ? 1 : 0); - DrawCommand command(count, vertex::PRIMITIVE_TRIANGLE_FAN); + // DrawCommand command(count, vertex::PRIMITIVE_TRIANGLE_FAN); - if (is2D) - transform.TransformXY(std::span(command.Positions().get(), command.count), - points); + // if (is2D) + // transform.TransformXY(std::span(command.Positions().get(), command.count), + // points); - command.FillVertices(this->GetColor()); + // command.FillVertices(this->GetColor()); - Renderer::Instance().Render(command); + // Renderer::Instance().Render(command); } } @@ -1398,17 +1751,18 @@ namespace love const auto& transform = this->GetTransform(); bool is2D = transform.IsAffine2DTransform(); - DrawCommand command(points.size(), vertex::PRIMITIVE_POINTS); + // DrawCommand command(points.size(), vertex::PRIMITIVE_POINTS); - if (is2D) - transform.TransformXY(std::span(command.Positions().get(), points.size()), points); + // if (is2D) + // transform.TransformXY(std::span(command.Positions().get(), points.size()), + // points); - if (colors.size() > 1) - command.FillVertices(colors); - else - command.FillVertices(colors[0]); + // if (colors.size() > 1) + // command.FillVertices(colors); + // else + // command.FillVertices(colors[0]); - Renderer::Instance().Render(command); + // Renderer::Instance().Render(command); } void Line(std::span points) @@ -1541,5 +1895,6 @@ namespace love int renderTargetSwitchCount; StrongReference defaultFont; + BatchedDrawState batchedDrawState; }; } // namespace love diff --git a/include/utilities/driver/renderer/buffer.hpp b/include/utilities/driver/renderer/buffer.hpp new file mode 100644 index 00000000..e61c310c --- /dev/null +++ b/include/utilities/driver/renderer/buffer.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +#include +#include + +namespace love +{ + template + class Graphics; + + class Buffer : public Object, public Resource + { + static Type type; + static constexpr size_t SHADER_STORAGE_BUFFER_MAX_STRIDE = 0x800; + + enum MapType + { + MAP_WRITE_INVALIDATE, + MAP_READ_ONLY + }; + + struct DataDeclaration + { + std::string name; + vertex::DataFormat format; + int arrayLength; + + DataDeclaration(const std::string& name, vertex::DataFormat format, + int arrayLength = 0) : + name(name), + format(format), + arrayLength(arrayLength) + {} + }; + + struct DataMember + { + DataDeclaration declaration; + vertex::DataFormatInfo info; + size_t offset; + size_t size; + + DataMember(const DataDeclaration& declaration) : + declaration(declaration), + info(vertex::GetDataFormatInfo(declaration.format)), + offset(0), + size(0) + {} + }; + + struct Settings + { + vertex::BufferUsageFlags usageFlags; + vertex::BufferDataUsage dataUsage; + bool zeroInitialize; + + Settings(uint32_t usageFlags, vertex::BufferDataUsage dataUsage) : + usageFlags((vertex::BufferUsageFlags)usageFlags), + dataUsage(dataUsage), + zeroInitialize(false) + {} + }; + + Buffer(Graphics* gfx, const Settings& settings, + const std::vector& format, size_t size, size_t arrayLength); + + virtual ~Buffer(); + }; +} // namespace love diff --git a/include/utilities/driver/renderer/polyline/polyline.hpp b/include/utilities/driver/renderer/polyline/polyline.hpp index 3c1e5410..b5d1a09f 100644 --- a/include/utilities/driver/renderer/polyline/polyline.hpp +++ b/include/utilities/driver/renderer/polyline/polyline.hpp @@ -26,7 +26,7 @@ namespace love public: static constexpr float LINES_PARALLEL_EPS = 0.05f; - Polyline(vertex::TriangleIndexMode mode = vertex::TRIANGLE_STRIP) : + Polyline(vertex::TriangleIndexMode mode = vertex::TRIANGLEINDEX_STRIP) : vertices(nullptr), overdraw(nullptr), vertex_count(0), diff --git a/include/utilities/driver/renderer/polyline/types/nonejoin.hpp b/include/utilities/driver/renderer/polyline/types/nonejoin.hpp index 51894f39..234a5694 100644 --- a/include/utilities/driver/renderer/polyline/types/nonejoin.hpp +++ b/include/utilities/driver/renderer/polyline/types/nonejoin.hpp @@ -13,7 +13,7 @@ namespace love class NoneJoinPolyline : public Polyline { public: - NoneJoinPolyline() : Polyline(vertex::TRIANGLE_QUADS) + NoneJoinPolyline() : Polyline(vertex::TRIANGLEINDEX_QUADS) {} void render(const Vector2* vertices, size_t count, float halfwidth, float pixel_size, diff --git a/include/utilities/driver/renderer/streambuffer.hpp b/include/utilities/driver/renderer/streambuffer.hpp new file mode 100644 index 00000000..cb7f23a9 --- /dev/null +++ b/include/utilities/driver/renderer/streambuffer.hpp @@ -0,0 +1,60 @@ +#include + +#include +#include + +#include + +namespace love +{ + class StreamBuffer : public Object, public Resource + { + public: + struct MapInfo + { + uint8_t* data = nullptr; + size_t size = 0; + + MapInfo() + {} + + MapInfo(uint8_t* data, size_t size) : data(data), size(size) + {} + }; + + virtual ~StreamBuffer() + {} + + size_t GetSize() const + { + return this->bufferSize; + } + + vertex::BufferUsage GetMode() const + { + return this->mode; + } + + size_t GetUsableSize() const + { + return this->bufferSize - this->frameGPUReadOffset; + } + + virtual MapInfo Map(size_t minsize) = 0; + + virtual size_t Unmap(size_t usedsize) = 0; + + virtual void MarkUsed(size_t usedsize) = 0; + + virtual void NextFrame() + {} + + protected: + StreamBuffer(vertex::BufferUsage mode, size_t size) : bufferSize(size), frameGPUReadOffset(0), mode(mode) + {} + + size_t bufferSize; + size_t frameGPUReadOffset; + vertex::BufferUsage mode; + }; +} // namespace love diff --git a/include/utilities/driver/renderer/streamclient.hpp b/include/utilities/driver/renderer/streamclient.hpp new file mode 100644 index 00000000..3edcef2f --- /dev/null +++ b/include/utilities/driver/renderer/streamclient.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +static constexpr int BUFFER_FRAMES = 0x04; + +namespace love +{ + class StreamBufferClientMemory final : public StreamBuffer + { + public: + StreamBufferClientMemory(vertex::BufferUsage mode, size_t size) : + StreamBuffer(mode, size), + data(nullptr) + { + try + { + this->data = new uint8_t[size]; + } + catch (std::exception& e) + { + throw love::Exception("Out of memory."); + } + } + + virtual ~StreamBufferClientMemory() + { + delete[] this->data; + } + + virtual MapInfo Map(size_t minsize) override + { + return MapInfo(this->data, this->bufferSize); + } + + virtual size_t Unmap(size_t usedsize) override + { + return (size_t)this->data; + } + + void MarkUsed(size_t /*usedsize*/) override + {} + + ptrdiff_t GetHandle() const override + { + return 0; + } + + private: + uint8_t* data; + }; + +#if defined(__3DS__) + #include <3ds.h> + + class StreamBufferSubDataOrphan final : public StreamBuffer, public Volatile + { + StreamBufferSubDataOrphan(vertex::BufferUsage mode, size_t size) : + StreamBuffer(mode, size), + data(nullptr), + orphan(false) + { + try + { + this->data = (uint8_t*)linearAlloc(size); + } + catch (std::exception& e) + { + throw love::Exception("Out of memory."); + } + + this->LoadVolatile(); + } + + virtual ~StreamBufferSubDataOrphan() + { + this->UnloadVolatile(); + linearFree(this->data); + } + + /* Maps some data to GPU memory */ + virtual MapInfo Map(size_t minSize) override + { + if (this->orphan) + { + this->orphan = false; + this->frameGPUReadOffset = 0; + // glBindBuffer(type, buffer)? + // glBufferData(mode, size, nullptr, usage)? + // BufInfo_Add(this->vbo, this->data, VERTEX_SIZE, ); + } + + return MapInfo(this->data, this->bufferSize - this->frameGPUReadOffset); + } + + /* Uploads data to the GPU */ + virtual size_t Unmap(size_t usedSize) override + { + BufInfo_Add(&this->vbo, this->data, VERTEX_SIZE, 0x03, 0x210); + C3D_SetBufInfo(&this->vbo); + + return this->frameGPUReadOffset; + } + + void MarkUsed(size_t usedSize) override + { + this->frameGPUReadOffset += usedSize; + } + + void NextFrame() override + { + this->frameGPUReadOffset = 0; + this->orphan = true; + } + + ptrdiff_t GetHandle() const override + { + return (ptrdiff_t)(&this->vbo); + } + + bool LoadVolatile() override + { + if (this->loaded) + return true; + + BufInfo_Init(&this->vbo); + + this->frameGPUReadOffset = 0; + this->orphan = false; + this->loaded = true; + + return true; + } + + void UnloadVolatile() override + { + if (!this->loaded) + return; + + BufInfo_Init(&this->vbo); + } + + protected: + C3D_BufInfo vbo; + uint8_t* data; + bool orphan; + bool loaded; + }; +#endif + + static inline StreamBuffer* CreateStreamBuffer(BufferUsage mode, size_t size) + { + return new StreamBufferClientMemory(mode, size); + } +} // namespace love diff --git a/include/utilities/driver/renderer/vertex.hpp b/include/utilities/driver/renderer/vertex.hpp index acc208e1..75bd90f6 100644 --- a/include/utilities/driver/renderer/vertex.hpp +++ b/include/utilities/driver/renderer/vertex.hpp @@ -1,13 +1,42 @@ #pragma once +#include +#include +#include #include #include +#include + namespace love { namespace vertex { + struct Vertex + { + Vector3 position; + std::array color; + std::array texcoord; + }; + + static constexpr size_t POSITION_OFFSET = offsetof(Vertex, position); + static constexpr size_t COLOR_OFFSET = offsetof(Vertex, color); + static constexpr size_t TEXCOORD_OFFSET = offsetof(Vertex, texcoord); + + struct STf_RGBAf + { + float s, t; + Color color; + }; + + enum IndexDataType + { + INDEX_UINT16, + INDEX_UINT32, + INDEX_MAX_ENUM + }; + enum CullMode { CULL_NONE, @@ -25,10 +54,10 @@ namespace love enum TriangleIndexMode { - TRIANGLE_NONE, - TRIANGLE_STRIP, - TRIANGLE_FAN, - TRIANGLE_QUADS + TRIANGLEINDEX_NONE, + TRIANGLEINDEX_STRIP, + TRIANGLEINDEX_FAN, + TRIANGLEINDEX_QUADS }; enum PrimitiveType @@ -50,7 +79,87 @@ namespace love NONE, PRIMITIVE, TEXTURE, - FONT + FONT, + XYf, + XYZf, + STf_RGBAf + }; + + enum DataFormat + { + DATAFORMAT_FLOAT, + DATAFORMAT_FLOAT_VEC2, + DATAFORMAT_FLOAT_VEC3, + DATAFORMAT_FLOAT_VEC4, + + DATAFORMAT_FLOAT_MAT2X2, + DATAFORMAT_FLOAT_MAT2X3, + DATAFORMAT_FLOAT_MAT2X4, + + DATAFORMAT_FLOAT_MAT3X2, + DATAFORMAT_FLOAT_MAT3X3, + DATAFORMAT_FLOAT_MAT3X4, + + DATAFORMAT_FLOAT_MAT4X2, + DATAFORMAT_FLOAT_MAT4X3, + DATAFORMAT_FLOAT_MAT4X4, + + DATAFORMAT_INT32, + DATAFORMAT_INT32_VEC2, + DATAFORMAT_INT32_VEC3, + DATAFORMAT_INT32_VEC4, + + DATAFORMAT_UINT32, + DATAFORMAT_UINT32_VEC2, + DATAFORMAT_UINT32_VEC3, + DATAFORMAT_UINT32_VEC4, + + DATAFORMAT_SNORM8_VEC4, + DATAFORMAT_UNORM8_VEC4, + DATAFORMAT_INT8_VEC4, + DATAFORMAT_UINT8_VEC4, + + DATAFORMAT_SNORM16_VEC2, + DATAFORMAT_SNORM16_VEC4, + + DATAFORMAT_UNORM16_VEC2, + DATAFORMAT_UNORM16_VEC4, + + DATAFORMAT_INT16_VEC2, + DATAFORMAT_INT16_VEC4, + + DATAFORMAT_UINT16, + DATAFORMAT_UINT16_VEC2, + DATAFORMAT_UINT16_VEC4, + + DATAFORMAT_BOOL, + DATAFORMAT_BOOL_VEC2, + DATAFORMAT_BOOL_VEC3, + DATAFORMAT_BOOL_VEC4, + + DATAFORMAT_MAX_ENUM + }; + + enum DataBaseType + { + DATA_BASETYPE_FLOAT, + DATA_BASETYPE_INT, + DATA_BASETYPE_UINT, + DATA_BASETYPE_SNORM, + DATA_BASETYPE_UNORM, + DATA_BASETYPE_BOOL, + DATA_BASETYPE_MAX_ENUM + }; + + struct DataFormatInfo + { + DataBaseType baseType; + bool isMatrix; + int components; + int matrixRows; + int matrixColumns; + size_t componentSize; + size_t size; }; enum AttributeStep @@ -60,6 +169,67 @@ namespace love STEP_MAX_ENUM }; + enum BufferUsage + { + BUFFERUSAGE_VERTEX = 0, + BUFFERUSAGE_INDEX, + BUFFERUSAGE_TEXEL, + BUFFERUSAGE_UNIFORM, + BUFFERUSAGE_SHADER_STORAGE, + BUFFERUSAGE_INDIRECT_ARGUMENTS, + BUFFERUSAGE_MAX_ENUM + }; + + enum BufferUsageFlags + { + BUFFERUSAGEFLAG_NONE = 0, + BUFFERUSAGEFLAG_VERTEX = 1 << BUFFERUSAGE_VERTEX, + BUFFERUSAGEFLAG_INDEX = 1 << BUFFERUSAGE_INDEX, + BUFFERUSAGEFLAG_TEXEL = 1 << BUFFERUSAGE_TEXEL, + BUFFERUSAGEFLAG_SHADER_STORAGE = 1 << BUFFERUSAGE_SHADER_STORAGE, + BUFFERUSAGEFLAG_INDIRECT_ARGUMENTS = 1 << BUFFERUSAGE_INDIRECT_ARGUMENTS, + }; + + // The expected usage pattern of buffer data. + enum BufferDataUsage + { + BUFFERDATAUSAGE_STREAM, + BUFFERDATAUSAGE_DYNAMIC, + BUFFERDATAUSAGE_STATIC, + BUFFERDATAUSAGE_READBACK, + BUFFERDATAUSAGE_MAX_ENUM + }; + + struct BufferBindings + { + static constexpr uint32_t MAX = 32; + + uint32_t useBits = 0; + + struct + { + Resource* buffer; + size_t offset; + } info[MAX]; + + void Set(uint32_t index, Resource* buffer, size_t offset) + { + this->useBits |= (1u << index); + this->info[index].buffer = buffer; + this->info[index].offset = offset; + } + + void Disable(uint32_t index) + { + this->useBits &= ~(1u << index); + } + + void Clear() + { + this->useBits = 0; + } + }; + enum BuiltinVertexAttribute { ATTRIB_POS = 0, @@ -75,17 +245,207 @@ namespace love ATTRIBFLAG_COLOR = 1 << ATTRIB_COLOR, }; + struct VertexAttributeInfo + { + uint8_t bufferIndex; + DataFormat format : 8; + uint16_t offset; + }; + + struct VertexBufferLayout + { + uint16_t stride; + }; + struct VertexAttributes { static constexpr uint32_t MAX = 32; + + uint32_t enableBits = 0; + uint32_t instanceBits = 0; + + VertexAttributeInfo attributes[MAX]; + VertexBufferLayout bufferLayouts[BufferBindings::MAX]; + + VertexAttributes() + { + std::memset(this, 0, sizeof(VertexAttributes)); + } + + VertexAttributes(CommonFormat format, uint8_t bufferIndex) + { + std::memset(this, 0, sizeof(VertexAttributes)); + SetCommonFormat(format, bufferIndex); + } + + void Set(uint32_t index, DataFormat format, uint16_t offsetFromVertex, + uint8_t bufferIndex) + { + this->enableBits |= (1u << index); + this->attributes[index] = { bufferIndex, format, offsetFromVertex }; + } + + void SetBufferLayout(uint8_t bufferIndex, uint16_t stride, AttributeStep step) + { + uint32_t bufferBit = (1u << bufferIndex); + + if (step == STEP_PER_INSTANCE) + this->instanceBits |= bufferBit; + else + this->instanceBits &= ~bufferBit; + + this->bufferLayouts[bufferIndex].stride = stride; + } + + void Disable(uint32_t index) + { + this->enableBits &= ~(1u << index); + } + + void Clear() + { + this->enableBits = 0; + } + + bool IsEnabled(uint32_t index) const + { + return (this->enableBits & (1u << index)) != 0; + } + + AttributeStep GetBufferStep(uint32_t index) const + { + const auto hasInstance = (this->instanceBits & (1u << index)) != 0; + return hasInstance ? STEP_PER_INSTANCE : STEP_PER_VERTEX; + } + + void SetCommonFormat(CommonFormat format, uint8_t bufferIndex) + { + switch (format) + { + case CommonFormat::NONE: + break; + case CommonFormat::PRIMITIVE: + { + Set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC3, 0, bufferIndex); + Set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, COLOR_OFFSET, bufferIndex); + break; + } + case CommonFormat::TEXTURE: + case CommonFormat::FONT: + { + Set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC3, 0, bufferIndex); + Set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, COLOR_OFFSET, bufferIndex); + Set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, TEXCOORD_OFFSET, bufferIndex); + break; + } + case CommonFormat::XYf: + { + Set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC2, 0, bufferIndex); + break; + } + case CommonFormat::XYZf: + { + Set(ATTRIB_POS, DATAFORMAT_FLOAT_VEC3, 0, bufferIndex); + break; + } + case CommonFormat::STf_RGBAf: + { + Set(ATTRIB_TEXCOORD, DATAFORMAT_FLOAT_VEC2, TEXCOORD_OFFSET, bufferIndex); + Set(ATTRIB_COLOR, DATAFORMAT_FLOAT_VEC4, COLOR_OFFSET, bufferIndex); + break; + } + } + } + + bool operator==(const VertexAttributes& other) const + { + if (this->enableBits != other.enableBits || + this->instanceBits != other.instanceBits) + { + return false; + } + + uint32_t allBits = this->enableBits; + uint32_t index = 0; + + while (allBits) + { + if (this->IsEnabled(index)) + { + const auto& thisAttrib = this->attributes[index]; + const auto& otherAttrib = other.attributes[index]; + + if (thisAttrib.bufferIndex != otherAttrib.bufferIndex || + thisAttrib.format != otherAttrib.format || + thisAttrib.offset != otherAttrib.offset) + { + return false; + } + + if (this->bufferLayouts[thisAttrib.bufferIndex].stride != + other.bufferLayouts[thisAttrib.bufferIndex].stride) + { + return false; + } + } + + index++; + allBits >>= 1; + } + + return true; + } }; - struct Vertex + void FillIndices(TriangleIndexMode mode, uint16_t start, uint16_t count, uint16_t* indices); + + void FillIndices(TriangleIndexMode mode, uint32_t start, uint32_t count, uint32_t* indices); + + static inline int GetIndexCount(TriangleIndexMode mode, int vertexCount) { - Vector3 position; - std::array color; - std::array texcoord; - }; + switch (mode) + { + case TRIANGLEINDEX_NONE: + return 0; + case TRIANGLEINDEX_STRIP: + case TRIANGLEINDEX_FAN: + return 3 * (vertexCount - 2); + case TRIANGLEINDEX_QUADS: + return vertexCount * 6 / 4; + } + + return 0; + } + + static inline size_t GetFormatStride(CommonFormat format) + { + switch (format) + { + case CommonFormat::NONE: + return 0; + case CommonFormat::XYf: + return sizeof(float) * 2; + case CommonFormat::XYZf: + return sizeof(float) * 3; + case CommonFormat::STf_RGBAf: + return sizeof(STf_RGBAf); + default: + return 0; + } + + return 0; + } + + static inline CommonFormat GetSinglePositionFormat(bool is2D) + { + /* the 3DS GPU expects us to upload all 3 vertices */ + if (is2D && Console::Is(Console::CTR)) + return CommonFormat::XYZf; + + return is2D ? CommonFormat::XYf : CommonFormat::XYZf; + } + + const DataFormatInfo& GetDataFormatInfo(DataFormat format); static constexpr size_t VERTEX_SIZE = sizeof(Vertex); @@ -102,10 +462,10 @@ namespace love }; static constexpr BidirectionalMap triangleModes = { - "none", TRIANGLE_NONE, - "strip", TRIANGLE_STRIP, - "fan", TRIANGLE_FAN, - "quads", TRIANGLE_QUADS + "none", TRIANGLEINDEX_NONE, + "strip", TRIANGLEINDEX_STRIP, + "fan", TRIANGLEINDEX_FAN, + "quads", TRIANGLEINDEX_QUADS }; static constexpr BidirectionalMap primitiveTypes = { diff --git a/platform/ctr/include/utilities/driver/renderer_ext.hpp b/platform/ctr/include/utilities/driver/renderer_ext.hpp index a684c8f2..a6de9ab8 100644 --- a/platform/ctr/include/utilities/driver/renderer_ext.hpp +++ b/platform/ctr/include/utilities/driver/renderer_ext.hpp @@ -84,6 +84,17 @@ namespace love void SetColorMask(const RenderState::ColorMask& mask); + void PrepareDraw(Graphics* gfx); + + void SetVertexAttributes(const vertex::VertexAttributes& attributes, + const BufferBindings& buffers); + + void BindTextureToUnit(Texture* texture, int unit); + + void DrawArrays(PrimitiveType mode, size_t start, size_t count); + + void DrawElements(PrimitiveType mode, size_t count, uint16_t* data, size_t offset); + Framebuffer& GetCurrent() { return this->targets[love::GetActiveScreen()]; diff --git a/platform/ctr/source/modules/graphics_ext.cpp b/platform/ctr/source/modules/graphics_ext.cpp index 768b3dd0..325b8853 100644 --- a/platform/ctr/source/modules/graphics_ext.cpp +++ b/platform/ctr/source/modules/graphics_ext.cpp @@ -45,8 +45,7 @@ void Graphics::Draw(Drawable* drawable, const Matrix4& matrix) drawable->Draw(*this, matrix); } -void Graphics::Draw(Texture* texture, Quad* quad, - const Matrix4& matrix) +void Graphics::Draw(Texture* texture, Quad* quad, const Matrix4& matrix) { texture->Draw(*this, quad, matrix); } @@ -92,5 +91,18 @@ void Graphics::SetMode(int x, int y, int width, int height) if (!Shader::current) Shader::defaults[Shader<>::STANDARD_DEFAULT]->Attach(); + if (this->batchedDrawState.vertexBuffer[0] == nullptr) + { + // Initial sizes that should be good enough for most cases. It will + // resize to fit if needed, later. + batchedDrawState.vertexBuffer[0] = CreateStreamBuffer(BUFFERUSAGE_VERTEX, 1024 * 1024 * 1); + batchedDrawState.vertexBuffer[1] = CreateStreamBuffer(BUFFERUSAGE_VERTEX, 256 * 1024 * 1); + batchedDrawState.indexBuffer = + CreateStreamBuffer(BUFFERUSAGE_INDEX, sizeof(uint16_t) * LOVE_UINT16_MAX); + } + + if (!Volatile::LoadAll()) + LOG("Failed to reload all Volatile objects."); + this->created = true; } diff --git a/platform/ctr/source/objects/texture_ext.cpp b/platform/ctr/source/objects/texture_ext.cpp index 6d01d84a..256ffa3f 100644 --- a/platform/ctr/source/objects/texture_ext.cpp +++ b/platform/ctr/source/objects/texture_ext.cpp @@ -270,8 +270,7 @@ void Texture::ReplacePixels(const void* data, size_t size, int sli C3D_TexFlush(this->texture); } -void Texture::Draw(Graphics& graphics, - const Matrix4& matrix) +void Texture::Draw(Graphics& graphics, const Matrix4& matrix) { this->Draw(graphics, this->quad, matrix); } @@ -333,17 +332,28 @@ void Texture::Draw(Graphics& graphics, Quad* quad, const auto& transform = graphics.GetTransform(); bool is2D = transform.IsAffine2DTransform(); - Matrix4 translated(graphics.GetTransform(), matrix); + Graphics<>::BatchedDrawCommand command {}; + command.formats[0] = vertex::GetSinglePositionFormat(is2D); + command.formats[1] = CommonFormat::STf_RGBAf; + command.indexMode = TRIANGLEINDEX_QUADS; + command.vertexCount = 4; + command.texture = this; - DrawCommand command(0x04, vertex::PRIMITIVE_TRIANGLE_FAN); - command.handles = { this->texture }; - command.format = CommonFormat::TEXTURE; + auto data = graphics.RequestBatchedDraw(command); + Matrix4 transformed(transform, matrix); if (is2D) - translated.TransformXY(std::span(command.Positions().get(), command.count), std::span(quad->GetVertexPositions(), command.count)); + transformed.TransformXY((Vector2*)data.stream[0], quad->GetVertexPositions(), 4); + else + transformed.TransformXY0((Vector3*)data.stream[0], quad->GetVertexPositions(), 4); - const auto* coords = quad->GetVertexTextureCoords(); - command.FillVertices(graphics.GetColor(), coords); + const auto* texCoords = quad->GetVertexTextureCoords(); + auto* vertexData = (vertex::STf_RGBAf*)data.stream[1]; - Renderer::Instance().Render(command); + for (int index = 0; index < 4; index++) + { + vertexData[index].s = texCoords[index].x; + vertexData[index].t = texCoords[index].y; + vertexData[index].color = graphics.GetColor(); + } } diff --git a/platform/ctr/source/utilities/driver/renderer/renderer_ext.cpp b/platform/ctr/source/utilities/driver/renderer/renderer_ext.cpp index 8a705523..c0413971 100644 --- a/platform/ctr/source/utilities/driver/renderer/renderer_ext.cpp +++ b/platform/ctr/source/utilities/driver/renderer/renderer_ext.cpp @@ -35,18 +35,6 @@ Renderer::Renderer() : targets {}, currentTexture(nullptr) AttrInfo_AddLoader(attributes, 1, GPU_FLOAT, 4); // color AttrInfo_AddLoader(attributes, 2, GPU_FLOAT, 2); // texcoord - BufInfo_Init(&this->bufferInfo); - m_vertices = (Vertex*)linearAlloc(TOTAL_BUFFER_SIZE); - - if (!m_vertices) - throw love::Exception("Out of memory."); - - int result = BufInfo_Add(&this->bufferInfo, (void*)m_vertices, VERTEX_SIZE, 0x03, 0x210); - C3D_SetBufInfo(&this->bufferInfo); - - if (result < 0) - throw love::Exception("Failed to add C3D_BufInfo."); - Mtx_Identity(&s_projection); Mtx_Identity(&s_modelView); } @@ -166,6 +154,66 @@ void Renderer::FlushVertices() m_commands.clear(); } +void Renderer::PrepareDraw(Graphics* graphics) +{ + if (Shader::current != nullptr) + { + if (s_dirtyProjection) + { + const auto uniforms = Shader::current->GetUniformLocations(); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uniforms.uLocProjMtx, &s_projection); + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uniforms.uLocMdlView, &s_modelView); + + s_dirtyProjection = false; + } + } +} + +void Renderer::SetVertexAttributes(const vertex::VertexAttributes& attributes, + const BufferBindings& buffers) +{} + +void Renderer::BindTextureToUnit(Texture* texture, int unit) +{ + if (texture == nullptr) + return; + + if (this->currentTexture == texture->GetHandle()) + return; + + this->currentTexture = texture->GetHandle(); + C3D_TexBind(unit, this->currentTexture); +} + +void Renderer::DrawArrays(PrimitiveType mode, size_t start, size_t count) +{ + if (s_primitiveType != mode) + { + if (!(s_primitive = primitiveModes.Find(mode))) + throw love::Exception("Invalid primitive mode"); + + s_primitiveType = mode; + } + + ++drawCallsBatched; + C3D_DrawArrays(*s_primitive, start, count); +} + +void Renderer::DrawElements(PrimitiveType mode, size_t count, uint16_t* data, + size_t offset) +{ + if (s_primitiveType != mode) + { + if (!(s_primitive = primitiveModes.Find(mode))) + throw love::Exception("Invalid primitive mode"); + + s_primitiveType = mode; + } + + ++drawCallsBatched; + C3D_DrawElements(*s_primitive, count, C3D_UNSIGNED_SHORT, &data[offset]); +} + bool Renderer::Render(DrawCommand& command) { Shader::defaults[command.shader]->Attach(); diff --git a/source/common/volatile.cpp b/source/common/volatile.cpp new file mode 100644 index 00000000..48dd1c0b --- /dev/null +++ b/source/common/volatile.cpp @@ -0,0 +1,30 @@ +#include + +using namespace love; + +std::list Volatile::all; + +Volatile::Volatile() +{ + all.push_back(this); +} + +Volatile::~Volatile() +{ + all.remove(this); +} + +bool Volatile::LoadAll() +{ + bool success = true; + for (Volatile* v : all) + success = success && v->LoadVolatile(); + + return success; +} + +void Volatile::UnloadAll() +{ + for (Volatile* v : all) + v->UnloadVolatile(); +} diff --git a/source/utilities/driver/renderer/polyline/polyline.cpp b/source/utilities/driver/renderer/polyline/polyline.cpp index 4e08c2da..205f706a 100644 --- a/source/utilities/driver/renderer/polyline/polyline.cpp +++ b/source/utilities/driver/renderer/polyline/polyline.cpp @@ -79,7 +79,7 @@ void Polyline::render(const Vector2* coords, size_t count, size_t size_hint, flo // extra degenerate triangle in between the core line and the overdraw // line in order to break up the strip into two. This will let us draw // everything in one draw call. - if (triangle_mode == vertex::TRIANGLE_STRIP) + if (triangle_mode == vertex::TRIANGLEINDEX_STRIP) extra_vertices = 2; } @@ -179,7 +179,7 @@ void Polyline::draw(Graphics* gfx) int maxvertices = LOVE_UINT16_MAX - 3; int advance = maxvertices; - if (triangle_mode == vertex::TRIANGLE_STRIP) + if (triangle_mode == vertex::TRIANGLEINDEX_STRIP) advance -= 2; for (int vertex_start = 0; vertex_start < total_vertex_count; vertex_start += advance) @@ -189,7 +189,7 @@ void Polyline::draw(Graphics* gfx) int totalVertices = std::min(maxvertices, total_vertex_count - vertex_start); auto mode = vertex::PRIMITIVE_TRIANGLE_STRIP; - if (this->triangle_mode == vertex::TRIANGLE_QUADS) + if (this->triangle_mode == vertex::TRIANGLEINDEX_QUADS) mode = vertex::PRIMITIVE_QUADS; DrawCommand command(totalVertices, mode); diff --git a/source/utilities/driver/renderer/vertex.cpp b/source/utilities/driver/renderer/vertex.cpp new file mode 100644 index 00000000..82ccfa3f --- /dev/null +++ b/source/utilities/driver/renderer/vertex.cpp @@ -0,0 +1,133 @@ +#include + +using namespace love::vertex; + +template +static void fillIndicesT(TriangleIndexMode mode, T start, T count, T* indices) +{ + switch (mode) + { + case TRIANGLEINDEX_NONE: + break; + case TRIANGLEINDEX_STRIP: + { + int i = 0; + for (T index = 0; index < count - 2; index++) + { + indices[i++] = start + index; + indices[i++] = start + index + 1 + (index & 1); + indices[i++] = start + index + 2 - (index & 1); + } + break; + } + case TRIANGLEINDEX_FAN: + { + int i = 0; + for (T index = 2; index < count; index++) + { + indices[i++] = start; + indices[i++] = start + index + 1; + indices[i++] = start + index; + } + break; + } + case TRIANGLEINDEX_QUADS: + { + // 0---3 + // | \ | + // 1---2 + + int total = count / 4; + for (int index = 0; index < total; index++) + { + const int i = index * 6; + T vertex = T(start + index * 4); + + indices[i + 0] = vertex + 0; + indices[i + 1] = vertex + 1; + indices[i + 2] = vertex + 2; + + indices[i + 3] = vertex + 2; + indices[i + 4] = vertex + 3; + indices[i + 5] = vertex + 0; + } + break; + } + } +} + +// clang-format off +// Order here relies on order of DataFormat enum. +static const DataFormatInfo dataFormatInfo[] +{ + // baseType, isMatrix, components, rows, columns, componentSize, size + { DATA_BASETYPE_FLOAT, false, 1, 0, 0, 4, 4 }, // DATAFORMAT_FLOAT + { DATA_BASETYPE_FLOAT, false, 2, 0, 0, 4, 8 }, // DATAFORMAT_FLOAT_VEC2 + { DATA_BASETYPE_FLOAT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_FLOAT_VEC3 + { DATA_BASETYPE_FLOAT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_FLOAT_VEC4 + + { DATA_BASETYPE_FLOAT, true, 0, 2, 2, 4, 16 }, // DATAFORMAT_FLOAT_MAT2X2 + { DATA_BASETYPE_FLOAT, true, 0, 2, 3, 4, 24 }, // DATAFORMAT_FLOAT_MAT2X3 + { DATA_BASETYPE_FLOAT, true, 0, 2, 4, 4, 32 }, // DATAFORMAT_FLOAT_MAT2X4 + + { DATA_BASETYPE_FLOAT, true, 0, 3, 2, 4, 24 }, // DATAFORMAT_FLOAT_MAT3X2 + { DATA_BASETYPE_FLOAT, true, 0, 3, 3, 4, 36 }, // DATAFORMAT_FLOAT_MAT3X3 + { DATA_BASETYPE_FLOAT, true, 0, 3, 4, 4, 48 }, // DATAFORMAT_FLOAT_MAT3X4 + + { DATA_BASETYPE_FLOAT, true, 0, 4, 2, 4, 32 }, // DATAFORMAT_FLOAT_MAT4X2 + { DATA_BASETYPE_FLOAT, true, 0, 4, 3, 4, 48 }, // DATAFORMAT_FLOAT_MAT4X3 + { DATA_BASETYPE_FLOAT, true, 0, 4, 4, 4, 64 }, // DATAFORMAT_FLOAT_MAT4X4 + + { DATA_BASETYPE_INT, false, 1, 0, 0, 4, 4 }, // DATAFORMAT_INT32 + { DATA_BASETYPE_INT, false, 2, 0, 0, 4, 8 }, // DATAFORMAT_INT32_VEC2 + { DATA_BASETYPE_INT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_INT32_VEC3 + { DATA_BASETYPE_INT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_INT32_VEC4 + + { DATA_BASETYPE_UINT, false, 1, 0, 0, 4, 4 }, // DATAFORMAT_UINT32 + { DATA_BASETYPE_UINT, false, 2, 0, 0, 4, 8 }, // DATAFORMAT_UINT32_VEC2 + { DATA_BASETYPE_UINT, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_UINT32_VEC3 + { DATA_BASETYPE_UINT, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_UINT32_VEC4 + + { DATA_BASETYPE_SNORM, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_SNORM8_VEC4 + { DATA_BASETYPE_UNORM, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_UNORM8_VEC4 + { DATA_BASETYPE_INT, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_INT8_VEC4 + { DATA_BASETYPE_UINT, false, 4, 0, 0, 1, 4 }, // DATAFORMAT_UINT8_VEC4 + + { DATA_BASETYPE_SNORM, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_SNORM16_VEC2 + { DATA_BASETYPE_SNORM, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_SNORM16_VEC4 + + { DATA_BASETYPE_UNORM, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_UNORM16_VEC2 + { DATA_BASETYPE_UNORM, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_UNORM16_VEC4 + + { DATA_BASETYPE_INT, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_INT16_VEC2 + { DATA_BASETYPE_INT, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_INT16_VEC4 + + { DATA_BASETYPE_UINT, false, 1, 0, 0, 2, 2 }, // DATAFORMAT_UINT16 + { DATA_BASETYPE_UINT, false, 2, 0, 0, 2, 4 }, // DATAFORMAT_UINT16_VEC2 + { DATA_BASETYPE_UINT, false, 4, 0, 0, 2, 8 }, // DATAFORMAT_UINT16_VEC4 + + { DATA_BASETYPE_BOOL, false, 1, 0, 0, 4, 4 }, // DATAFORMAT_BOOL + { DATA_BASETYPE_BOOL, false, 2, 0, 0, 4, 8 }, // DATAFORMAT_BOOL_VEC2 + { DATA_BASETYPE_BOOL, false, 3, 0, 0, 4, 12 }, // DATAFORMAT_BOOL_VEC3 + { DATA_BASETYPE_BOOL, false, 4, 0, 0, 4, 16 }, // DATAFORMAT_BOOL_VEC4 +}; +// clang-format on +static_assert((sizeof(dataFormatInfo) / sizeof(DataFormatInfo)) == DATAFORMAT_MAX_ENUM, + "dataFormatInfo array size must match number of DataFormat enum values."); + +const DataFormatInfo& GetDataFormatInfo(DataFormat format) +{ + return dataFormatInfo[format]; +} + +void love::vertex::FillIndices(TriangleIndexMode mode, uint16_t start, uint16_t count, + uint16_t* indices) +{ + fillIndicesT(mode, start, count, indices); +} + +void love::vertex::FillIndices(TriangleIndexMode mode, uint32_t start, uint32_t count, + uint32_t* indices) +{ + fillIndicesT(mode, start, count, indices); +}