From 65778a6b78ab8bde4090478482227e40c551db4d Mon Sep 17 00:00:00 2001 From: riperiperi Date: Thu, 24 Nov 2022 14:50:15 +0000 Subject: [PATCH] GPU: Don't trigger uploads for redundant buffer updates (#3828) * Initial implementation * Actually do The Thing * Add remark about performance to IVirtualMemoryManager --- Ryujinx.Cpu/Jit/MemoryManager.cs | 31 +++++++++++++++++++ Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs | 28 +++++++++++++++++ .../Engine/Threed/ConstantBufferUpdater.cs | 24 +++++++++----- Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs | 13 ++++++++ .../MockVirtualMemoryManager.cs | 5 +++ Ryujinx.Memory/AddressSpaceManager.cs | 8 +++++ Ryujinx.Memory/IVirtualMemoryManager.cs | 11 +++++++ 7 files changed, 113 insertions(+), 7 deletions(-) diff --git a/Ryujinx.Cpu/Jit/MemoryManager.cs b/Ryujinx.Cpu/Jit/MemoryManager.cs index 86c69431d..21c50d51f 100644 --- a/Ryujinx.Cpu/Jit/MemoryManager.cs +++ b/Ryujinx.Cpu/Jit/MemoryManager.cs @@ -180,6 +180,37 @@ namespace Ryujinx.Cpu.Jit WriteImpl(va, data); } + /// + public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan 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; + } + } + /// /// Writes data to CPU mapped memory. /// diff --git a/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs index 8994e9c0e..c4e59db9f 100644 --- a/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs +++ b/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs @@ -307,6 +307,34 @@ namespace Ryujinx.Cpu.Jit } } + /// + public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + try + { + SignalMemoryTracking(va, (ulong)data.Length, false); + + Span 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; + } + } + /// public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs index f4006ba99..5c9366160 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs @@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// class ConstantBufferUpdater { + private const int UniformDataCacheSize = 512; + private readonly GpuChannel _channel; private readonly DeviceStateWithShadow _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]; /// /// 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 data = MemoryMarshal.Cast(_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(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(data); - _channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData); + data.CopyTo(_ubData.AsSpan(_ubIndex)); + _ubIndex += data.Length; _ubFollowUpAddress = address + size; _ubByteCount += size; diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index 155cba0f5..051838f1f 100644 --- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -242,6 +242,19 @@ namespace Ryujinx.Graphics.Gpu.Memory WriteImpl(range, data, _cpuMemory.WriteUntracked); } + /// + /// 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. + /// + /// The memory manager can return that memory has changed when it hasn't to avoid expensive data copies. + /// Address to write into + /// Data to be written + /// True if the data was changed, false otherwise + public bool WriteWithRedundancyCheck(ulong address, ReadOnlySpan data) + { + return _cpuMemory.WriteWithRedundancyCheck(address, data); + } + private delegate void WriteCallback(ulong address, ReadOnlySpan data); /// diff --git a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs index 29922f898..6c4422829 100644 --- a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs +++ b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs @@ -44,6 +44,11 @@ namespace Ryujinx.Memory.Tests throw new NotImplementedException(); } + public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + throw new NotImplementedException(); + } + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { throw new NotImplementedException(); diff --git a/Ryujinx.Memory/AddressSpaceManager.cs b/Ryujinx.Memory/AddressSpaceManager.cs index 45f3225e1..ffe880bf8 100644 --- a/Ryujinx.Memory/AddressSpaceManager.cs +++ b/Ryujinx.Memory/AddressSpaceManager.cs @@ -136,6 +136,14 @@ namespace Ryujinx.Memory } } + /// + public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data) + { + Write(va, data); + + return true; + } + /// public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) { diff --git a/Ryujinx.Memory/IVirtualMemoryManager.cs b/Ryujinx.Memory/IVirtualMemoryManager.cs index f97cb0b57..c8a74f665 100644 --- a/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -58,6 +58,17 @@ namespace Ryujinx.Memory /// Throw for unhandled invalid or unmapped memory accesses void Write(ulong va, ReadOnlySpan data); + /// + /// 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. + /// + /// The memory manager can return that memory has changed when it hasn't to avoid expensive data copies. + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + /// True if the data was changed, false otherwise + bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data); + void Fill(ulong va, ulong size, byte value) { const int MaxChunkSize = 1 << 24;