diff options
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs')
-rw-r--r-- | src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 215 |
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() |