GPU: Allow granular buffer updates from the constant buffer updater (#4749)

* GPU: Allow granular buffer updates from the constant buffer updater

Sometimes, constant buffer updates can't be avoided, either due to a cb0 access that cannot be eliminated, or the game updating a buffer between draws to the detriment of everyone.

To avoid uploading the full 4096 bytes each time, this PR remembers the offset and size containing all constant buffer updates since the last sync. It will then upload that range after sync.

* Allow clearing the dirty range

* Always use precise

Might want to not do this if distance between the existing range and new one is too high.

* Use old force dirty mechanism when distance between regions is too great

* Update src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Fix inheritance of _dirtyStart and _dirtyEnd

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
This commit is contained in:
riperiperi 2023-05-05 14:47:15 +01:00 committed by GitHub
parent 1f664100bd
commit 1f5d881860
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -68,6 +68,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
private int _referenceCount = 1; private int _referenceCount = 1;
private ulong _dirtyStart = ulong.MaxValue;
private ulong _dirtyEnd = ulong.MaxValue;
/// <summary> /// <summary>
/// Creates a new instance of the buffer. /// Creates a new instance of the buffer.
/// </summary> /// </summary>
@ -221,6 +224,26 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
_sequenceNumber = _context.SequenceNumber; _sequenceNumber = _context.SequenceNumber;
_dirtyStart = ulong.MaxValue;
}
}
if (_dirtyStart != ulong.MaxValue)
{
ulong end = address + size;
if (end > _dirtyStart && address < _dirtyEnd)
{
if (_modifiedRanges != null)
{
_modifiedRanges.ExcludeModifiedRegions(_dirtyStart, _dirtyEnd - _dirtyStart, _loadDelegate);
}
else
{
LoadRegion(_dirtyStart, _dirtyEnd - _dirtyStart);
}
_dirtyStart = ulong.MaxValue;
} }
} }
} }
@ -291,7 +314,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
/// <summary> /// <summary>
/// Inherit modified ranges from another buffer. /// Inherit modified and dirty ranges from another buffer.
/// </summary> /// </summary>
/// <param name="from">The buffer to inherit from</param> /// <param name="from">The buffer to inherit from</param>
public void InheritModifiedRanges(Buffer from) public void InheritModifiedRanges(Buffer from)
@ -320,6 +343,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction); _modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
} }
if (from._dirtyStart != ulong.MaxValue)
{
ForceDirty(from._dirtyStart, from._dirtyEnd - from._dirtyStart);
}
} }
/// <summary> /// <summary>
@ -338,6 +366,44 @@ namespace Ryujinx.Graphics.Gpu.Memory
return false; return false;
} }
/// <summary>
/// Clear the dirty range that overlaps with the given region.
/// </summary>
/// <param name="address">Start address of the modified region</param>
/// <param name="size">Size of the modified region</param>
private void ClearDirty(ulong address, ulong size)
{
if (_dirtyStart != ulong.MaxValue)
{
ulong end = address + size;
if (end > _dirtyStart && address < _dirtyEnd)
{
if (address <= _dirtyStart)
{
// Cut off the start.
if (end < _dirtyEnd)
{
_dirtyStart = end;
}
else
{
_dirtyStart = ulong.MaxValue;
}
}
else if (end >= _dirtyEnd)
{
// Cut off the end.
_dirtyEnd = address;
}
// If fully contained, do nothing.
}
}
}
/// <summary> /// <summary>
/// Indicate that a region of the buffer was modified, and must be loaded from memory. /// Indicate that a region of the buffer was modified, and must be loaded from memory.
/// </summary> /// </summary>
@ -357,6 +423,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
mSize = maxSize; mSize = maxSize;
} }
ClearDirty(mAddress, mSize);
if (_modifiedRanges != null) if (_modifiedRanges != null)
{ {
_modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate); _modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate);
@ -380,14 +448,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
/// <summary> /// <summary>
/// Force a region of the buffer to be dirty. Avoids reprotection and nullifies sequence number check. /// Force a region of the buffer to be dirty within the memory tracking. Avoids reprotection and nullifies sequence number check.
/// </summary> /// </summary>
/// <param name="mAddress">Start address of the modified region</param> /// <param name="mAddress">Start address of the modified region</param>
/// <param name="mSize">Size of the region to force dirty</param> /// <param name="mSize">Size of the region to force dirty</param>
public void ForceDirty(ulong mAddress, ulong mSize) private void ForceTrackingDirty(ulong mAddress, ulong mSize)
{ {
_modifiedRanges?.Clear(mAddress, mSize);
if (_useGranular) if (_useGranular)
{ {
_memoryTrackingGranular.ForceDirty(mAddress, mSize); _memoryTrackingGranular.ForceDirty(mAddress, mSize);
@ -399,6 +465,39 @@ namespace Ryujinx.Graphics.Gpu.Memory
} }
} }
/// <summary>
/// Force a region of the buffer to be dirty. Avoids reprotection and nullifies sequence number check.
/// </summary>
/// <param name="mAddress">Start address of the modified region</param>
/// <param name="mSize">Size of the region to force dirty</param>
public void ForceDirty(ulong mAddress, ulong mSize)
{
_modifiedRanges?.Clear(mAddress, mSize);
ulong end = mAddress + mSize;
if (_dirtyStart == ulong.MaxValue)
{
_dirtyStart = mAddress;
_dirtyEnd = end;
}
else
{
// Is the new range more than a page away from the existing one?
if ((long)(mAddress - _dirtyEnd) >= (long)MemoryManager.PageSize ||
(long)(_dirtyStart - end) >= (long)MemoryManager.PageSize)
{
ForceTrackingDirty(mAddress, mSize);
}
else
{
_dirtyStart = Math.Min(_dirtyStart, mAddress);
_dirtyEnd = Math.Max(_dirtyEnd, end);
}
}
}
/// <summary> /// <summary>
/// Performs copy of all the buffer data from one buffer to another. /// Performs copy of all the buffer data from one buffer to another.
/// </summary> /// </summary>