瀏覽代碼

GPU: Don't trigger uploads for redundant buffer updates (#3828)

* Initial implementation

* Actually do The Thing

* Add remark about performance to IVirtualMemoryManager
riperiperi 3 年之前
父節點
當前提交
65778a6b78

+ 31 - 0
Ryujinx.Cpu/Jit/MemoryManager.cs

@@ -180,6 +180,37 @@ namespace Ryujinx.Cpu.Jit
             WriteImpl(va, data);
         }
 
+        /// <inheritdoc/>
+        public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
+        {
+            if (data.Length == 0)
+            {
+                return false;
+            }
+
+            SignalMemoryTracking(va, (ulong)data.Length, false);
+
+            if (IsContiguousAndMapped(va, data.Length))
+            {
+                var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
+
+                bool changed = !data.SequenceEqual(target);
+
+                if (changed)
+                {
+                    data.CopyTo(target);
+                }
+
+                return changed;
+            }
+            else
+            {
+                WriteImpl(va, data);
+
+                return true;
+            }
+        }
+
         /// <summary>
         /// Writes data to CPU mapped memory.
         /// </summary>

+ 28 - 0
Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs

@@ -307,6 +307,34 @@ namespace Ryujinx.Cpu.Jit
             }
         }
 
+        /// <inheritdoc/>
+        public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
+        {
+            try
+            {
+                SignalMemoryTracking(va, (ulong)data.Length, false);
+
+                Span<byte> target = _addressSpaceMirror.GetSpan(va, data.Length);
+                bool changed = !data.SequenceEqual(target);
+
+                if (changed)
+                {
+                    data.CopyTo(target);
+                }
+
+                return changed;
+            }
+            catch (InvalidMemoryRegionException)
+            {
+                if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
+                {
+                    throw;
+                }
+
+                return true;
+            }
+        }
+
         /// <inheritdoc/>
         public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
         {

+ 17 - 7
Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs

@@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
     /// </summary>
     class ConstantBufferUpdater
     {
+        private const int UniformDataCacheSize = 512;
+
         private readonly GpuChannel _channel;
         private readonly DeviceStateWithShadow<ThreedClassState> _state;
 
@@ -16,6 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
         private ulong _ubBeginCpuAddress = 0;
         private ulong _ubFollowUpAddress = 0;
         private ulong _ubByteCount = 0;
+        private int _ubIndex = 0;
+        private int[] _ubData = new int[UniformDataCacheSize];
 
         /// <summary>
         /// Creates a new instance of the constant buffer updater.
@@ -108,9 +112,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             if (_ubFollowUpAddress != 0)
             {
                 var memoryManager = _channel.MemoryManager;
-                memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
+
+                Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
+
+                if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
+                {
+                    memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
+                }
 
                 _ubFollowUpAddress = 0;
+                _ubIndex = 0;
             }
         }
 
@@ -124,7 +135,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
 
             ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset;
 
-            if (_ubFollowUpAddress != address)
+            if (_ubFollowUpAddress != address || _ubIndex == _ubData.Length)
             {
                 FlushUboDirty();
 
@@ -132,8 +143,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 _ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
             }
 
-            var byteData = MemoryMarshal.Cast<int, byte>(MemoryMarshal.CreateSpan(ref argument, 1));
-            _channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData);
+            _ubData[_ubIndex++] = argument;
 
             _ubFollowUpAddress = address + 4;
             _ubByteCount += 4;
@@ -153,7 +163,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
 
             ulong size = (ulong)data.Length * 4;
 
-            if (_ubFollowUpAddress != address)
+            if (_ubFollowUpAddress != address || _ubIndex + data.Length > _ubData.Length)
             {
                 FlushUboDirty();
 
@@ -161,8 +171,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 _ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
             }
 
-            var byteData = MemoryMarshal.Cast<int, byte>(data);
-            _channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData);
+            data.CopyTo(_ubData.AsSpan(_ubIndex));
+            _ubIndex += data.Length;
 
             _ubFollowUpAddress = address + size;
             _ubByteCount += size;

+ 13 - 0
Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs

@@ -242,6 +242,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
             WriteImpl(range, data, _cpuMemory.WriteUntracked);
         }
 
+        /// <summary>
+        /// Writes data to the application process, returning false if the data was not changed.
+        /// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
+        /// </summary>
+        /// <remarks>The memory manager can return that memory has changed when it hasn't to avoid expensive data copies.</remarks>
+        /// <param name="address">Address to write into</param>
+        /// <param name="data">Data to be written</param>
+        /// <returns>True if the data was changed, false otherwise</returns>
+        public bool WriteWithRedundancyCheck(ulong address, ReadOnlySpan<byte> data)
+        {
+            return _cpuMemory.WriteWithRedundancyCheck(address, data);
+        }
+
         private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
 
         /// <summary>

+ 5 - 0
Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs

@@ -44,6 +44,11 @@ namespace Ryujinx.Memory.Tests
             throw new NotImplementedException();
         }
 
+        public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
+        {
+            throw new NotImplementedException();
+        }
+
         public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
         {
             throw new NotImplementedException();

+ 8 - 0
Ryujinx.Memory/AddressSpaceManager.cs

@@ -136,6 +136,14 @@ namespace Ryujinx.Memory
             }
         }
 
+        /// <inheritdoc/>
+        public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
+        {
+            Write(va, data);
+
+            return true;
+        }
+
         /// <inheritdoc/>
         public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
         {

+ 11 - 0
Ryujinx.Memory/IVirtualMemoryManager.cs

@@ -58,6 +58,17 @@ namespace Ryujinx.Memory
         /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
         void Write(ulong va, ReadOnlySpan<byte> data);
 
+        /// <summary>
+        /// Writes data to the application process, returning false if the data was not changed.
+        /// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
+        /// </summary>
+        /// <remarks>The memory manager can return that memory has changed when it hasn't to avoid expensive data copies.</remarks>
+        /// <param name="va">Virtual address to write the data into</param>
+        /// <param name="data">Data to be written</param>
+        /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
+        /// <returns>True if the data was changed, false otherwise</returns>
+        bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data);
+
         void Fill(ulong va, ulong size, byte value)
         {
             const int MaxChunkSize = 1 << 24;