From 812e32f7753d452f5c6776fa18e2b2a26b4ff3bb Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Sun, 25 Oct 2020 17:23:42 -0300
Subject: [PATCH] Fix transform feedback errors caused by host pause/resume and
 multiple uses (#1634)

* Fix transform feedback errors caused by host pause/resume

* Fix TFB being used as something else issue with copies

* This is supposed to be StreamCopy
---
 Ryujinx.Graphics.GAL/BufferRange.cs          |  2 +-
 Ryujinx.Graphics.GAL/IPipeline.cs            |  2 +-
 Ryujinx.Graphics.Gpu/Memory/BufferManager.cs | 11 +--
 Ryujinx.Graphics.OpenGL/Buffer.cs            | 11 +++
 Ryujinx.Graphics.OpenGL/Constants.cs         |  1 +
 Ryujinx.Graphics.OpenGL/Pipeline.cs          | 89 +++++++++++++++-----
 Ryujinx.Graphics.OpenGL/Program.cs           |  6 +-
 7 files changed, 92 insertions(+), 30 deletions(-)

diff --git a/Ryujinx.Graphics.GAL/BufferRange.cs b/Ryujinx.Graphics.GAL/BufferRange.cs
index 34d523f9f..9a030c808 100644
--- a/Ryujinx.Graphics.GAL/BufferRange.cs
+++ b/Ryujinx.Graphics.GAL/BufferRange.cs
@@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.GAL
     {
         private static readonly BufferRange _empty = new BufferRange(BufferHandle.Null, 0, 0);
 
-        public BufferRange Empty => _empty;
+        public static BufferRange Empty => _empty;
 
         public BufferHandle Handle { get; }
 
diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
index a7f138938..818712c32 100644
--- a/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.GAL
 
         void SetTexture(int index, ShaderStage stage, ITexture texture);
 
-        void SetTransformFeedbackBuffer(int index, BufferRange buffer);
+        void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
         void SetUniformBuffer(int index, ShaderStage stage, BufferRange buffer);
 
         void SetUserClipDistance(int index, bool enableClip);
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index ee1be74b1..eec545b91 100644
--- a/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -587,21 +587,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
             {
                 _transformFeedbackBuffersDirty = false;
 
+                Span<BufferRange> tfbs = stackalloc BufferRange[Constants.TotalTransformFeedbackBuffers];
+
                 for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++)
                 {
                     BufferBounds tfb = _transformFeedbackBuffers[index];
 
                     if (tfb.Address == 0)
                     {
-                        _context.Renderer.Pipeline.SetTransformFeedbackBuffer(index, new BufferRange(BufferHandle.Null, 0, 0));
-
+                        tfbs[index] = BufferRange.Empty;
                         continue;
                     }
 
-                    BufferRange buffer = GetBufferRange(tfb.Address, tfb.Size);
-
-                    _context.Renderer.Pipeline.SetTransformFeedbackBuffer(index, buffer);
+                    tfbs[index] = GetBufferRange(tfb.Address, tfb.Size);
                 }
+
+                _context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
             }
             else
             {
diff --git a/Ryujinx.Graphics.OpenGL/Buffer.cs b/Ryujinx.Graphics.OpenGL/Buffer.cs
index e8fd9a6bf..89216b834 100644
--- a/Ryujinx.Graphics.OpenGL/Buffer.cs
+++ b/Ryujinx.Graphics.OpenGL/Buffer.cs
@@ -6,6 +6,11 @@ namespace Ryujinx.Graphics.OpenGL
 {
     static class Buffer
     {
+        public static BufferHandle Create()
+        {
+            return Handle.FromInt32<BufferHandle>(GL.GenBuffer());
+        }
+
         public static BufferHandle Create(int size)
         {
             int handle = GL.GenBuffer();
@@ -40,6 +45,12 @@ namespace Ryujinx.Graphics.OpenGL
             return data;
         }
 
+        public static void Resize(BufferHandle handle, int size)
+        {
+            GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
+            GL.BufferData(BufferTarget.CopyWriteBuffer, size, IntPtr.Zero, BufferUsageHint.StreamCopy);
+        }
+
         public static void SetData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
         {
             GL.BindBuffer(BufferTarget.CopyWriteBuffer, buffer.ToInt32());
diff --git a/Ryujinx.Graphics.OpenGL/Constants.cs b/Ryujinx.Graphics.OpenGL/Constants.cs
index 9775b240d..8817011a9 100644
--- a/Ryujinx.Graphics.OpenGL/Constants.cs
+++ b/Ryujinx.Graphics.OpenGL/Constants.cs
@@ -6,5 +6,6 @@
         public const int MaxViewports = 16;
         public const int MaxVertexAttribs = 16;
         public const int MaxVertexBuffers = 16;
+        public const int MaxTransformFeedbackBuffers = 4;
     }
 }
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 2650e9ee5..6277fe168 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -5,7 +5,6 @@ using Ryujinx.Graphics.OpenGL.Image;
 using Ryujinx.Graphics.OpenGL.Queries;
 using Ryujinx.Graphics.Shader;
 using System;
-using System.Threading;
 
 namespace Ryujinx.Graphics.OpenGL
 {
@@ -49,6 +48,10 @@ namespace Ryujinx.Graphics.OpenGL
         private bool _scissor0Enable = false;
 
         private bool _tfEnabled;
+        private TransformFeedbackPrimitiveType _tfTopology;
+
+        private readonly BufferHandle[] _tfbs;
+        private readonly BufferRange[] _tfbTargets;
 
         private ColorF _blendConstant;
 
@@ -74,6 +77,9 @@ namespace Ryujinx.Graphics.OpenGL
             {
                 _cpRenderScale[index] = 1f;
             }
+
+            _tfbs = new BufferHandle[Constants.MaxTransformFeedbackBuffers];
+            _tfbTargets = new BufferRange[Constants.MaxTransformFeedbackBuffers];
         }
 
         public void Barrier()
@@ -83,7 +89,7 @@ namespace Ryujinx.Graphics.OpenGL
 
         public void BeginTransformFeedback(PrimitiveTopology topology)
         {
-            GL.BeginTransformFeedback(topology.ConvertToTfType());
+            GL.BeginTransformFeedback(_tfTopology = topology.ConvertToTfType());
             _tfEnabled = true;
         }
 
@@ -175,7 +181,7 @@ namespace Ryujinx.Graphics.OpenGL
                 return;
             }
 
-            PrepareForDraw();
+            PreDraw();
 
             if (_primitiveType == PrimitiveType.Quads)
             {
@@ -190,7 +196,7 @@ namespace Ryujinx.Graphics.OpenGL
                 DrawImpl(vertexCount, instanceCount, firstVertex, firstInstance);
             }
 
-            _framebuffer.SignalModified();
+            PostDraw();
         }
 
         private void DrawQuadsImpl(
@@ -293,7 +299,7 @@ namespace Ryujinx.Graphics.OpenGL
                 return;
             }
 
-            PrepareForDraw();
+            PreDraw();
 
             int indexElemSize = 1;
 
@@ -335,7 +341,7 @@ namespace Ryujinx.Graphics.OpenGL
                     firstInstance);
             }
 
-            _framebuffer.SignalModified();
+            PostDraw();
         }
 
         private void DrawQuadsIndexedImpl(
@@ -790,9 +796,9 @@ namespace Ryujinx.Graphics.OpenGL
 
             if (_tfEnabled)
             {
-                GL.PauseTransformFeedback();
+                GL.EndTransformFeedback();
                 _program.Bind();
-                GL.ResumeTransformFeedback();
+                GL.BeginTransformFeedback(_tfTopology);
             }
             else
             {
@@ -993,19 +999,39 @@ namespace Ryujinx.Graphics.OpenGL
             }
         }
 
-        public void SetTransformFeedbackBuffer(int index, BufferRange buffer)
+        public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
         {
-            const BufferRangeTarget target = BufferRangeTarget.TransformFeedbackBuffer;
+            if (_tfEnabled)
+            {
+                GL.EndTransformFeedback();
+            }
+
+            int count = Math.Min(buffers.Length, Constants.MaxTransformFeedbackBuffers);
+
+            for (int i = 0; i < count; i++)
+            {
+                BufferRange buffer = buffers[i];
+                _tfbTargets[i] = buffer;
+
+                if (buffer.Handle == BufferHandle.Null)
+                {
+                    GL.BindBufferBase(BufferRangeTarget.TransformFeedbackBuffer, i, 0);
+                    continue;
+                }
+
+                if (_tfbs[i] == BufferHandle.Null)
+                {
+                    _tfbs[i] = Buffer.Create();
+                }
+
+                Buffer.Resize(_tfbs[i], buffer.Size);
+                Buffer.Copy(buffer.Handle, _tfbs[i], buffer.Offset, 0, buffer.Size);
+                GL.BindBufferBase(BufferRangeTarget.TransformFeedbackBuffer, i, _tfbs[i].ToInt32());
+            }
 
             if (_tfEnabled)
             {
-                GL.PauseTransformFeedback();
-                GL.BindBufferRange(target, index, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size);
-                GL.ResumeTransformFeedback();
-            }
-            else
-            {
-                GL.BindBufferRange(target, index, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size);
+                GL.BeginTransformFeedback(_tfTopology);
             }
         }
 
@@ -1104,7 +1130,7 @@ namespace Ryujinx.Graphics.OpenGL
                 ? BufferRangeTarget.ShaderStorageBuffer
                 : BufferRangeTarget.UniformBuffer;
 
-            if (buffer.Handle == null)
+            if (buffer.Handle == BufferHandle.Null)
             {
                 GL.BindBufferRange(target, bindingPoint, 0, IntPtr.Zero, 0);
                 return;
@@ -1237,7 +1263,7 @@ namespace Ryujinx.Graphics.OpenGL
             }
         }
 
-        private void PrepareForDraw()
+        private void PreDraw()
         {
             _vertexArray.Validate();
 
@@ -1247,6 +1273,22 @@ namespace Ryujinx.Graphics.OpenGL
             }
         }
 
+        private void PostDraw()
+        {
+            _framebuffer?.SignalModified();
+
+            if (_tfEnabled)
+            {
+                for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++)
+                {
+                    if (_tfbTargets[i].Handle != BufferHandle.Null)
+                    {
+                        Buffer.Copy(_tfbs[i], _tfbTargets[i].Handle, 0, _tfbTargets[i].Offset, _tfbTargets[i].Size);
+                    }
+                }
+            }
+        }
+
         private void RestoreComponentMask(int index)
         {
             GL.ColorMask(
@@ -1319,6 +1361,15 @@ namespace Ryujinx.Graphics.OpenGL
 
         public void Dispose()
         {
+            for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++)
+            {
+                if (_tfbs[i] != BufferHandle.Null)
+                {
+                    Buffer.Delete(_tfbs[i]);
+                    _tfbs[i] = BufferHandle.Null;
+                }
+            }
+
             _framebuffer?.Dispose();
             _vertexArray?.Dispose();
         }
diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs
index 6e253140e..babe17a0d 100644
--- a/Ryujinx.Graphics.OpenGL/Program.cs
+++ b/Ryujinx.Graphics.OpenGL/Program.cs
@@ -131,8 +131,6 @@ namespace Ryujinx.Graphics.OpenGL
 
             CheckProgramLink();
 
-            Bind();
-
             int ubBindingPoint = 0;
             int sbBindingPoint = 0;
             int textureUnit    = 0;
@@ -189,7 +187,7 @@ namespace Ryujinx.Graphics.OpenGL
                         continue;
                     }
 
-                    GL.Uniform1(location, textureUnit);
+                    GL.ProgramUniform1(Handle, location, textureUnit);
 
                     int uIndex = (int)shader.Stage << TexStageShift | samplerIndex++;
 
@@ -209,7 +207,7 @@ namespace Ryujinx.Graphics.OpenGL
                         continue;
                     }
 
-                    GL.Uniform1(location, imageUnit);
+                    GL.ProgramUniform1(Handle, location, imageUnit);
 
                     int uIndex = (int)shader.Stage << ImgStageShift | imageIndex++;