From 9cdc576f6055cbb308551f09e3566b34233b226e Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 20 Jun 2019 03:44:06 -0300
Subject: [PATCH] gl_rasterizer: Fix vertex and index data invalidations

---
 .../renderer_opengl/gl_rasterizer.cpp         | 14 ++++-----
 .../renderer_opengl/gl_rasterizer.h           |  3 +-
 src/video_core/renderer_opengl/utils.cpp      | 31 +++++++++++++++++++
 src/video_core/renderer_opengl/utils.h        | 27 ++++++++++++++++
 4 files changed, 67 insertions(+), 8 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index b57d60856..f3527d65b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -198,9 +198,8 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
         const auto [vertex_buffer, vertex_buffer_offset] = buffer_cache.UploadMemory(start, size);
 
         // Bind the vertex array to the buffer at the current offset.
-        // FIXME(Rodrigo): This dereferenced pointer might be invalidated in future uploads.
-        glVertexArrayVertexBuffer(vao, index, *vertex_buffer, vertex_buffer_offset,
-                                  vertex_array.stride);
+        vertex_array_pushbuffer.SetVertexBuffer(index, vertex_buffer, vertex_buffer_offset,
+                                                vertex_array.stride);
 
         if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) {
             // Enable vertex buffer instancing with the specified divisor.
@@ -214,7 +213,7 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
     gpu.dirty_flags.vertex_array.reset();
 }
 
-GLintptr RasterizerOpenGL::SetupIndexBuffer(GLuint vao) {
+GLintptr RasterizerOpenGL::SetupIndexBuffer() {
     if (accelerate_draw != AccelDraw::Indexed) {
         return 0;
     }
@@ -222,8 +221,7 @@ GLintptr RasterizerOpenGL::SetupIndexBuffer(GLuint vao) {
     const auto& regs = system.GPU().Maxwell3D().regs;
     const std::size_t size = CalculateIndexBufferSize();
     const auto [buffer, offset] = buffer_cache.UploadMemory(regs.index_array.IndexStart(), size);
-    // FIXME(Rodrigo): This dereferenced pointer might be invalidated in future uploads.
-    glVertexArrayElementBuffer(vao, *buffer);
+    vertex_array_pushbuffer.SetIndexBuffer(buffer);
     return offset;
 }
 
@@ -644,10 +642,11 @@ void RasterizerOpenGL::DrawArrays() {
 
     // Prepare vertex array format.
     const GLuint vao = SetupVertexFormat();
+    vertex_array_pushbuffer.Setup(vao);
 
     // Upload vertex and index data.
     SetupVertexBuffer(vao);
-    const GLintptr index_buffer_offset = SetupIndexBuffer(vao);
+    const GLintptr index_buffer_offset = SetupIndexBuffer();
 
     // Setup draw parameters. It will automatically choose what glDraw* method to use.
     const DrawParameters params = SetupDraw(index_buffer_offset);
@@ -667,6 +666,7 @@ void RasterizerOpenGL::DrawArrays() {
     const bool invalidate = buffer_cache.Unmap();
 
     // Now that we are no longer uploading data, we can safely bind the buffers to OpenGL.
+    vertex_array_pushbuffer.Bind();
     bind_ubo_pushbuffer.Bind();
     bind_ssbo_pushbuffer.Bind();
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 7067ad5b4..1c915fd7f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -207,6 +207,7 @@ private:
     static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
     OGLBufferCache buffer_cache;
 
+    VertexArrayPushBuffer vertex_array_pushbuffer;
     BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER};
     BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER};
 
@@ -219,7 +220,7 @@ private:
 
     void SetupVertexBuffer(GLuint vao);
 
-    GLintptr SetupIndexBuffer(GLuint vao);
+    GLintptr SetupIndexBuffer();
 
     DrawParameters SetupDraw(GLintptr index_buffer_offset);
 
diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp
index 22eefa1d7..c504a2c1a 100644
--- a/src/video_core/renderer_opengl/utils.cpp
+++ b/src/video_core/renderer_opengl/utils.cpp
@@ -13,6 +13,37 @@
 
 namespace OpenGL {
 
+VertexArrayPushBuffer::VertexArrayPushBuffer() = default;
+
+VertexArrayPushBuffer::~VertexArrayPushBuffer() = default;
+
+void VertexArrayPushBuffer::Setup(GLuint vao_) {
+    vao = vao_;
+    index_buffer = nullptr;
+    vertex_buffers.clear();
+}
+
+void VertexArrayPushBuffer::SetIndexBuffer(const GLuint* buffer) {
+    index_buffer = buffer;
+}
+
+void VertexArrayPushBuffer::SetVertexBuffer(GLuint binding_index, const GLuint* buffer,
+                                            GLintptr offset, GLsizei stride) {
+    vertex_buffers.push_back(Entry{binding_index, buffer, offset, stride});
+}
+
+void VertexArrayPushBuffer::Bind() {
+    if (index_buffer) {
+        glVertexArrayElementBuffer(vao, *index_buffer);
+    }
+
+    // TODO(Rodrigo): Find a way to ARB_multi_bind this
+    for (const auto& entry : vertex_buffers) {
+        glVertexArrayVertexBuffer(vao, entry.binding_index, *entry.buffer, entry.offset,
+                                  entry.stride);
+    }
+}
+
 BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {}
 
 BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default;
diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h
index d2a3d25d9..6c2b45546 100644
--- a/src/video_core/renderer_opengl/utils.h
+++ b/src/video_core/renderer_opengl/utils.h
@@ -11,6 +11,33 @@
 
 namespace OpenGL {
 
+class VertexArrayPushBuffer final {
+public:
+    explicit VertexArrayPushBuffer();
+    ~VertexArrayPushBuffer();
+
+    void Setup(GLuint vao_);
+
+    void SetIndexBuffer(const GLuint* buffer);
+
+    void SetVertexBuffer(GLuint binding_index, const GLuint* buffer, GLintptr offset,
+                         GLsizei stride);
+
+    void Bind();
+
+private:
+    struct Entry {
+        GLuint binding_index{};
+        const GLuint* buffer{};
+        GLintptr offset{};
+        GLsizei stride{};
+    };
+
+    GLuint vao{};
+    const GLuint* index_buffer{};
+    std::vector<Entry> vertex_buffers;
+};
+
 class BindBuffersRangePushBuffer final {
 public:
     explicit BindBuffersRangePushBuffer(GLenum target);