From 8e9f23f393763a6d76605206eeb20f6f8885d9a9 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Mon, 2 Mar 2020 19:31:26 -0300
Subject: [PATCH] gl_rasterizer: Implement transform feedback bindings

---
 src/video_core/engines/maxwell_3d.h           |  9 +++
 .../renderer_opengl/gl_rasterizer.cpp         | 70 +++++++++++++++++--
 .../renderer_opengl/gl_rasterizer.h           | 14 +++-
 3 files changed, 83 insertions(+), 10 deletions(-)

diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 8752a1cfb..ba9c76593 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -634,6 +634,11 @@ public:
             u32 address_low;
             s32 buffer_size;
             s32 buffer_offset;
+
+            GPUVAddr Address() const {
+                return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+                                             address_low);
+            }
         };
         static_assert(sizeof(TransformFeedbackBinding) == 32);
 
@@ -652,6 +657,10 @@ public:
             return shader_config[index].enable != 0;
         }
 
+        bool IsShaderConfigEnabled(Regs::ShaderProgram type) const {
+            return IsShaderConfigEnabled(static_cast<std::size_t>(type));
+        }
+
         union {
             struct {
                 INSERT_UNION_PADDING_WORDS(0x45);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 8a2db8e36..1af4268a4 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -496,7 +496,6 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
     SyncCullMode();
     SyncPrimitiveRestart();
     SyncScissorTest();
-    SyncTransformFeedback();
     SyncPointState();
     SyncPolygonOffset();
     SyncAlphaTest();
@@ -569,7 +568,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
         glTextureBarrier();
     }
 
-    ++num_queued_commands;
+    BeginTransformFeedback(primitive_mode);
 
     const GLuint base_instance = static_cast<GLuint>(gpu.regs.vb_base_instance);
     const GLsizei num_instances =
@@ -608,6 +607,10 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
                                               num_instances, base_instance);
         }
     }
+
+    EndTransformFeedback();
+
+    ++num_queued_commands;
 }
 
 void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
@@ -1290,11 +1293,6 @@ void RasterizerOpenGL::SyncScissorTest() {
     }
 }
 
-void RasterizerOpenGL::SyncTransformFeedback() {
-    const auto& regs = system.GPU().Maxwell3D().regs;
-    UNIMPLEMENTED_IF_MSG(regs.tfb_enabled != 0, "Transform feedbacks are not implemented");
-}
-
 void RasterizerOpenGL::SyncPointState() {
     auto& gpu = system.GPU().Maxwell3D();
     auto& flags = gpu.dirty.flags;
@@ -1370,4 +1368,62 @@ void RasterizerOpenGL::SyncFramebufferSRGB() {
     oglEnable(GL_FRAMEBUFFER_SRGB, gpu.regs.framebuffer_srgb);
 }
 
+void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
+    const auto& regs = system.GPU().Maxwell3D().regs;
+    if (regs.tfb_enabled == 0) {
+        return;
+    }
+
+    UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationControl) ||
+                     regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::TesselationEval) ||
+                     regs.IsShaderConfigEnabled(Maxwell::ShaderProgram::Geometry));
+
+    for (std::size_t index = 0; index < Maxwell::NumTransformFeedbackBuffers; ++index) {
+        const auto& binding = regs.tfb_bindings[index];
+        if (!binding.buffer_enable) {
+            if (enabled_transform_feedback_buffers[index]) {
+                glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(index), 0, 0,
+                                  0);
+            }
+            enabled_transform_feedback_buffers[index] = false;
+            continue;
+        }
+        enabled_transform_feedback_buffers[index] = true;
+
+        auto& tfb_buffer = transform_feedback_buffers[index];
+        tfb_buffer.Create();
+
+        const GLuint handle = tfb_buffer.handle;
+        const std::size_t size = binding.buffer_size;
+        glNamedBufferData(handle, static_cast<GLsizeiptr>(size), nullptr, GL_STREAM_COPY);
+        glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(index), handle, 0,
+                          static_cast<GLsizeiptr>(size));
+    }
+
+    glBeginTransformFeedback(GL_POINTS);
+}
+
+void RasterizerOpenGL::EndTransformFeedback() {
+    const auto& regs = system.GPU().Maxwell3D().regs;
+    if (regs.tfb_enabled == 0) {
+        return;
+    }
+
+    glEndTransformFeedback();
+
+    for (std::size_t index = 0; index < Maxwell::NumTransformFeedbackBuffers; ++index) {
+        const auto& binding = regs.tfb_bindings[index];
+        if (!binding.buffer_enable) {
+            continue;
+        }
+        UNIMPLEMENTED_IF(binding.buffer_offset != 0);
+
+        const GLuint handle = transform_feedback_buffers[index].handle;
+        const GPUVAddr gpu_addr = binding.Address();
+        const std::size_t size = binding.buffer_size;
+        const auto [dest_buffer, offset] = buffer_cache.UploadMemory(gpu_addr, size, 4, true);
+        glCopyNamedBufferSubData(handle, *dest_buffer, 0, offset, static_cast<GLsizeiptr>(size));
+    }
+}
+
 } // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index e6424f5d2..2d3be2437 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -168,9 +168,6 @@ private:
     /// Syncs the scissor test state to match the guest state
     void SyncScissorTest();
 
-    /// Syncs the transform feedback state to match the guest state
-    void SyncTransformFeedback();
-
     /// Syncs the point state to match the guest state
     void SyncPointState();
 
@@ -192,6 +189,12 @@ private:
     /// Syncs the framebuffer sRGB state to match the guest state
     void SyncFramebufferSRGB();
 
+    /// Begin a transform feedback
+    void BeginTransformFeedback(GLenum primitive_mode);
+
+    /// End a transform feedback
+    void EndTransformFeedback();
+
     /// Check for extension that are not strictly required but are needed for correct emulation
     void CheckExtensions();
 
@@ -229,6 +232,11 @@ private:
     BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER};
     BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER};
 
+    std::array<OGLBuffer, Tegra::Engines::Maxwell3D::Regs::NumTransformFeedbackBuffers>
+        transform_feedback_buffers;
+    std::bitset<Tegra::Engines::Maxwell3D::Regs::NumTransformFeedbackBuffers>
+        enabled_transform_feedback_buffers;
+
     /// Number of commands queued to the OpenGL driver. Reseted on flush.
     std::size_t num_queued_commands = 0;