aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs')
-rw-r--r--src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs544
1 files changed, 544 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
new file mode 100644
index 00000000..f267dfda
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -0,0 +1,544 @@
+using Ryujinx.Cpu.Tracking;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Memory.Range;
+using Ryujinx.Memory.Tracking;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ /// <summary>
+ /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
+ /// </summary>
+ class Buffer : IRange, IDisposable
+ {
+ private const ulong GranularBufferThreshold = 4096;
+
+ private readonly GpuContext _context;
+ private readonly PhysicalMemory _physicalMemory;
+
+ /// <summary>
+ /// Host buffer handle.
+ /// </summary>
+ public BufferHandle Handle { get; }
+
+ /// <summary>
+ /// Start address of the buffer in guest memory.
+ /// </summary>
+ public ulong Address { get; }
+
+ /// <summary>
+ /// Size of the buffer in bytes.
+ /// </summary>
+ public ulong Size { get; }
+
+ /// <summary>
+ /// End address of the buffer in guest memory.
+ /// </summary>
+ public ulong EndAddress => Address + Size;
+
+ /// <summary>
+ /// Increments when the buffer is (partially) unmapped or disposed.
+ /// </summary>
+ public int UnmappedSequence { get; private set; }
+
+ /// <summary>
+ /// Ranges of the buffer that have been modified on the GPU.
+ /// Ranges defined here cannot be updated from CPU until a CPU waiting sync point is reached.
+ /// Then, write tracking will signal, wait for GPU sync (generated at the syncpoint) and flush these regions.
+ /// </summary>
+ /// <remarks>
+ /// This is null until at least one modification occurs.
+ /// </remarks>
+ private BufferModifiedRangeList _modifiedRanges = null;
+
+ private readonly CpuMultiRegionHandle _memoryTrackingGranular;
+ private readonly CpuRegionHandle _memoryTracking;
+
+ private readonly RegionSignal _externalFlushDelegate;
+ private readonly Action<ulong, ulong> _loadDelegate;
+ private readonly Action<ulong, ulong> _modifiedDelegate;
+
+ private int _sequenceNumber;
+
+ private bool _useGranular;
+ private bool _syncActionRegistered;
+
+ private int _referenceCount = 1;
+
+ /// <summary>
+ /// Creates a new instance of the buffer.
+ /// </summary>
+ /// <param name="context">GPU context that the buffer belongs to</param>
+ /// <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="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param>
+ public Buffer(GpuContext context, PhysicalMemory physicalMemory, ulong address, ulong size, IEnumerable<Buffer> baseBuffers = null)
+ {
+ _context = context;
+ _physicalMemory = physicalMemory;
+ Address = address;
+ Size = size;
+
+ Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
+
+ _useGranular = size > GranularBufferThreshold;
+
+ IEnumerable<IRegionHandle> baseHandles = null;
+
+ if (baseBuffers != null)
+ {
+ baseHandles = baseBuffers.SelectMany(buffer =>
+ {
+ if (buffer._useGranular)
+ {
+ return buffer._memoryTrackingGranular.GetHandles();
+ }
+ else
+ {
+ return Enumerable.Repeat(buffer._memoryTracking.GetHandle(), 1);
+ }
+ });
+ }
+
+ if (_useGranular)
+ {
+ _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles);
+
+ _memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction);
+ }
+ else
+ {
+ _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer);
+
+ if (baseHandles != null)
+ {
+ _memoryTracking.Reprotect(false);
+
+ foreach (IRegionHandle handle in baseHandles)
+ {
+ if (handle.Dirty)
+ {
+ _memoryTracking.Reprotect(true);
+ }
+
+ handle.Dispose();
+ }
+ }
+
+ _memoryTracking.RegisterPreciseAction(PreciseAction);
+ }
+
+ _externalFlushDelegate = new RegionSignal(ExternalFlush);
+ _loadDelegate = new Action<ulong, ulong>(LoadRegion);
+ _modifiedDelegate = new Action<ulong, ulong>(RegionModified);
+ }
+
+ /// <summary>
+ /// Gets a sub-range from the buffer, from a start address till the end of the buffer.
+ /// </summary>
+ /// <remarks>
+ /// This can be used to bind and use sub-ranges of the buffer on the host API.
+ /// </remarks>
+ /// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
+ /// <returns>The buffer sub-range</returns>
+ public BufferRange GetRange(ulong address)
+ {
+ ulong offset = address - Address;
+
+ return new BufferRange(Handle, (int)offset, (int)(Size - offset));
+ }
+
+ /// <summary>
+ /// Gets a sub-range from the buffer.
+ /// </summary>
+ /// <remarks>
+ /// This can be used to bind and use sub-ranges of the buffer on the host API.
+ /// </remarks>
+ /// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
+ /// <param name="size">Size in bytes of the sub-range, must be less than or equal to the buffer size</param>
+ /// <returns>The buffer sub-range</returns>
+ public BufferRange GetRange(ulong address, ulong size)
+ {
+ int offset = (int)(address - Address);
+
+ return new BufferRange(Handle, offset, (int)size);
+ }
+
+ /// <summary>
+ /// Checks if a given range overlaps with the buffer.
+ /// </summary>
+ /// <param name="address">Start address of the range</param>
+ /// <param name="size">Size in bytes of the range</param>
+ /// <returns>True if the range overlaps, false otherwise</returns>
+ public bool OverlapsWith(ulong address, ulong size)
+ {
+ return Address < address + size && address < EndAddress;
+ }
+
+ /// <summary>
+ /// Checks if a given range is fully contained in the buffer.
+ /// </summary>
+ /// <param name="address">Start address of the range</param>
+ /// <param name="size">Size in bytes of the range</param>
+ /// <returns>True if the range is contained, false otherwise</returns>
+ public bool FullyContains(ulong address, ulong size)
+ {
+ return address >= Address && address + size <= EndAddress;
+ }
+
+ /// <summary>
+ /// Performs guest to host memory synchronization of the buffer data.
+ /// </summary>
+ /// <remarks>
+ /// This causes the buffer data to be overwritten if a write was detected from the CPU,
+ /// since the last call to this method.
+ /// </remarks>
+ /// <param name="address">Start address of the range to synchronize</param>
+ /// <param name="size">Size in bytes of the range to synchronize</param>
+ public void SynchronizeMemory(ulong address, ulong size)
+ {
+ if (_useGranular)
+ {
+ _memoryTrackingGranular.QueryModified(address, size, _modifiedDelegate, _context.SequenceNumber);
+ }
+ else
+ {
+ if (_context.SequenceNumber != _sequenceNumber && _memoryTracking.DirtyOrVolatile())
+ {
+ _memoryTracking.Reprotect();
+
+ if (_modifiedRanges != null)
+ {
+ _modifiedRanges.ExcludeModifiedRegions(Address, Size, _loadDelegate);
+ }
+ else
+ {
+ _context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
+ }
+
+ _sequenceNumber = _context.SequenceNumber;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Ensure that the modified range list exists.
+ /// </summary>
+ private void EnsureRangeList()
+ {
+ if (_modifiedRanges == null)
+ {
+ _modifiedRanges = new BufferModifiedRangeList(_context, this, Flush);
+ }
+ }
+
+ /// <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)
+ {
+ EnsureRangeList();
+
+ _modifiedRanges.SignalModified(address, size);
+
+ if (!_syncActionRegistered)
+ {
+ _context.RegisterSyncAction(SyncAction);
+ _syncActionRegistered = true;
+ }
+ }
+
+ /// <summary>
+ /// Indicate that mofifications in a given region of this buffer have been overwritten.
+ /// </summary>
+ /// <param name="address">The start address of the region</param>
+ /// <param name="size">The size of the region</param>
+ public void ClearModified(ulong address, ulong size)
+ {
+ _modifiedRanges?.Clear(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>
+ private void SyncAction()
+ {
+ _syncActionRegistered = false;
+
+ if (_useGranular)
+ {
+ _modifiedRanges?.GetRanges(Address, Size, (address, size) =>
+ {
+ _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
+ SynchronizeMemory(address, size);
+ });
+ }
+ else
+ {
+ _memoryTracking.RegisterAction(_externalFlushDelegate);
+ SynchronizeMemory(Address, Size);
+ }
+ }
+
+ /// <summary>
+ /// Inherit modified ranges from another buffer.
+ /// </summary>
+ /// <param name="from">The buffer to inherit from</param>
+ public void InheritModifiedRanges(Buffer from)
+ {
+ if (from._modifiedRanges != null && from._modifiedRanges.HasRanges)
+ {
+ if (from._syncActionRegistered && !_syncActionRegistered)
+ {
+ _context.RegisterSyncAction(SyncAction);
+ _syncActionRegistered = true;
+ }
+
+ Action<ulong, ulong> registerRangeAction = (ulong address, ulong size) =>
+ {
+ if (_useGranular)
+ {
+ _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
+ }
+ else
+ {
+ _memoryTracking.RegisterAction(_externalFlushDelegate);
+ }
+ };
+
+ EnsureRangeList();
+
+ _modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
+ }
+ }
+
+ /// <summary>
+ /// Determine if a given region of the buffer has been modified, and must be flushed.
+ /// </summary>
+ /// <param name="address">The start address of the region</param>
+ /// <param name="size">The size of the region</param>
+ /// <returns></returns>
+ public bool IsModified(ulong address, ulong size)
+ {
+ if (_modifiedRanges != null)
+ {
+ return _modifiedRanges.HasRange(address, size);
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Indicate that a region of the buffer was modified, and must be loaded from memory.
+ /// </summary>
+ /// <param name="mAddress">Start address of the modified region</param>
+ /// <param name="mSize">Size of the modified region</param>
+ private void RegionModified(ulong mAddress, ulong mSize)
+ {
+ if (mAddress < Address)
+ {
+ mAddress = Address;
+ }
+
+ ulong maxSize = Address + Size - mAddress;
+
+ if (mSize > maxSize)
+ {
+ mSize = maxSize;
+ }
+
+ if (_modifiedRanges != null)
+ {
+ _modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate);
+ }
+ else
+ {
+ LoadRegion(mAddress, mSize);
+ }
+ }
+
+ /// <summary>
+ /// Load a region of the buffer from memory.
+ /// </summary>
+ /// <param name="mAddress">Start address of the modified region</param>
+ /// <param name="mSize">Size of the modified region</param>
+ private void LoadRegion(ulong mAddress, ulong mSize)
+ {
+ int offset = (int)(mAddress - Address);
+
+ _context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
+ }
+
+ /// <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);
+
+ if (_useGranular)
+ {
+ _memoryTrackingGranular.ForceDirty(mAddress, mSize);
+ }
+ else
+ {
+ _memoryTracking.ForceDirty();
+ _sequenceNumber--;
+ }
+ }
+
+ /// <summary>
+ /// Performs copy of all the buffer data from one buffer to another.
+ /// </summary>
+ /// <param name="destination">The destination buffer to copy the data into</param>
+ /// <param name="dstOffset">The offset of the destination buffer to copy into</param>
+ public void CopyTo(Buffer destination, int dstOffset)
+ {
+ _context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)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>
+ public void Flush(ulong address, ulong size)
+ {
+ int offset = (int)(address - Address);
+
+ 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, data.Get());
+ }
+
+ /// <summary>
+ /// Align a given address and size region to page boundaries.
+ /// </summary>
+ /// <param name="address">The start address of the region</param>
+ /// <param name="size">The size of the region</param>
+ /// <returns>The page aligned address and size</returns>
+ private static (ulong address, ulong size) PageAlign(ulong address, ulong size)
+ {
+ ulong pageMask = MemoryManager.PageMask;
+ ulong rA = address & ~pageMask;
+ ulong rS = ((address + size + pageMask) & ~pageMask) - rA;
+ return (rA, rS);
+ }
+
+ /// <summary>
+ /// Flush modified ranges of the buffer from another thread.
+ /// This will flush all modifications made before the active SyncNumber was set, and may block to wait for GPU sync.
+ /// </summary>
+ /// <param name="address">Address of the memory action</param>
+ /// <param name="size">Size in bytes</param>
+ public void ExternalFlush(ulong address, ulong size)
+ {
+ _context.Renderer.BackgroundContextAction(() =>
+ {
+ var ranges = _modifiedRanges;
+
+ if (ranges != null)
+ {
+ (address, size) = PageAlign(address, size);
+ ranges.WaitForAndFlushRanges(address, size);
+ }
+ }, true);
+ }
+
+ /// <summary>
+ /// An action to be performed when a precise memory access occurs to this resource.
+ /// For buffers, this skips flush-on-write by punching holes directly into the modified range list.
+ /// </summary>
+ /// <param name="address">Address of the memory action</param>
+ /// <param name="size">Size in bytes</param>
+ /// <param name="write">True if the access was a write, false otherwise</param>
+ private bool PreciseAction(ulong address, ulong size, bool write)
+ {
+ if (!write)
+ {
+ // We only want to skip flush-on-write.
+ return false;
+ }
+
+ ulong maxAddress = Math.Max(address, Address);
+ ulong minEndAddress = Math.Min(address + size, Address + Size);
+
+ if (maxAddress >= minEndAddress)
+ {
+ // Access doesn't overlap.
+ return false;
+ }
+
+ ForceDirty(maxAddress, minEndAddress - maxAddress);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Called when part of the memory for this buffer has been unmapped.
+ /// Calls are from non-GPU threads.
+ /// </summary>
+ /// <param name="address">Start address of the unmapped region</param>
+ /// <param name="size">Size of the unmapped region</param>
+ public void Unmapped(ulong address, ulong size)
+ {
+ BufferModifiedRangeList modifiedRanges = _modifiedRanges;
+
+ modifiedRanges?.Clear(address, size);
+
+ UnmappedSequence++;
+ }
+
+ /// <summary>
+ /// Increments the buffer reference count.
+ /// </summary>
+ public void IncrementReferenceCount()
+ {
+ _referenceCount++;
+ }
+
+ /// <summary>
+ /// Decrements the buffer reference count.
+ /// </summary>
+ public void DecrementReferenceCount()
+ {
+ if (--_referenceCount == 0)
+ {
+ DisposeData();
+ }
+ }
+
+ /// <summary>
+ /// Disposes the host buffer's data, not its tracking handles.
+ /// </summary>
+ public void DisposeData()
+ {
+ _modifiedRanges?.Clear();
+
+ _context.Renderer.DeleteBuffer(Handle);
+
+ UnmappedSequence++;
+ }
+
+ /// <summary>
+ /// Disposes the host buffer.
+ /// </summary>
+ public void Dispose()
+ {
+ _memoryTrackingGranular?.Dispose();
+ _memoryTracking?.Dispose();
+
+ DecrementReferenceCount();
+ }
+ }
+} \ No newline at end of file