diff --git a/Ryujinx.Cpu/Jit/MemoryManager.cs b/Ryujinx.Cpu/Jit/MemoryManager.cs
index 86c69431..21c50d51 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 8994e9c0..c4e59db9 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 f4006ba9..5c936616 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 155cba0f..051838f1 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 29922f89..6c442282 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 45f3225e..ffe880bf 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 f97cb0b5..c8a74f66 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;