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

* Initial implementation

* Actually do The Thing

* Add remark about performance to IVirtualMemoryManager
This commit is contained in:
riperiperi 2022-11-24 14:50:15 +00:00 committed by GitHub
parent f4e879a1e6
commit 65778a6b78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 113 additions and 7 deletions

View File

@ -180,6 +180,37 @@ namespace Ryujinx.Cpu.Jit
WriteImpl(va, data); 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> /// <summary>
/// Writes data to CPU mapped memory. /// Writes data to CPU mapped memory.
/// </summary> /// </summary>

View File

@ -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/> /// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{ {

View File

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

View File

@ -242,6 +242,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
WriteImpl(range, data, _cpuMemory.WriteUntracked); 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); private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
/// <summary> /// <summary>

View File

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

View File

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

View File

@ -58,6 +58,17 @@ namespace Ryujinx.Memory
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception> /// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
void Write(ulong va, ReadOnlySpan<byte> data); 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) void Fill(ulong va, ulong size, byte value)
{ {
const int MaxChunkSize = 1 << 24; const int MaxChunkSize = 1 << 24;