diff options
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs')
| -rw-r--r-- | src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 174 |
1 files changed, 168 insertions, 6 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index d293060b..e060e0b4 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -10,6 +10,8 @@ using System.Threading; namespace Ryujinx.Graphics.Gpu.Memory { + delegate void BufferFlushAction(ulong address, ulong size, ulong syncNumber); + /// <summary> /// Buffer, used to store vertex and index data, uniform and storage buffers, and others. /// </summary> @@ -23,7 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// <summary> /// Host buffer handle. /// </summary> - public BufferHandle Handle { get; } + public BufferHandle Handle { get; private set; } /// <summary> /// Start address of the buffer in guest memory. @@ -60,6 +62,17 @@ namespace Ryujinx.Graphics.Gpu.Memory /// </remarks> private BufferModifiedRangeList _modifiedRanges = null; + /// <summary> + /// A structure that is used to flush buffer data back to a host mapped buffer for cached readback. + /// Only used if the buffer data is explicitly owned by device local memory. + /// </summary> + private BufferPreFlush _preFlush = null; + + /// <summary> + /// Usage tracking state that determines what type of backing the buffer should use. + /// </summary> + public BufferBackingState BackingState; + private readonly MultiRegionHandle _memoryTrackingGranular; private readonly RegionHandle _memoryTracking; @@ -87,6 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// <param name="physicalMemory">Physical memory where the buffer is mapped</param> /// <param name="address">Start address of the buffer</param> /// <param name="size">Size of the buffer in bytes</param> + /// <param name="stage">The type of usage that created the buffer</param> /// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param> /// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param> public Buffer( @@ -94,6 +108,7 @@ namespace Ryujinx.Graphics.Gpu.Memory PhysicalMemory physicalMemory, ulong address, ulong size, + BufferStage stage, bool sparseCompatible, IEnumerable<Buffer> baseBuffers = null) { @@ -103,9 +118,11 @@ namespace Ryujinx.Graphics.Gpu.Memory Size = size; SparseCompatible = sparseCompatible; - BufferAccess access = sparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default; + BackingState = new BufferBackingState(_context, this, stage, baseBuffers); + + BufferAccess access = BackingState.SwitchAccess(this); - Handle = context.Renderer.CreateBuffer((int)size, access, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null); + Handle = context.Renderer.CreateBuffer((int)size, access); _useGranular = size > GranularBufferThreshold; @@ -162,6 +179,29 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Recreates the backing buffer based on the desired access type + /// reported by the backing state struct. + /// </summary> + private void ChangeBacking() + { + BufferAccess access = BackingState.SwitchAccess(this); + + BufferHandle newHandle = _context.Renderer.CreateBuffer((int)Size, access); + + _context.Renderer.Pipeline.CopyBuffer(Handle, newHandle, 0, 0, (int)Size); + + _modifiedRanges?.SelfMigration(); + + // If swtiching from device local to host mapped, pre-flushing data no longer makes sense. + // This is set to null and disposed when the migration fully completes. + _preFlush = null; + + Handle = newHandle; + + _physicalMemory.BufferCache.BufferBackingChanged(this); + } + + /// <summary> /// Gets a sub-range from the buffer, from a start address til a page boundary after the given size. /// </summary> /// <remarks> @@ -246,6 +286,7 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { + BackingState.RecordSet(); _context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size)); CopyToDependantVirtualBuffers(); } @@ -284,14 +325,34 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Checks if a backing change is deemed necessary from the given usage. + /// If it is, queues a backing change to happen on the next sync action. + /// </summary> + /// <param name="stage">Buffer stage that can change backing type</param> + private void TryQueueBackingChange(BufferStage stage) + { + if (BackingState.ShouldChangeBacking(stage)) + { + if (!_syncActionRegistered) + { + _context.RegisterSyncAction(this); + _syncActionRegistered = true; + } + } + } + + /// <summary> /// Signal that the given region of the buffer has been modified. /// </summary> /// <param name="address">The start address of the modified region</param> /// <param name="size">The size of the modified region</param> - public void SignalModified(ulong address, ulong size) + /// <param name="stage">Buffer stage that triggered the modification</param> + public void SignalModified(ulong address, ulong size, BufferStage stage) { EnsureRangeList(); + TryQueueBackingChange(stage); + _modifiedRanges.SignalModified(address, size); if (!_syncActionRegistered) @@ -312,6 +373,37 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Action to be performed immediately before sync is created. + /// This will copy any buffer ranges designated for pre-flushing. + /// </summary> + /// <param name="syncpoint">True if the action is a guest syncpoint</param> + public void SyncPreAction(bool syncpoint) + { + if (_referenceCount == 0) + { + return; + } + + if (BackingState.ShouldChangeBacking()) + { + ChangeBacking(); + } + + if (BackingState.IsDeviceLocal) + { + _preFlush ??= new BufferPreFlush(_context, this, FlushImpl); + + if (_preFlush.ShouldCopy) + { + _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, (address, size) => + { + _preFlush.CopyModified(address, size); + }); + } + } + } + + /// <summary> /// Action to be performed when a syncpoint is reached after modification. /// This will register read/write tracking to flush the buffer from GPU when its memory is used. /// </summary> @@ -466,6 +558,8 @@ namespace Ryujinx.Graphics.Gpu.Memory /// <param name="mSize">Size of the modified region</param> private void LoadRegion(ulong mAddress, ulong mSize) { + BackingState.RecordSet(); + int offset = (int)(mAddress - Address); _context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize)); @@ -539,19 +633,85 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Flushes a range of the buffer. /// This writes the range data back into guest memory. /// </summary> + /// <param name="handle">Buffer handle to flush data from</param> /// <param name="address">Start address of the range</param> /// <param name="size">Size in bytes of the range</param> - public void Flush(ulong address, ulong size) + private void FlushImpl(BufferHandle handle, ulong address, ulong size) { int offset = (int)(address - Address); - using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size); + using PinnedSpan<byte> data = _context.Renderer.GetBufferData(handle, offset, (int)size); // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers. _physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size)); } /// <summary> + /// Flushes a range of the buffer. + /// This writes the range data back into guest memory. + /// </summary> + /// <param name="address">Start address of the range</param> + /// <param name="size">Size in bytes of the range</param> + private void FlushImpl(ulong address, ulong size) + { + FlushImpl(Handle, address, size); + } + + /// <summary> + /// Flushes a range of the buffer from the most optimal source. + /// This writes the range data back into guest memory. + /// </summary> + /// <param name="address">Start address of the range</param> + /// <param name="size">Size in bytes of the range</param> + /// <param name="syncNumber">Sync number waited for before flushing the data</param> + public void Flush(ulong address, ulong size, ulong syncNumber) + { + BackingState.RecordFlush(); + + BufferPreFlush preFlush = _preFlush; + + if (preFlush != null) + { + preFlush.FlushWithAction(address, size, syncNumber); + } + else + { + FlushImpl(address, size); + } + } + /// <summary> + /// Gets an action that disposes the backing buffer using its current handle. + /// Useful for deleting an old copy of the buffer after the handle changes. + /// </summary> + /// <returns>An action that flushes data from the specified range, using the buffer handle at the time the method is generated</returns> + public Action GetSnapshotDisposeAction() + { + BufferHandle handle = Handle; + BufferPreFlush preFlush = _preFlush; + + return () => + { + _context.Renderer.DeleteBuffer(handle); + preFlush?.Dispose(); + }; + } + + /// <summary> + /// Gets an action that flushes a range of the buffer using its current handle. + /// Useful for flushing data from old copies of the buffer after the handle changes. + /// </summary> + /// <returns>An action that flushes data from the specified range, using the buffer handle at the time the method is generated</returns> + public BufferFlushAction GetSnapshotFlushAction() + { + BufferHandle handle = Handle; + + return (ulong address, ulong size, ulong _) => + { + FlushImpl(handle, address, size); + }; + } + + /// <summary> /// Align a given address and size region to page boundaries. /// </summary> /// <param name="address">The start address of the region</param> @@ -857,6 +1017,8 @@ namespace Ryujinx.Graphics.Gpu.Memory _modifiedRanges?.Clear(); _context.Renderer.DeleteBuffer(Handle); + _preFlush?.Dispose(); + _preFlush = null; UnmappedSequence++; } |
