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.cs215
1 files changed, 214 insertions, 1 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
index 12461e96..e01e5142 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -5,6 +5,8 @@ using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
{
@@ -65,6 +67,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly Action<ulong, ulong> _loadDelegate;
private readonly Action<ulong, ulong> _modifiedDelegate;
+ private HashSet<MultiRangeBuffer> _virtualDependencies;
+ private readonly ReaderWriterLockSlim _virtualDependenciesLock;
+
private int _sequenceNumber;
private readonly bool _useGranular;
@@ -152,6 +157,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_externalFlushDelegate = new RegionSignal(ExternalFlush);
_loadDelegate = new Action<ulong, ulong>(LoadRegion);
_modifiedDelegate = new Action<ulong, ulong>(RegionModified);
+
+ _virtualDependenciesLock = new ReaderWriterLockSlim();
}
/// <summary>
@@ -220,6 +227,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// </remarks>
/// <param name="address">Start address of the range to synchronize</param>
/// <param name="size">Size in bytes of the range to synchronize</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SynchronizeMemory(ulong address, ulong size)
{
if (_useGranular)
@@ -239,6 +247,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
else
{
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
+ CopyToDependantVirtualBuffers();
}
_sequenceNumber = _context.SequenceNumber;
@@ -460,6 +469,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
int offset = (int)(mAddress - Address);
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
+
+ CopyToDependantVirtualBuffers(mAddress, mSize);
}
/// <summary>
@@ -520,6 +531,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="dstOffset">The offset of the destination buffer to copy into</param>
public void CopyTo(Buffer destination, int dstOffset)
{
+ CopyFromDependantVirtualBuffers();
_context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size);
}
@@ -536,7 +548,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
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());
+ _physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
}
/// <summary>
@@ -618,6 +630,207 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Adds a virtual buffer dependency, indicating that a virtual buffer depends on data from this buffer.
+ /// </summary>
+ /// <param name="virtualBuffer">Dependant virtual buffer</param>
+ public void AddVirtualDependency(MultiRangeBuffer virtualBuffer)
+ {
+ _virtualDependenciesLock.EnterWriteLock();
+
+ try
+ {
+ (_virtualDependencies ??= new()).Add(virtualBuffer);
+ }
+ finally
+ {
+ _virtualDependenciesLock.ExitWriteLock();
+ }
+ }
+
+ /// <summary>
+ /// Removes a virtual buffer dependency, indicating that a virtual buffer no longer depends on data from this buffer.
+ /// </summary>
+ /// <param name="virtualBuffer">Dependant virtual buffer</param>
+ public void RemoveVirtualDependency(MultiRangeBuffer virtualBuffer)
+ {
+ _virtualDependenciesLock.EnterWriteLock();
+
+ try
+ {
+ if (_virtualDependencies != null)
+ {
+ _virtualDependencies.Remove(virtualBuffer);
+
+ if (_virtualDependencies.Count == 0)
+ {
+ _virtualDependencies = null;
+ }
+ }
+ }
+ finally
+ {
+ _virtualDependenciesLock.ExitWriteLock();
+ }
+ }
+
+ /// <summary>
+ /// Copies the buffer data to all virtual buffers that depends on it.
+ /// </summary>
+ public void CopyToDependantVirtualBuffers()
+ {
+ CopyToDependantVirtualBuffers(Address, Size);
+ }
+
+ /// <summary>
+ /// Copies the buffer data inside the specifide range to all virtual buffers that depends on it.
+ /// </summary>
+ /// <param name="address">Address of the range</param>
+ /// <param name="size">Size of the range in bytes</param>
+ public void CopyToDependantVirtualBuffers(ulong address, ulong size)
+ {
+ if (_virtualDependencies != null)
+ {
+ foreach (var virtualBuffer in _virtualDependencies)
+ {
+ CopyToDependantVirtualBuffer(virtualBuffer, address, size);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies all modified ranges from all virtual buffers back into this buffer.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void CopyFromDependantVirtualBuffers()
+ {
+ if (_virtualDependencies != null)
+ {
+ CopyFromDependantVirtualBuffersImpl();
+ }
+ }
+
+ /// <summary>
+ /// Copies all modified ranges from all virtual buffers back into this buffer.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void CopyFromDependantVirtualBuffersImpl()
+ {
+ foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber))
+ {
+ virtualBuffer.ConsumeModifiedRegion(this, (mAddress, mSize) =>
+ {
+ // Get offset inside both this and the virtual buffer.
+ // Note that sometimes there is no right answer for the virtual offset,
+ // as the same physical range might be mapped multiple times inside a virtual buffer.
+ // We just assume it does not happen in practice as it can only be implemented correctly
+ // when the host has support for proper sparse mapping.
+
+ ulong mEndAddress = mAddress + mSize;
+ mAddress = Math.Max(mAddress, Address);
+ mSize = Math.Min(mEndAddress, EndAddress) - mAddress;
+
+ int physicalOffset = (int)(mAddress - Address);
+ int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize));
+
+ _context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)mSize);
+ });
+ }
+ }
+
+ /// <summary>
+ /// Copies all overlapping modified ranges from all virtual buffers back into this buffer, and returns an updated span with the data.
+ /// </summary>
+ /// <param name="dataSpan">Span where the unmodified data will be taken from for the output</param>
+ /// <param name="address">Address of the region to copy</param>
+ /// <param name="size">Size of the region to copy in bytes</param>
+ /// <returns>A span with <paramref name="dataSpan"/>, and the data for all modified ranges if any</returns>
+ private ReadOnlySpan<byte> CopyFromDependantVirtualBuffers(ReadOnlySpan<byte> dataSpan, ulong address, ulong size)
+ {
+ _virtualDependenciesLock.EnterReadLock();
+
+ try
+ {
+ if (_virtualDependencies != null)
+ {
+ byte[] storage = dataSpan.ToArray();
+
+ foreach (var virtualBuffer in _virtualDependencies.OrderBy(x => x.ModificationSequenceNumber))
+ {
+ virtualBuffer.ConsumeModifiedRegion(address, size, (mAddress, mSize) =>
+ {
+ // Get offset inside both this and the virtual buffer.
+ // Note that sometimes there is no right answer for the virtual offset,
+ // as the same physical range might be mapped multiple times inside a virtual buffer.
+ // We just assume it does not happen in practice as it can only be implemented correctly
+ // when the host has support for proper sparse mapping.
+
+ ulong mEndAddress = mAddress + mSize;
+ mAddress = Math.Max(mAddress, address);
+ mSize = Math.Min(mEndAddress, address + size) - mAddress;
+
+ int physicalOffset = (int)(mAddress - Address);
+ int virtualOffset = virtualBuffer.Range.FindOffset(new(mAddress, mSize));
+
+ _context.Renderer.Pipeline.CopyBuffer(virtualBuffer.Handle, Handle, virtualOffset, physicalOffset, (int)size);
+ virtualBuffer.GetData(storage.AsSpan().Slice((int)(mAddress - address), (int)mSize), virtualOffset, (int)mSize);
+ });
+ }
+
+ dataSpan = storage;
+ }
+ }
+ finally
+ {
+ _virtualDependenciesLock.ExitReadLock();
+ }
+
+ return dataSpan;
+ }
+
+ /// <summary>
+ /// Copies the buffer data to the specified virtual buffer.
+ /// </summary>
+ /// <param name="virtualBuffer">Virtual buffer to copy the data into</param>
+ public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer)
+ {
+ CopyToDependantVirtualBuffer(virtualBuffer, Address, Size);
+ }
+
+ /// <summary>
+ /// Copies the buffer data inside the given range to the specified virtual buffer.
+ /// </summary>
+ /// <param name="virtualBuffer">Virtual buffer to copy the data into</param>
+ /// <param name="address">Address of the range</param>
+ /// <param name="size">Size of the range in bytes</param>
+ public void CopyToDependantVirtualBuffer(MultiRangeBuffer virtualBuffer, ulong address, ulong size)
+ {
+ // Broadcast data to all ranges of the virtual buffer that are contained inside this buffer.
+
+ ulong lastOffset = 0;
+
+ while (virtualBuffer.TryGetPhysicalOffset(this, lastOffset, out ulong srcOffset, out ulong dstOffset, out ulong copySize))
+ {
+ ulong innerOffset = address - Address;
+ ulong innerEndOffset = (address + size) - Address;
+
+ lastOffset = dstOffset + copySize;
+
+ // Clamp range to the specified range.
+ ulong copySrcOffset = Math.Max(srcOffset, innerOffset);
+ ulong copySrcEndOffset = Math.Min(innerEndOffset, srcOffset + copySize);
+
+ if (copySrcEndOffset > copySrcOffset)
+ {
+ copySize = copySrcEndOffset - copySrcOffset;
+ dstOffset += copySrcOffset - srcOffset;
+ srcOffset = copySrcOffset;
+
+ _context.Renderer.Pipeline.CopyBuffer(Handle, virtualBuffer.Handle, (int)srcOffset, (int)dstOffset, (int)copySize);
+ }
+ }
+ }
+
+ /// <summary>
/// Increments the buffer reference count.
/// </summary>
public void IncrementReferenceCount()