From 1f5d8818609a82df893167a8ec5bd6ccda406c61 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Fri, 5 May 2023 14:47:15 +0100 Subject: [PATCH] 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 * Fix inheritance of _dirtyStart and _dirtyEnd --------- Co-authored-by: gdkchan --- src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 109 +++++++++++++++++++++- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index ef8c807465..8e16b3ae10 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -68,6 +68,9 @@ namespace Ryujinx.Graphics.Gpu.Memory private int _referenceCount = 1; + private ulong _dirtyStart = ulong.MaxValue; + private ulong _dirtyEnd = ulong.MaxValue; + /// /// Creates a new instance of the buffer. /// @@ -221,6 +224,26 @@ namespace Ryujinx.Graphics.Gpu.Memory } _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 } /// - /// Inherit modified ranges from another buffer. + /// Inherit modified and dirty ranges from another buffer. /// /// The buffer to inherit from public void InheritModifiedRanges(Buffer from) @@ -320,6 +343,11 @@ namespace Ryujinx.Graphics.Gpu.Memory _modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction); } + + if (from._dirtyStart != ulong.MaxValue) + { + ForceDirty(from._dirtyStart, from._dirtyEnd - from._dirtyStart); + } } /// @@ -338,6 +366,44 @@ namespace Ryujinx.Graphics.Gpu.Memory return false; } + /// + /// Clear the dirty range that overlaps with the given region. + /// + /// Start address of the modified region + /// Size of the modified region + 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. + } + } + } + /// /// Indicate that a region of the buffer was modified, and must be loaded from memory. /// @@ -357,6 +423,8 @@ namespace Ryujinx.Graphics.Gpu.Memory mSize = maxSize; } + ClearDirty(mAddress, mSize); + if (_modifiedRanges != null) { _modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate); @@ -380,14 +448,12 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// - /// 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. /// /// Start address of the modified region /// Size of the region to force dirty - public void ForceDirty(ulong mAddress, ulong mSize) + private void ForceTrackingDirty(ulong mAddress, ulong mSize) { - _modifiedRanges?.Clear(mAddress, mSize); - if (_useGranular) { _memoryTrackingGranular.ForceDirty(mAddress, mSize); @@ -399,6 +465,39 @@ namespace Ryujinx.Graphics.Gpu.Memory } } + /// + /// Force a region of the buffer to be dirty. Avoids reprotection and nullifies sequence number check. + /// + /// Start address of the modified region + /// Size of the region to force dirty + 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); + } + } + } + /// /// Performs copy of all the buffer data from one buffer to another. ///