GPU: Pre-emptively flush textures that are flushed often (to imported memory when available) (#4711)
* WIP texture pre-flush Improve performance of TextureView GetData to buffer Fix copy/sync ordering Fix minor bug Make this actually work WIP host mapping stuff * Fix usage flags * message * Cleanup 1 * Fix rebase * Fix * Improve pre-flush rules * Fix pre-flush * A lot of cleanup * Use the host memory bits * Select the correct memory type * Cleanup TextureGroupHandle * Missing comment * Remove debugging logs * Revert BufferHandle _value access modifier * One interrupt action at a time. * Support D32S8 to D24S8 conversion, safeguards * Interrupt cannot happen in sync handle's lock Waitable needs to be checked twice now, but this should stop it from deadlocking. * Remove unused using * Address some feedback * Address feedback * Address more feedback * Address more feedback * Improve sync rules Should allow for faster sync in some cases.
This commit is contained in:
parent
36f10df775
commit
e18d258fa0
40 changed files with 1328 additions and 79 deletions
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Cpu.Tracking;
|
||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -13,8 +14,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// Also tracks copy dependencies for the handle - references to other handles that must be kept
|
||||
/// in sync with this one before use.
|
||||
/// </summary>
|
||||
class TextureGroupHandle : IDisposable
|
||||
class TextureGroupHandle : ISyncActionHandler, IDisposable
|
||||
{
|
||||
private const int FlushBalanceIncrement = 6;
|
||||
private const int FlushBalanceWriteCost = 1;
|
||||
private const int FlushBalanceThreshold = 7;
|
||||
private const int FlushBalanceMax = 60;
|
||||
private const int FlushBalanceMin = -10;
|
||||
|
||||
private TextureGroup _group;
|
||||
private int _bindCount;
|
||||
private int _firstLevel;
|
||||
|
@ -26,6 +33,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// The sync number last registered.
|
||||
/// </summary>
|
||||
private ulong _registeredSync;
|
||||
private ulong _registeredBufferSync = ulong.MaxValue;
|
||||
private ulong _registeredBufferGuestSync = ulong.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// The sync number when the texture was last modified by GPU.
|
||||
|
@ -42,6 +51,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// </summary>
|
||||
private bool _syncActionRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Determines the balance of synced writes to flushes.
|
||||
/// Used to determine if the texture should always write data to a persistent buffer for flush.
|
||||
/// </summary>
|
||||
private int _flushBalance;
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset from the start of the storage of this handle.
|
||||
/// </summary>
|
||||
|
@ -132,6 +147,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
|
||||
Handles = handles;
|
||||
|
||||
if (group.Storage.Info.IsLinear)
|
||||
{
|
||||
// Linear textures are presumed to be used for readback initially.
|
||||
_flushBalance = FlushBalanceThreshold + FlushBalanceIncrement;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -159,6 +180,35 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the next sync will copy into the flush buffer.
|
||||
/// </summary>
|
||||
/// <returns>True if it will copy, false otherwise</returns>
|
||||
private bool NextSyncCopies()
|
||||
{
|
||||
return _flushBalance - FlushBalanceWriteCost > FlushBalanceThreshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alters the flush balance by the given value. Should increase significantly with each sync, decrease with each write.
|
||||
/// A flush balance higher than the threshold will cause a texture to repeatedly copy to a flush buffer on each use.
|
||||
/// </summary>
|
||||
/// <param name="modifier">Value to add to the existing flush balance</param>
|
||||
/// <returns>True if the new balance is over the threshold, false otherwise</returns>
|
||||
private bool ModifyFlushBalance(int modifier)
|
||||
{
|
||||
int result;
|
||||
int existingBalance;
|
||||
do
|
||||
{
|
||||
existingBalance = _flushBalance;
|
||||
result = Math.Max(FlushBalanceMin, Math.Min(FlushBalanceMax, existingBalance + modifier));
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _flushBalance, result, existingBalance) != existingBalance);
|
||||
|
||||
return result > FlushBalanceThreshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single texture view as an overlap if its range overlaps.
|
||||
/// </summary>
|
||||
|
@ -204,7 +254,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
if (!_syncActionRegistered)
|
||||
{
|
||||
_modifiedSync = context.SyncNumber;
|
||||
context.RegisterSyncAction(SyncAction, true);
|
||||
context.RegisterSyncAction(this, true);
|
||||
_syncActionRegistered = true;
|
||||
}
|
||||
|
||||
|
@ -241,6 +291,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
SignalModified(context);
|
||||
|
||||
if (!bound && _syncActionRegistered && NextSyncCopies())
|
||||
{
|
||||
// On unbind, textures that flush often should immediately create sync so their result can be obtained as soon as possible.
|
||||
|
||||
context.CreateHostSyncIfNeeded(HostSyncFlags.Force);
|
||||
}
|
||||
|
||||
// Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change.
|
||||
_bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1));
|
||||
}
|
||||
|
@ -266,25 +323,35 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
/// removing the modified flag if it was reached, or leaving it set if it has not yet been created.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context used to wait for sync</param>
|
||||
public void Sync(GpuContext context)
|
||||
/// <returns>True if the texture data can be read from the flush buffer</returns>
|
||||
public bool Sync(GpuContext context)
|
||||
{
|
||||
ulong registeredSync = _registeredSync;
|
||||
long diff = (long)(context.SyncNumber - registeredSync);
|
||||
// Currently assumes the calling thread is a guest thread.
|
||||
|
||||
bool inBuffer = _registeredBufferGuestSync != ulong.MaxValue;
|
||||
ulong sync = inBuffer ? _registeredBufferGuestSync : _registeredSync;
|
||||
|
||||
long diff = (long)(context.SyncNumber - sync);
|
||||
|
||||
ModifyFlushBalance(FlushBalanceIncrement);
|
||||
|
||||
if (diff > 0)
|
||||
{
|
||||
context.Renderer.WaitSync(registeredSync);
|
||||
context.Renderer.WaitSync(sync);
|
||||
|
||||
if ((long)(_modifiedSync - registeredSync) > 0)
|
||||
if ((long)(_modifiedSync - sync) > 0)
|
||||
{
|
||||
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
||||
return;
|
||||
return inBuffer;
|
||||
}
|
||||
|
||||
Modified = false;
|
||||
|
||||
return inBuffer;
|
||||
}
|
||||
|
||||
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -296,15 +363,41 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
Interlocked.Exchange(ref _actionRegistered, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action to perform before a sync number is registered after modification.
|
||||
/// This action will copy the texture data to the flush buffer if this texture
|
||||
/// flushes often enough, which is determined by the flush balance.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public void SyncPreAction(bool syncpoint)
|
||||
{
|
||||
if (syncpoint || NextSyncCopies())
|
||||
{
|
||||
if (ModifyFlushBalance(0) && _registeredBufferSync != _modifiedSync)
|
||||
{
|
||||
_group.FlushIntoBuffer(this);
|
||||
_registeredBufferSync = _modifiedSync;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action to perform when a sync number is registered after modification.
|
||||
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
|
||||
/// </summary>
|
||||
private void SyncAction()
|
||||
/// <inheritdoc/>
|
||||
public bool SyncAction(bool syncpoint)
|
||||
{
|
||||
// The storage will need to signal modified again to update the sync number in future.
|
||||
_group.Storage.SignalModifiedDirty();
|
||||
|
||||
bool lastInBuffer = _registeredBufferSync == _modifiedSync;
|
||||
|
||||
if (!lastInBuffer)
|
||||
{
|
||||
_registeredBufferSync = ulong.MaxValue;
|
||||
}
|
||||
|
||||
lock (Overlaps)
|
||||
{
|
||||
foreach (Texture texture in Overlaps)
|
||||
|
@ -314,6 +407,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
}
|
||||
|
||||
// Register region tracking for CPU? (again)
|
||||
|
||||
_registeredSync = _modifiedSync;
|
||||
_syncActionRegistered = false;
|
||||
|
||||
|
@ -321,6 +415,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||
{
|
||||
_group.RegisterAction(this);
|
||||
}
|
||||
|
||||
if (syncpoint)
|
||||
{
|
||||
_registeredBufferGuestSync = _registeredBufferSync;
|
||||
}
|
||||
|
||||
// If the last modification is in the buffer, keep this sync action alive until it sees a syncpoint.
|
||||
return syncpoint || !lastInBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue