From 5fe0bc584b21c660e083a9cb37aa0a8be4719f95 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 19 Jul 2018 16:02:51 -0300
Subject: [PATCH] Send data to OpenGL host without client-side copies (#285)

* Directly send host address to buffer data

* Cleanup OGLShader

* Directly copy vertex and index data too

* Revert shader bind "cache"

* Address feedback
---
 ChocolArm64/Memory/AMemory.cs                 |  7 ++
 Ryujinx.Graphics/Gal/IGalRasterizer.cs        |  6 +-
 Ryujinx.Graphics/Gal/IGalShader.cs            |  3 +-
 Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs  | 16 ++--
 Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs      | 22 ++---
 .../Gal/OpenGL/OGLStreamBuffer.cs             | 93 +++----------------
 Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs      | 12 +--
 Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs            |  6 ++
 8 files changed, 55 insertions(+), 110 deletions(-)

diff --git a/ChocolArm64/Memory/AMemory.cs b/ChocolArm64/Memory/AMemory.cs
index da5cf00749..054277b295 100644
--- a/ChocolArm64/Memory/AMemory.cs
+++ b/ChocolArm64/Memory/AMemory.cs
@@ -204,6 +204,13 @@ namespace ChocolArm64.Memory
             return Modified;
         }
 
+        public IntPtr GetHostAddress(long Position, long Size)
+        {
+            EnsureRangeIsValid(Position, Size, AMemoryPerm.Read);
+
+            return (IntPtr)(RamPtr + (ulong)Position);
+        }
+
         public sbyte ReadSByte(long Position)
         {
             return (sbyte)ReadByte(Position);
diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs
index 0c5d37e40e..a87d36c38f 100644
--- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs
+++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs
@@ -1,3 +1,5 @@
+using System;
+
 namespace Ryujinx.Graphics.Gal
 {
     public interface IGalRasterizer
@@ -45,9 +47,9 @@ namespace Ryujinx.Graphics.Gal
 
         void SetPrimitiveRestartIndex(uint Index);
 
-        void CreateVbo(long Key, byte[] Buffer);
+        void CreateVbo(long Key, int DataSize, IntPtr HostAddress);
 
-        void CreateIbo(long Key, byte[] Buffer);
+        void CreateIbo(long Key, int DataSize, IntPtr HostAddress);
 
         void SetVertexArray(int Stride, long VboKey, GalVertexAttrib[] Attribs);
 
diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs
index 9adaceaf50..56235a0709 100644
--- a/Ryujinx.Graphics/Gal/IGalShader.cs
+++ b/Ryujinx.Graphics/Gal/IGalShader.cs
@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 
 namespace Ryujinx.Graphics.Gal
@@ -10,7 +11,7 @@ namespace Ryujinx.Graphics.Gal
 
         IEnumerable<ShaderDeclInfo> GetTextureUsage(long Key);
 
-        void SetConstBuffer(long Key, int Cbuf, byte[] Data);
+        void SetConstBuffer(long Key, int Cbuf, int DataSize, IntPtr HostAddress);
 
         void EnsureTextureBinding(string UniformName, int Value);
 
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
index 0dc56966b3..f2e5859e5e 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
@@ -211,28 +211,28 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             GL.PrimitiveRestartIndex(Index);
         }
 
-        public void CreateVbo(long Key, byte[] Buffer)
+        public void CreateVbo(long Key, int DataSize, IntPtr HostAddress)
         {
             int Handle = GL.GenBuffer();
 
-            VboCache.AddOrUpdate(Key, Handle, (uint)Buffer.Length);
+            VboCache.AddOrUpdate(Key, Handle, (uint)DataSize);
 
-            IntPtr Length = new IntPtr(Buffer.Length);
+            IntPtr Length = new IntPtr(DataSize);
 
             GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
-            GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
+            GL.BufferData(BufferTarget.ArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw);
         }
 
-        public void CreateIbo(long Key, byte[] Buffer)
+        public void CreateIbo(long Key, int DataSize, IntPtr HostAddress)
         {
             int Handle = GL.GenBuffer();
 
-            IboCache.AddOrUpdate(Key, Handle, (uint)Buffer.Length);
+            IboCache.AddOrUpdate(Key, Handle, (uint)DataSize);
 
-            IntPtr Length = new IntPtr(Buffer.Length);
+            IntPtr Length = new IntPtr(DataSize);
 
             GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
-            GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
+            GL.BufferData(BufferTarget.ElementArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw);
         }
 
         public void SetVertexArray(int Stride, long VboKey, GalVertexAttrib[] Attribs)
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
index fe98aa0911..37213d8ed1 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
@@ -5,6 +5,8 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 
+using Buffer = System.Buffer;
+
 namespace Ryujinx.Graphics.Gal.OpenGL
 {
     public class OGLShader : IGalShader
@@ -151,7 +153,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             return Enumerable.Empty<ShaderDeclInfo>();
         }
 
-        public void SetConstBuffer(long Key, int Cbuf, byte[] Data)
+        public void SetConstBuffer(long Key, int Cbuf, int DataSize, IntPtr HostAddress)
         {
             if (Stages.TryGetValue(Key, out ShaderStage Stage))
             {
@@ -159,13 +161,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 {
                     OGLStreamBuffer Buffer = GetConstBuffer(Stage.Type, Cbuf);
 
-                    int Size = Math.Min(Data.Length, Buffer.Size);
+                    int Size = Math.Min(DataSize, Buffer.Size);
 
-                    byte[] Destiny = Buffer.Map(Size);
-
-                    Array.Copy(Data, Destiny, Size);
-
-                    Buffer.Unmap(Size);
+                    Buffer.SetData(Size, HostAddress);
                 }
             }
         }
@@ -278,7 +276,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         {
             int FreeBinding = 0;
 
-            int BindUniformBlocksIfNotNull(ShaderStage Stage)
+            void BindUniformBlocksIfNotNull(ShaderStage Stage)
             {
                 if (Stage != null)
                 {
@@ -297,8 +295,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                         FreeBinding++;
                     }
                 }
-
-                return FreeBinding;
             }
 
             BindUniformBlocksIfNotNull(Current.Vertex);
@@ -312,7 +308,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         {
             int FreeBinding = 0;
 
-            int BindUniformBuffersIfNotNull(ShaderStage Stage)
+            void BindUniformBuffersIfNotNull(ShaderStage Stage)
             {
                 if (Stage != null)
                 {
@@ -325,8 +321,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                         FreeBinding++;
                     }
                 }
-
-                return FreeBinding;
             }
 
             BindUniformBuffersIfNotNull(Current.Vertex);
@@ -347,7 +341,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 //Allocate a maximum of 64 KiB
                 int Size = Math.Min(GL.GetInteger(GetPName.MaxUniformBlockSize), 64 * 1024);
 
-                Buffer = OGLStreamBuffer.Create(BufferTarget.UniformBuffer, Size);
+                Buffer = new OGLStreamBuffer(BufferTarget.UniformBuffer, Size);
 
                 ConstBuffers[StageIndex][Cbuf] = Buffer;
             }
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs
index 329c5b5df7..0d5dee93f5 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs
@@ -1,9 +1,9 @@
-using System;
 using OpenTK.Graphics.OpenGL;
+using System;
 
 namespace Ryujinx.Graphics.Gal.OpenGL
 {
-    abstract class OGLStreamBuffer : IDisposable
+    class OGLStreamBuffer : IDisposable
     {
         public int Handle { get; protected set; }
 
@@ -11,53 +11,25 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
         protected BufferTarget Target { get; private set; }
 
-        private bool Mapped = false;
-
-        public OGLStreamBuffer(BufferTarget Target, int MaxSize)
+        public OGLStreamBuffer(BufferTarget Target, int Size)
         {
-            Handle = 0;
-            Mapped = false;
-
             this.Target = Target;
-            this.Size = MaxSize;
+            this.Size   = Size;
+
+            Handle = GL.GenBuffer();
+
+            GL.BindBuffer(Target, Handle);
+
+            GL.BufferData(Target, Size, IntPtr.Zero, BufferUsageHint.StreamDraw);
         }
 
-        public static OGLStreamBuffer Create(BufferTarget Target, int MaxSize)
+        public void SetData(int Size, IntPtr HostAddress)
         {
-            //TODO: Query here for ARB_buffer_storage and use when available
-            return new SubDataBuffer(Target, MaxSize);
+            GL.BindBuffer(Target, Handle);
+
+            GL.BufferSubData(Target, IntPtr.Zero, Size, HostAddress);
         }
 
-        public byte[] Map(int Size)
-        {
-            if (Handle == 0 || Mapped || Size > this.Size)
-            {
-                throw new InvalidOperationException();
-            }
-
-            byte[] Memory = InternMap(Size);
-
-            Mapped = true;
-
-            return Memory;
-        }
-
-        public void Unmap(int UsedSize)
-        {
-            if (Handle == 0 || !Mapped)
-            {
-                throw new InvalidOperationException();
-            }
-
-            InternUnmap(UsedSize);
-
-            Mapped = false;
-        }
-
-        protected abstract byte[] InternMap(int Size);
-
-        protected abstract void InternUnmap(int UsedSize);
-
         public void Dispose()
         {
             Dispose(true);
@@ -73,41 +45,4 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             }
         }
     }
-
-    class SubDataBuffer : OGLStreamBuffer
-    {
-        private byte[] Memory;
-
-        public SubDataBuffer(BufferTarget Target, int MaxSize)
-            : base(Target, MaxSize)
-        {
-            Memory = new byte[MaxSize];
-
-            GL.GenBuffers(1, out int Handle);
-
-            GL.BindBuffer(Target, Handle);
-
-            GL.BufferData(Target, Size, IntPtr.Zero, BufferUsageHint.StreamDraw);
-
-            this.Handle = Handle;
-        }
-
-        protected override byte[] InternMap(int Size)
-        {
-            return Memory;
-        }
-
-        protected override void InternUnmap(int UsedSize)
-        {
-            GL.BindBuffer(Target, Handle);
-            
-            unsafe
-            {
-                fixed (byte* MemoryPtr = Memory)
-                {
-                    GL.BufferSubData(Target, IntPtr.Zero, UsedSize, (IntPtr)MemoryPtr);
-                }
-            }
-        }
-    }
 }
diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
index dce25a5e94..c3e7a77fcb 100644
--- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
+++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
@@ -560,9 +560,9 @@ namespace Ryujinx.HLE.Gpu.Engines
 
                     if (Cb.Enabled)
                     {
-                        byte[] Data = Vmm.ReadBytes(Cb.Position, (uint)Cb.Size);
+                        IntPtr DataAddress = Vmm.GetHostAddress(Cb.Position, Cb.Size);
 
-                        Gpu.Renderer.Shader.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Data);
+                        Gpu.Renderer.Shader.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Cb.Size, DataAddress);
                     }
                 }
             }
@@ -595,9 +595,9 @@ namespace Ryujinx.HLE.Gpu.Engines
 
                 if (!IboCached || Vmm.IsRegionModified(IboKey, (uint)IbSize, NvGpuBufferType.Index))
                 {
-                    byte[] Data = Vmm.ReadBytes(IndexPosition, (uint)IbSize);
+                    IntPtr DataAddress = Vmm.GetHostAddress(IndexPosition, IbSize);
 
-                    Gpu.Renderer.Rasterizer.CreateIbo(IboKey, Data);
+                    Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, DataAddress);
                 }
 
                 Gpu.Renderer.Rasterizer.SetIndexArray(IbSize, IndexFormat);
@@ -659,9 +659,9 @@ namespace Ryujinx.HLE.Gpu.Engines
 
                 if (!VboCached || Vmm.IsRegionModified(VboKey, VbSize, NvGpuBufferType.Vertex))
                 {
-                    byte[] Data = Vmm.ReadBytes(VertexPosition, VbSize);
+                    IntPtr DataAddress = Vmm.GetHostAddress(VertexPosition, VbSize);
 
-                    Gpu.Renderer.Rasterizer.CreateVbo(VboKey, Data);
+                    Gpu.Renderer.Rasterizer.CreateVbo(VboKey, (int)VbSize, DataAddress);
                 }
 
                 Gpu.Renderer.Rasterizer.SetVertexArray(Stride, VboKey, Attribs[Index].ToArray());
diff --git a/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs b/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs
index 0c81dd1508..7b23e49fac 100644
--- a/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs
+++ b/Ryujinx.HLE/Gpu/Memory/NvGpuVmm.cs
@@ -1,5 +1,6 @@
 using ChocolArm64.Memory;
 using Ryujinx.Graphics.Gal;
+using System;
 using System.Collections.Concurrent;
 
 namespace Ryujinx.HLE.Gpu.Memory
@@ -279,6 +280,11 @@ namespace Ryujinx.HLE.Gpu.Memory
             return Cache.IsRegionModified(Memory, BufferType, PA, Size);
         }
 
+        public IntPtr GetHostAddress(long Position, long Size)
+        {
+            return Memory.GetHostAddress(GetPhysicalAddress(Position), Size);
+        }
+
         public byte ReadByte(long Position)
         {
             Position = GetPhysicalAddress(Position);