From 720882efeb10c086944524e399d17bb423b0c92e Mon Sep 17 00:00:00 2001
From: Samuliak <samuliak77@gmail.com>
Date: Wed, 3 Jul 2024 14:31:15 +0200
Subject: [PATCH] store vertices in 1 big buffer

---
 .../renderer_mtl/mtl_vertex_buffer_cache.hpp  | 71 +++++++++++++++++++
 include/renderer_mtl/renderer_mtl.hpp         |  2 +
 src/core/renderer_mtl/renderer_mtl.cpp        | 11 ++-
 3 files changed, 81 insertions(+), 3 deletions(-)
 create mode 100644 include/renderer_mtl/mtl_vertex_buffer_cache.hpp

diff --git a/include/renderer_mtl/mtl_vertex_buffer_cache.hpp b/include/renderer_mtl/mtl_vertex_buffer_cache.hpp
new file mode 100644
index 00000000..778499cf
--- /dev/null
+++ b/include/renderer_mtl/mtl_vertex_buffer_cache.hpp
@@ -0,0 +1,71 @@
+#pragma once
+
+#include <map>
+#include "pica_to_mtl.hpp"
+
+using namespace PICA;
+
+namespace Metal {
+
+struct BufferHandle {
+    MTL::Buffer* buffer;
+    size_t offset;
+};
+
+// 64MB buffer for caching vertex data
+#define CACHE_BUFFER_SIZE 64 * 1024 * 1024
+
+class VertexBufferCache {
+public:
+    VertexBufferCache() = default;
+
+    ~VertexBufferCache() {
+        clear();
+    }
+
+    void set(MTL::Device* dev) {
+        device = dev;
+        buffer = device->newBuffer(CACHE_BUFFER_SIZE, MTL::ResourceStorageModeShared);
+    }
+
+    void endFrame() {
+        ptr = 0;
+        for (auto buffer : additionalAllocations) {
+            buffer->release();
+        }
+        additionalAllocations.clear();
+    }
+
+    BufferHandle get(const std::span<const PICA::Vertex>& vertices) {
+        // If the vertex buffer is too large, just create a new one
+        if (ptr + vertices.size_bytes() > CACHE_BUFFER_SIZE) {
+            MTL::Buffer* newBuffer = device->newBuffer(vertices.data(), vertices.size_bytes(), MTL::ResourceStorageModeShared);
+            additionalAllocations.push_back(newBuffer);
+            Helpers::warn("Vertex buffer doesn't have enough space, creating a new buffer");
+
+            return BufferHandle{newBuffer, 0};
+        }
+
+        // Copy the data into the buffer
+        memcpy((char*)buffer->contents() + ptr, vertices.data(), vertices.size_bytes());
+
+        size_t oldPtr = ptr;
+        ptr += vertices.size_bytes();
+
+        return BufferHandle{buffer, oldPtr};
+    }
+
+    void clear() {
+        endFrame();
+        buffer->release();
+    }
+
+private:
+    MTL::Buffer* buffer;
+    size_t ptr = 0;
+    std::vector<MTL::Buffer*> additionalAllocations;
+
+    MTL::Device* device;
+};
+
+} // namespace Metal
diff --git a/include/renderer_mtl/renderer_mtl.hpp b/include/renderer_mtl/renderer_mtl.hpp
index 51e3f97e..8ac3f2c2 100644
--- a/include/renderer_mtl/renderer_mtl.hpp
+++ b/include/renderer_mtl/renderer_mtl.hpp
@@ -6,6 +6,7 @@
 #include "render_target.hpp"
 #include "mtl_pipeline_cache.hpp"
 #include "mtl_depth_stencil_cache.hpp"
+#include "mtl_vertex_buffer_cache.hpp"
 // HACK: use the OpenGL cache
 #include "../renderer_gl/surface_cache.hpp"
 
@@ -43,6 +44,7 @@ class RendererMTL final : public Renderer {
 	Metal::PipelineCache blitPipelineCache;
 	Metal::PipelineCache drawPipelineCache;
 	Metal::DepthStencilCache depthStencilCache;
+	Metal::VertexBufferCache vertexBufferCache;
 
 	// Helpers
 	MTL::SamplerState* basicSampler;
diff --git a/src/core/renderer_mtl/renderer_mtl.cpp b/src/core/renderer_mtl/renderer_mtl.cpp
index c2fe9f1e..01a40f0a 100644
--- a/src/core/renderer_mtl/renderer_mtl.cpp
+++ b/src/core/renderer_mtl/renderer_mtl.cpp
@@ -78,6 +78,9 @@ void RendererMTL::display() {
 
 	commandBuffer->presentDrawable(drawable);
 	commitCommandBuffer();
+
+	// Inform the vertex buffer cache that the frame ended
+	vertexBufferCache.endFrame();
 }
 
 void RendererMTL::initGraphicsContext(SDL_Window* window) {
@@ -191,6 +194,9 @@ void RendererMTL::initGraphicsContext(SDL_Window* window) {
 
 	// Depth stencil cache
 	depthStencilCache.set(device);
+
+	// Vertex buffer cache
+	vertexBufferCache.set(device);
 }
 
 void RendererMTL::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {
@@ -382,9 +388,8 @@ void RendererMTL::drawVertices(PICA::PrimType primType, std::span<const PICA::Ve
 	if (vertices.size_bytes() < 4 * 1024) {
 		renderCommandEncoder->setVertexBytes(vertices.data(), vertices.size_bytes(), VERTEX_BUFFER_BINDING_INDEX);
 	} else {
-		// TODO: cache this buffer
-		MTL::Buffer* vertexBuffer = device->newBuffer(vertices.data(), vertices.size_bytes(), MTL::ResourceStorageModeShared);
-		renderCommandEncoder->setVertexBuffer(vertexBuffer, 0, VERTEX_BUFFER_BINDING_INDEX);
+	    Metal::BufferHandle buffer = vertexBufferCache.get(vertices);
+		renderCommandEncoder->setVertexBuffer(buffer.buffer, buffer.offset, VERTEX_BUFFER_BINDING_INDEX);
 	}
 
 	// Bind resources