diff --git a/src/Ryujinx.Graphics.GAL/BufferAccess.cs b/src/Ryujinx.Graphics.GAL/BufferAccess.cs index e7d7ceb0a..faefa5188 100644 --- a/src/Ryujinx.Graphics.GAL/BufferAccess.cs +++ b/src/Ryujinx.Graphics.GAL/BufferAccess.cs @@ -1,9 +1,13 @@ +using System; + namespace Ryujinx.Graphics.GAL { + [Flags] public enum BufferAccess { - Default, - FlushPersistent, - Stream + Default = 0, + FlushPersistent = 1 << 0, + Stream = 1 << 1, + SparseCompatible = 1 << 2, } } diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index 8959bf93e..dc927eaba 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -23,6 +23,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsR4G4B4A4Format; public readonly bool SupportsScaledVertexFormats; public readonly bool SupportsSnormBufferTextureFormat; + public readonly bool SupportsSparseBuffer; public readonly bool Supports5BitComponentFormat; public readonly bool SupportsBlendEquationAdvanced; public readonly bool SupportsFragmentShaderInterlock; @@ -79,6 +80,7 @@ namespace Ryujinx.Graphics.GAL bool supportsScaledVertexFormats, bool supportsSnormBufferTextureFormat, bool supports5BitComponentFormat, + bool supportsSparseBuffer, bool supportsBlendEquationAdvanced, bool supportsFragmentShaderInterlock, bool supportsFragmentShaderOrderingIntel, @@ -130,6 +132,7 @@ namespace Ryujinx.Graphics.GAL SupportsScaledVertexFormats = supportsScaledVertexFormats; SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat; Supports5BitComponentFormat = supports5BitComponentFormat; + SupportsSparseBuffer = supportsSparseBuffer; SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced; SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; diff --git a/src/Ryujinx.Graphics.GAL/IRenderer.cs b/src/Ryujinx.Graphics.GAL/IRenderer.cs index 1dabbdaef..3bf56465e 100644 --- a/src/Ryujinx.Graphics.GAL/IRenderer.cs +++ b/src/Ryujinx.Graphics.GAL/IRenderer.cs @@ -16,13 +16,10 @@ namespace Ryujinx.Graphics.GAL void BackgroundContextAction(Action action, bool alwaysBackground = false); - BufferHandle CreateBuffer(int size, BufferHandle storageHint); - BufferHandle CreateBuffer(int size) - { - return CreateBuffer(size, BufferHandle.Null); - } + BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default); + BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint); BufferHandle CreateBuffer(nint pointer, int size); - BufferHandle CreateBuffer(int size, BufferAccess access); + BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers); IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs index 8feeacf4c..5bf3d3283 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs @@ -44,6 +44,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading Register(CommandType.Action); Register(CommandType.CreateBuffer); Register(CommandType.CreateBufferAccess); + Register(CommandType.CreateBufferSparse); Register(CommandType.CreateHostBuffer); Register(CommandType.CreateProgram); Register(CommandType.CreateSampler); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs index 55a04573e..6be639253 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs @@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading Action, CreateBuffer, CreateBufferAccess, + CreateBufferSparse, CreateHostBuffer, CreateProgram, CreateSampler, diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs index 353227b6a..60a6e4bf4 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs @@ -5,12 +5,14 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer public readonly CommandType CommandType => CommandType.CreateBuffer; private BufferHandle _threadedHandle; private int _size; + private BufferAccess _access; private BufferHandle _storageHint; - public void Set(BufferHandle threadedHandle, int size, BufferHandle storageHint) + public void Set(BufferHandle threadedHandle, int size, BufferAccess access, BufferHandle storageHint) { _threadedHandle = threadedHandle; _size = size; + _access = access; _storageHint = storageHint; } @@ -23,7 +25,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer hint = threaded.Buffers.MapBuffer(command._storageHint); } - threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, hint)); + threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, command._access, hint)); } } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferSparseCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferSparseCommand.cs new file mode 100644 index 000000000..965529ad1 --- /dev/null +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferSparseCommand.cs @@ -0,0 +1,25 @@ +using Ryujinx.Graphics.GAL.Multithreading.Model; +using System; + +namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer +{ + struct CreateBufferSparseCommand : IGALCommand, IGALCommand + { + public readonly CommandType CommandType => CommandType.CreateBufferSparse; + private BufferHandle _threadedHandle; + private SpanRef _buffers; + + public void Set(BufferHandle threadedHandle, SpanRef buffers) + { + _threadedHandle = threadedHandle; + _buffers = buffers; + } + + public static void Run(ref CreateBufferSparseCommand command, ThreadedRenderer threaded, IRenderer renderer) + { + Span buffers = command._buffers.Get(threaded); + threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBufferSparse(threaded.Buffers.MapBufferRanges(buffers))); + command._buffers.Dispose(threaded); + } + } +} diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs index 0e0031b06..830fbf2d9 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -263,10 +263,19 @@ namespace Ryujinx.Graphics.GAL.Multithreading } } - public BufferHandle CreateBuffer(int size, BufferHandle storageHint) + public BufferHandle CreateBuffer(int size, BufferAccess access) { BufferHandle handle = Buffers.CreateBufferHandle(); - New().Set(handle, size, storageHint); + New().Set(handle, size, access); + QueueCommand(); + + return handle; + } + + public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint) + { + BufferHandle handle = Buffers.CreateBufferHandle(); + New().Set(handle, size, access, storageHint); QueueCommand(); return handle; @@ -281,10 +290,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading return handle; } - public BufferHandle CreateBuffer(int size, BufferAccess access) + public BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers) { BufferHandle handle = Buffers.CreateBufferHandle(); - New().Set(handle, size, access); + New().Set(handle, CopySpan(storageBuffers)); QueueCommand(); return handle; diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs index 7d9e1ec02..7f3772f44 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs @@ -5,6 +5,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.GPFifo; using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Memory.Range; using System; using System.Collections.Generic; @@ -392,12 +393,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME _processor.ThreedClass.DrawIndirect( topology, - indirectBufferAddress, - 0, + new MultiRange(indirectBufferAddress, IndirectIndexedDataEntrySize), + default, 1, IndirectIndexedDataEntrySize, indexCount, - Threed.IndirectDrawType.DrawIndexedIndirect); + IndirectDrawType.DrawIndexedIndirect); } else { @@ -494,13 +495,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride; - ulong indirectBufferAddress = bufferCache.TranslateAndCreateBuffer(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize); - ulong parameterBufferAddress = bufferCache.TranslateAndCreateBuffer(_processor.MemoryManager, parameterBufferGpuVa, 4); + MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize); + MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4); _processor.ThreedClass.DrawIndirect( topology, - indirectBufferAddress, - parameterBufferAddress, + indirectBufferRange, + parameterBufferRange, maxDrawCount, stride, indexCount, diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs index d1a333a71..6324e6a15 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs @@ -370,8 +370,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw { var memoryManager = _channel.MemoryManager; - address = memoryManager.Translate(address); - BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(address, size); + BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size)); ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format); bufferTexture.SetStorage(range); @@ -412,9 +411,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw var memoryManager = _channel.MemoryManager; - address = memoryManager.Translate(address + indexOffset); ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1); - BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(address - misalign, size + misalign); + BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign)); misalignedOffset = (int)misalign >> shift; SetIndexBufferTexture(reservations, range, format); diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs index 399ecdd77..8c72663f1 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw; using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Memory.Range; using System; namespace Ryujinx.Graphics.Gpu.Engine.Threed @@ -630,8 +631,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// /// 3D engine where this method is being called /// Primitive topology - /// Address of the buffer with the draw parameters, such as count, first index, etc - /// Address of the buffer with the draw count + /// Memory range of the buffer with the draw parameters, such as count, first index, etc + /// Memory range of the buffer with the draw count /// Maximum number of draws that can be made /// Distance in bytes between each entry on the data pointed to by /// Maximum number of indices that the draw can consume @@ -639,8 +640,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed public void DrawIndirect( ThreedClass engine, PrimitiveTopology topology, - ulong indirectBufferAddress, - ulong parameterBufferAddress, + MultiRange indirectBufferRange, + MultiRange parameterBufferRange, int maxDrawCount, int stride, int indexCount, @@ -681,8 +682,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed if (hasCount) { - var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferAddress, (ulong)maxDrawCount * (ulong)stride); - var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferAddress, 4); + var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange); + var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange); if (indexed) { @@ -695,7 +696,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed } else { - var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferAddress, (ulong)stride); + var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange); if (indexed) { diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs index 0ce2f7b52..ab1d27a1c 100644 --- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs +++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs @@ -6,6 +6,7 @@ using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; using Ryujinx.Graphics.Gpu.Engine.Threed.Blender; using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Synchronization; +using Ryujinx.Memory.Range; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -803,22 +804,22 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed /// Performs a indirect draw, with parameters from a GPU buffer. /// /// Primitive topology - /// Address of the buffer with the draw parameters, such as count, first index, etc - /// Address of the buffer with the draw count + /// Memory range of the buffer with the draw parameters, such as count, first index, etc + /// Memory range of the buffer with the draw count /// Maximum number of draws that can be made - /// Distance in bytes between each entry on the data pointed to by + /// Distance in bytes between each entry on the data pointed to by /// Maximum number of indices that the draw can consume /// Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count public void DrawIndirect( PrimitiveTopology topology, - ulong indirectBufferAddress, - ulong parameterBufferAddress, + MultiRange indirectBufferRange, + MultiRange parameterBufferRange, int maxDrawCount, int stride, int indexCount, IndirectDrawType drawType) { - _drawManager.DrawIndirect(this, topology, indirectBufferAddress, parameterBufferAddress, maxDrawCount, stride, indexCount, drawType); + _drawManager.DrawIndirect(this, topology, indirectBufferRange, parameterBufferRange, maxDrawCount, stride, indexCount, drawType); } /// diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 8eca18b48..963bd947d 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -382,7 +382,7 @@ namespace Ryujinx.Graphics.Gpu.Image { ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex); - cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedTextureBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(bounds.Range)); cachedTextureBufferIndex = textureBufferIndex; if (samplerBufferIndex == textureBufferIndex) @@ -396,7 +396,7 @@ namespace Ryujinx.Graphics.Gpu.Image { ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex); - cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedSamplerBuffer = MemoryMarshal.Cast(_channel.MemoryManager.Physical.GetSpan(bounds.Range)); cachedSamplerBufferIndex = samplerBufferIndex; } } @@ -524,7 +524,7 @@ namespace Ryujinx.Graphics.Gpu.Image // Ensure that the buffer texture is using the correct buffer as storage. // Buffers are frequently re-created to accommodate larger data, so we need to re-bind // to ensure we're not using a old buffer that was already deleted. - _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false); + _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, bindingInfo.Format, false); // Cache is not used for buffer texture, it must always rebind. state.CachedTexture = null; @@ -661,7 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Image format = texture.Format; } - _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true); + _channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range, bindingInfo, format, true); // Cache is not used for buffer texture, it must always rebind. state.CachedTexture = null; diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index c9286a619..12461e96e 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -43,6 +43,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// public int UnmappedSequence { get; private set; } + /// + /// Indicates if the buffer can be used in a sparse buffer mapping. + /// + public bool SparseCompatible { get; } + /// /// 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. @@ -77,15 +82,25 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Physical memory where the buffer is mapped /// Start address of the buffer /// Size of the buffer in bytes + /// Indicates if the buffer can be used in a sparse buffer mapping /// Buffers which this buffer contains, and will inherit tracking handles from - public Buffer(GpuContext context, PhysicalMemory physicalMemory, ulong address, ulong size, IEnumerable baseBuffers = null) + public Buffer( + GpuContext context, + PhysicalMemory physicalMemory, + ulong address, + ulong size, + bool sparseCompatible, + IEnumerable baseBuffers = null) { _context = context; _physicalMemory = physicalMemory; Address = address; Size = size; + SparseCompatible = sparseCompatible; - Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null); + BufferAccess access = sparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default; + + Handle = context.Renderer.CreateBuffer((int)size, access, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null); _useGranular = size > GranularBufferThreshold; diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs index a9ea04cef..aed3268ae 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.Shader; +using Ryujinx.Memory.Range; namespace Ryujinx.Graphics.Gpu.Memory { @@ -8,30 +9,28 @@ namespace Ryujinx.Graphics.Gpu.Memory readonly struct BufferBounds { /// - /// Region virtual address. + /// Physical memory ranges where the buffer is mapped. /// - public ulong Address { get; } - - /// - /// Region size in bytes. - /// - public ulong Size { get; } + public MultiRange Range { get; } /// /// Buffer usage flags. /// public BufferUsageFlags Flags { get; } + /// + /// Indicates that the backing memory for the buffer does not exist. + /// + public bool IsUnmapped => Range.IsUnmapped; + /// /// Creates a new buffer region. /// - /// Region address - /// Region size + /// Physical memory ranges where the buffer is mapped /// Buffer usage flags - public BufferBounds(ulong address, ulong size, BufferUsageFlags flags = BufferUsageFlags.None) + public BufferBounds(MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None) { - Address = address; - Size = size; + Range = range; Flags = flags; } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs index 05cc312c7..bd9aa39c8 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs @@ -11,12 +11,24 @@ namespace Ryujinx.Graphics.Gpu.Memory /// class BufferCache : IDisposable { - private const int OverlapsBufferInitialCapacity = 10; - private const int OverlapsBufferMaxCapacity = 10000; + /// + /// Initial size for the array holding overlaps. + /// + public const int OverlapsBufferInitialCapacity = 10; + + /// + /// Maximum size that an array holding overlaps may have after trimming. + /// + public const int OverlapsBufferMaxCapacity = 10000; private const ulong BufferAlignmentSize = 0x1000; private const ulong BufferAlignmentMask = BufferAlignmentSize - 1; + /// + /// Alignment required for sparse buffer mappings. + /// + public const ulong SparseBufferAlignmentSize = 0x10000; + private const ulong MaxDynamicGrowthSize = 0x100000; private readonly GpuContext _context; @@ -27,6 +39,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Must lock for any access from other threads. /// private readonly RangeList _buffers; + private readonly MultiRangeList _multiRangeBuffers; private Buffer[] _bufferOverlaps; @@ -47,6 +60,7 @@ namespace Ryujinx.Graphics.Gpu.Memory _physicalMemory = physicalMemory; _buffers = new RangeList(); + _multiRangeBuffers = new MultiRangeList(); _bufferOverlaps = new Buffer[OverlapsBufferInitialCapacity]; @@ -66,45 +80,100 @@ namespace Ryujinx.Graphics.Gpu.Memory Buffer[] overlaps = new Buffer[10]; int overlapCount; - ulong address = ((MemoryManager)sender).Translate(e.Address); - ulong size = e.Size; + MultiRange range = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size); - lock (_buffers) + for (int index = 0; index < range.Count; index++) { - overlapCount = _buffers.FindOverlaps(address, size, ref overlaps); - } + MemoryRange subRange = range.GetSubRange(index); - for (int i = 0; i < overlapCount; i++) - { - overlaps[i].Unmapped(address, size); + lock (_buffers) + { + overlapCount = _buffers.FindOverlaps(subRange.Address, subRange.Size, ref overlaps); + } + + for (int i = 0; i < overlapCount; i++) + { + overlaps[i].Unmapped(subRange.Address, subRange.Size); + } } } /// /// Performs address translation of the GPU virtual address, and creates a - /// new buffer, if needed, for the specified range. + /// new buffer, if needed, for the specified contiguous range. /// /// GPU memory manager where the buffer is mapped /// Start GPU virtual address of the buffer /// Size in bytes of the buffer - /// CPU virtual address of the buffer, after address translation - public ulong TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size) + /// Contiguous physical range of the buffer, after address translation + public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size) { if (gpuVa == 0) { - return 0; + return new MultiRange(MemoryManager.PteUnmapped, size); } ulong address = memoryManager.Translate(gpuVa); - if (address == MemoryManager.PteUnmapped) + if (address != MemoryManager.PteUnmapped) { - return 0; + CreateBuffer(address, size); } - CreateBuffer(address, size); + return new MultiRange(address, size); + } - return address; + /// + /// Performs address translation of the GPU virtual address, and creates + /// new buffers, if needed, for the specified range. + /// + /// GPU memory manager where the buffer is mapped + /// Start GPU virtual address of the buffer + /// Size in bytes of the buffer + /// Physical ranges of the buffer, after address translation + public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size) + { + if (gpuVa == 0) + { + return new MultiRange(MemoryManager.PteUnmapped, size); + } + + bool supportsSparse = _context.Capabilities.SupportsSparseBuffer; + + // Fast path not taken for non-contiguous ranges, + // since multi-range buffers are not coalesced, so a buffer that covers + // the entire cached range might not actually exist. + if (memoryManager.VirtualBufferCache.TryGetOrAddRange(gpuVa, size, supportsSparse, out MultiRange range) && + range.Count == 1) + { + return range; + } + + CreateBuffer(range); + + return range; + } + + /// + /// Creates a new buffer for the specified range, if it does not yet exist. + /// This can be used to ensure the existance of a buffer. + /// + /// Physical ranges of memory where the buffer data is located + public void CreateBuffer(MultiRange range) + { + if (range.Count > 1) + { + CreateMultiRangeBuffer(range); + } + else + { + MemoryRange subRange = range.GetSubRange(0); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + CreateBuffer(subRange.Address, subRange.Size); + } + } } /// @@ -118,7 +187,6 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong endAddress = address + size; ulong alignedAddress = address & ~BufferAlignmentMask; - ulong alignedEndAddress = (endAddress + BufferAlignmentMask) & ~BufferAlignmentMask; // The buffer must have the size of at least one page. @@ -130,6 +198,108 @@ namespace Ryujinx.Graphics.Gpu.Memory CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress); } + /// + /// Creates a new buffer for the specified range, if it does not yet exist. + /// This can be used to ensure the existance of a buffer. + /// + /// Address of the buffer in memory + /// Size of the buffer in bytes + /// Alignment of the start address of the buffer in bytes + public void CreateBuffer(ulong address, ulong size, ulong alignment) + { + ulong alignmentMask = alignment - 1; + ulong pageAlignmentMask = BufferAlignmentMask; + ulong endAddress = address + size; + + ulong alignedAddress = address & ~alignmentMask; + ulong alignedEndAddress = (endAddress + pageAlignmentMask) & ~pageAlignmentMask; + + // The buffer must have the size of at least one page. + if (alignedEndAddress == alignedAddress) + { + alignedEndAddress += pageAlignmentMask; + } + + CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, alignment); + } + + /// + /// Creates a buffer for a memory region composed of multiple physical ranges, + /// if it does not exist yet. + /// + /// Physical ranges of memory + private void CreateMultiRangeBuffer(MultiRange range) + { + // Ensure all non-contiguous buffer we might use are sparse aligned. + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize); + } + } + + // Create sparse buffer. + MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10]; + + int overlapCount = _multiRangeBuffers.FindOverlaps(range, ref overlaps); + + for (int index = 0; index < overlapCount; index++) + { + if (overlaps[index].Range.Contains(range)) + { + return; + } + } + + for (int index = 0; index < overlapCount; index++) + { + if (range.Contains(overlaps[index].Range)) + { + _multiRangeBuffers.Remove(overlaps[index]); + overlaps[index].Dispose(); + } + } + + BufferRange[] storages = new BufferRange[range.Count]; + MemoryRange[] alignedSubRanges = new MemoryRange[range.Count]; + + ulong alignmentMask = SparseBufferAlignmentSize - 1; + + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + if (subRange.Address != MemoryManager.PteUnmapped) + { + ulong endAddress = subRange.Address + subRange.Size; + + ulong alignedAddress = subRange.Address & ~alignmentMask; + ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask; + ulong alignedSize = alignedEndAddress - alignedAddress; + + Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize); + BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false); + + storages[i] = bufferRange; + alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); + } + else + { + ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask; + + storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize); + alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize); + } + } + + MultiRangeBuffer multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages); + + _multiRangeBuffers.Add(multiRangeBuffer); + } + /// /// Performs address translation of the GPU virtual address, and attempts to force /// the buffer in the region as dirty. @@ -150,7 +320,8 @@ namespace Ryujinx.Graphics.Gpu.Memory result.EndGpuAddress < gpuVa + size || result.UnmappedSequence != result.Buffer.UnmappedSequence) { - ulong address = TranslateAndCreateBuffer(memoryManager, gpuVa, size); + MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size); + ulong address = range.GetSubRange(0).Address; result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size)); _dirtyCache[gpuVa] = result; @@ -184,7 +355,8 @@ namespace Ryujinx.Graphics.Gpu.Memory result.EndGpuAddress < alignedEndGpuVa || result.UnmappedSequence != result.Buffer.UnmappedSequence) { - ulong address = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size); + MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size); + ulong address = range.GetSubRange(0).Address; result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size)); _modifiedCache[alignedGpuVa] = result; @@ -204,7 +376,8 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Size in bytes of the buffer private void CreateBufferAligned(ulong address, ulong size) { - int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps); + Buffer[] overlaps = _bufferOverlaps; + int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); if (overlapsCount != 0) { @@ -215,9 +388,12 @@ namespace Ryujinx.Graphics.Gpu.Memory // old buffer(s) to the new buffer. ulong endAddress = address + size; + Buffer overlap0 = overlaps[0]; - if (_bufferOverlaps[0].Address > address || _bufferOverlaps[0].EndAddress < endAddress) + if (overlap0.Address > address || overlap0.EndAddress < endAddress) { + bool anySparseCompatible = false; + // Check if the following conditions are met: // - We have a single overlap. // - The overlap starts at or before the requested range. That is, the overlap happens at the end. @@ -228,23 +404,25 @@ namespace Ryujinx.Graphics.Gpu.Memory // Allowing for 2 pages (rather than just one) is necessary to catch cases where the // range crosses a page, and after alignment, ends having a size of 2 pages. if (overlapsCount == 1 && - address >= _bufferOverlaps[0].Address && - endAddress - _bufferOverlaps[0].EndAddress <= BufferAlignmentSize * 2) + address >= overlap0.Address && + endAddress - overlap0.EndAddress <= BufferAlignmentSize * 2) { // Try to grow the buffer by 1.5x of its current size. // This improves performance in the cases where the buffer is resized often by small amounts. - ulong existingSize = _bufferOverlaps[0].Size; + ulong existingSize = overlap0.Size; ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask; size = Math.Max(size, growthSize); endAddress = address + size; - overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps); + overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); } for (int index = 0; index < overlapsCount; index++) { - Buffer buffer = _bufferOverlaps[index]; + Buffer buffer = overlaps[index]; + + anySparseCompatible |= buffer.SparseCompatible; address = Math.Min(address, buffer.Address); endAddress = Math.Max(endAddress, buffer.EndAddress); @@ -257,35 +435,13 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong newSize = endAddress - address; - Buffer newBuffer = new(_context, _physicalMemory, address, newSize, _bufferOverlaps.Take(overlapsCount)); - - lock (_buffers) - { - _buffers.Add(newBuffer); - } - - for (int index = 0; index < overlapsCount; index++) - { - Buffer buffer = _bufferOverlaps[index]; - - int dstOffset = (int)(buffer.Address - newBuffer.Address); - - buffer.CopyTo(newBuffer, dstOffset); - newBuffer.InheritModifiedRanges(buffer); - - buffer.DecrementReferenceCount(); - } - - newBuffer.SynchronizeMemory(address, newSize); - - // Existing buffers were modified, we need to rebind everything. - NotifyBuffersModified?.Invoke(); + CreateBufferAligned(address, newSize, anySparseCompatible, overlaps, overlapsCount); } } else { // No overlap, just create a new buffer. - Buffer buffer = new(_context, _physicalMemory, address, size); + Buffer buffer = new(_context, _physicalMemory, address, size, sparseCompatible: false); lock (_buffers) { @@ -296,6 +452,151 @@ namespace Ryujinx.Graphics.Gpu.Memory ShrinkOverlapsBufferIfNeeded(); } + /// + /// Creates a new buffer for the specified range, if needed. + /// If a buffer where this range can be fully contained already exists, + /// then the creation of a new buffer is not necessary. + /// + /// Address of the buffer in guest memory + /// Size in bytes of the buffer + /// Alignment of the start address of the buffer + private void CreateBufferAligned(ulong address, ulong size, ulong alignment) + { + Buffer[] overlaps = _bufferOverlaps; + int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps); + bool sparseAligned = alignment >= SparseBufferAlignmentSize; + + if (overlapsCount != 0) + { + // If the buffer already exists, make sure if covers the entire range, + // and make sure it is properly aligned, otherwise sparse mapping may fail. + + ulong endAddress = address + size; + Buffer overlap0 = overlaps[0]; + + if (overlap0.Address > address || + overlap0.EndAddress < endAddress || + (overlap0.Address & (alignment - 1)) != 0 || + (!overlap0.SparseCompatible && sparseAligned)) + { + // We need to make sure the new buffer is properly aligned. + // However, after the range is aligned, it is possible that it + // overlaps more buffers, so try again after each extension + // and ensure we cover all overlaps. + + int oldOverlapsCount; + + do + { + for (int index = 0; index < overlapsCount; index++) + { + Buffer buffer = overlaps[index]; + + address = Math.Min(address, buffer.Address); + endAddress = Math.Max(endAddress, buffer.EndAddress); + } + + address &= ~(alignment - 1); + + oldOverlapsCount = overlapsCount; + overlapsCount = _buffers.FindOverlapsNonOverlapping(address, endAddress - address, ref overlaps); + } + while (oldOverlapsCount != overlapsCount); + + lock (_buffers) + { + for (int index = 0; index < overlapsCount; index++) + { + _buffers.Remove(overlaps[index]); + } + } + + ulong newSize = endAddress - address; + + CreateBufferAligned(address, newSize, sparseAligned, overlaps, overlapsCount); + } + } + else + { + // No overlap, just create a new buffer. + Buffer buffer = new(_context, _physicalMemory, address, size, sparseAligned); + + lock (_buffers) + { + _buffers.Add(buffer); + } + } + + ShrinkOverlapsBufferIfNeeded(); + } + + /// + /// Creates a new buffer for the specified range, if needed. + /// If a buffer where this range can be fully contained already exists, + /// then the creation of a new buffer is not necessary. + /// + /// Address of the buffer in guest memory + /// Size in bytes of the buffer + /// Indicates if the buffer can be used in a sparse buffer mapping + /// Buffers overlapping the range + /// Total of overlaps + private void CreateBufferAligned(ulong address, ulong size, bool sparseCompatible, Buffer[] overlaps, int overlapsCount) + { + Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, sparseCompatible, overlaps.Take(overlapsCount)); + + lock (_buffers) + { + _buffers.Add(newBuffer); + } + + for (int index = 0; index < overlapsCount; index++) + { + Buffer buffer = overlaps[index]; + + int dstOffset = (int)(buffer.Address - newBuffer.Address); + + buffer.CopyTo(newBuffer, dstOffset); + newBuffer.InheritModifiedRanges(buffer); + + buffer.DecrementReferenceCount(); + } + + newBuffer.SynchronizeMemory(address, size); + + // Existing buffers were modified, we need to rebind everything. + NotifyBuffersModified?.Invoke(); + + RecreateMultiRangeBuffers(address, size); + } + + /// + /// Recreates all the multi-range buffers that overlaps a given physical memory range. + /// + /// Start address of the range + /// Size of the range in bytes + private void RecreateMultiRangeBuffers(ulong address, ulong size) + { + if ((address & (SparseBufferAlignmentSize - 1)) != 0 || (size & (SparseBufferAlignmentSize - 1)) != 0) + { + return; + } + + MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10]; + + int overlapCount = _multiRangeBuffers.FindOverlaps(address, size, ref overlaps); + + for (int index = 0; index < overlapCount; index++) + { + _multiRangeBuffers.Remove(overlaps[index]); + overlaps[index].Dispose(); + } + + for (int index = 0; index < overlapCount; index++) + { + CreateMultiRangeBuffer(overlaps[index].Range); + } + } + /// /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. /// @@ -319,9 +620,63 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Size in bytes of the copy public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size) { - ulong srcAddress = TranslateAndCreateBuffer(memoryManager, srcVa, size); - ulong dstAddress = TranslateAndCreateBuffer(memoryManager, dstVa, size); + MultiRange srcRange = TranslateAndCreateMultiBuffers(memoryManager, srcVa, size); + MultiRange dstRange = TranslateAndCreateMultiBuffers(memoryManager, dstVa, size); + if (srcRange.Count == 1 && dstRange.Count == 1) + { + CopyBufferSingleRange(memoryManager, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size); + } + else + { + ulong copiedSize = 0; + ulong srcOffset = 0; + ulong dstOffset = 0; + int srcRangeIndex = 0; + int dstRangeIndex = 0; + + while (copiedSize < size) + { + if (srcRange.GetSubRange(srcRangeIndex).Size == srcOffset) + { + srcRangeIndex++; + srcOffset = 0; + } + + if (dstRange.GetSubRange(dstRangeIndex).Size == dstOffset) + { + dstRangeIndex++; + dstOffset = 0; + } + + MemoryRange srcSubRange = srcRange.GetSubRange(srcRangeIndex); + MemoryRange dstSubRange = dstRange.GetSubRange(dstRangeIndex); + + ulong srcSize = srcSubRange.Size - srcOffset; + ulong dstSize = dstSubRange.Size - dstOffset; + ulong copySize = Math.Min(srcSize, dstSize); + + CopyBufferSingleRange(memoryManager, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize); + + srcOffset += copySize; + dstOffset += copySize; + copiedSize += copySize; + } + } + } + + /// + /// Copy a buffer data from a given address to another. + /// + /// + /// This does a GPU side copy. + /// + /// GPU memory manager where the buffer is mapped + /// Physical address of the copy source + /// Physical address of the copy destination + /// Size in bytes of the copy + private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size) + { Buffer srcBuffer = GetBuffer(srcAddress, size); Buffer dstBuffer = GetBuffer(dstAddress, size); @@ -360,39 +715,98 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Value to be written into the buffer public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value) { - ulong address = TranslateAndCreateBuffer(memoryManager, gpuVa, size); + MultiRange range = TranslateAndCreateMultiBuffers(memoryManager, gpuVa, size); - Buffer buffer = GetBuffer(address, size); + for (int index = 0; index < range.Count; index++) + { + MemoryRange subRange = range.GetSubRange(index); + Buffer buffer = GetBuffer(subRange.Address, subRange.Size); - int offset = (int)(address - buffer.Address); + int offset = (int)(subRange.Address - buffer.Address); - _context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)size, value); + _context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value); - memoryManager.Physical.FillTrackedResource(address, size, value, ResourceKind.Buffer); + memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer); + } } /// - /// Gets a buffer sub-range from a start address til a page boundary after the given size. + /// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary. /// - /// Start address of the memory range - /// Size in bytes of the memory range + /// Physical regions of memory where the buffer is mapped /// Whether the buffer will be written to by this use /// The buffer sub-range starting at the given memory address - public BufferRange GetBufferRangeAligned(ulong address, ulong size, bool write = false) + public BufferRange GetBufferRangeAligned(MultiRange range, bool write = false) { - return GetBuffer(address, size, write).GetRangeAligned(address, size, write); + if (range.Count > 1) + { + return GetBuffer(range, write).GetRange(range); + } + else + { + MemoryRange subRange = range.GetSubRange(0); + return GetBuffer(subRange.Address, subRange.Size, write).GetRangeAligned(subRange.Address, subRange.Size, write); + } } /// /// Gets a buffer sub-range for a given memory range. /// - /// Start address of the memory range - /// Size in bytes of the memory range + /// Physical regions of memory where the buffer is mapped /// Whether the buffer will be written to by this use /// The buffer sub-range for the given range - public BufferRange GetBufferRange(ulong address, ulong size, bool write = false) + public BufferRange GetBufferRange(MultiRange range, bool write = false) { - return GetBuffer(address, size, write).GetRange(address, size, write); + if (range.Count > 1) + { + return GetBuffer(range, write).GetRange(range); + } + else + { + MemoryRange subRange = range.GetSubRange(0); + return GetBuffer(subRange.Address, subRange.Size, write).GetRange(subRange.Address, subRange.Size, write); + } + } + + /// + /// Gets a buffer for a given memory range. + /// A buffer overlapping with the specified range is assumed to already exist on the cache. + /// + /// Physical regions of memory where the buffer is mapped + /// Whether the buffer will be written to by this use + /// The buffer where the range is fully contained + private MultiRangeBuffer GetBuffer(MultiRange range, bool write = false) + { + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + Buffer subBuffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size); + + subBuffer.SynchronizeMemory(subRange.Address, subRange.Size); + + if (write) + { + subBuffer.SignalModified(subRange.Address, subRange.Size); + } + } + + MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10]; + + int overlapCount = _multiRangeBuffers.FindOverlaps(range, ref overlaps); + + MultiRangeBuffer buffer = null; + + for (int i = 0; i < overlapCount; i++) + { + if (overlaps[i].Range.Contains(range)) + { + buffer = overlaps[i]; + break; + } + } + + return buffer; } /// @@ -426,12 +840,33 @@ namespace Ryujinx.Graphics.Gpu.Memory return buffer; } + /// + /// Performs guest to host memory synchronization of a given memory range. + /// + /// Physical regions of memory where the buffer is mapped + public void SynchronizeBufferRange(MultiRange range) + { + if (range.Count == 1) + { + MemoryRange subRange = range.GetSubRange(0); + SynchronizeBufferRange(subRange.Address, subRange.Size); + } + else + { + for (int index = 0; index < range.Count; index++) + { + MemoryRange subRange = range.GetSubRange(index); + SynchronizeBufferRange(subRange.Address, subRange.Size); + } + } + } + /// /// Performs guest to host memory synchronization of a given memory range. /// /// Start address of the memory range /// Size in bytes of the memory range - public void SynchronizeBufferRange(ulong address, ulong size) + private void SynchronizeBufferRange(ulong address, ulong size) { if (size != 0) { @@ -491,7 +926,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Disposes all buffers in the cache. - /// It's an error to use the buffer manager after disposal. + /// It's an error to use the buffer cache after disposal. /// public void Dispose() { diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs index 01f594776..c65602b5b 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs @@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Shader; +using Ryujinx.Memory.Range; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -62,18 +63,19 @@ namespace Ryujinx.Graphics.Gpu.Memory Bindings = new BufferDescriptor[count]; Buffers = new BufferBounds[count]; Unaligned = new bool[count]; + + Buffers.AsSpan().Fill(new BufferBounds(new MultiRange(MemoryManager.PteUnmapped, 0UL))); } /// /// Sets the region of a buffer at a given slot. /// /// Buffer slot - /// Region virtual address - /// Region size in bytes + /// Physical memory regions where the buffer is mapped /// Buffer usage flags - public void SetBounds(int index, ulong address, ulong size, BufferUsageFlags flags = BufferUsageFlags.None) + public void SetBounds(int index, MultiRange range, BufferUsageFlags flags = BufferUsageFlags.None) { - Buffers[index] = new BufferBounds(address, size, flags); + Buffers[index] = new BufferBounds(range, flags); } /// @@ -120,6 +122,7 @@ namespace Ryujinx.Graphics.Gpu.Memory _context = context; _channel = channel; + _indexBuffer.Range = new MultiRange(MemoryManager.PteUnmapped, 0UL); _vertexBuffers = new VertexBuffer[Constants.TotalVertexBuffers]; _transformFeedbackBuffers = new BufferBounds[Constants.TotalTransformFeedbackBuffers]; @@ -150,10 +153,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Type of each index buffer element public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type) { - ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); - _indexBuffer.Address = address; - _indexBuffer.Size = size; + _indexBuffer.Range = range; _indexBuffer.Type = type; _indexBufferDirty = true; @@ -181,16 +183,15 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Vertex divisor of the buffer, for instanced draws public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor) { - ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); - _vertexBuffers[index].Address = address; - _vertexBuffers[index].Size = size; + _vertexBuffers[index].Range = range; _vertexBuffers[index].Stride = stride; _vertexBuffers[index].Divisor = divisor; _vertexBuffersDirty = true; - if (address != 0) + if (!range.IsUnmapped) { _vertexBuffersEnableMask |= 1u << index; } @@ -209,9 +210,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Size in bytes of the transform feedback buffer public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size) { - ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size); - _transformFeedbackBuffers[index] = new BufferBounds(address, size); + _transformFeedbackBuffers[index] = new BufferBounds(range); _transformFeedbackBuffersDirty = true; } @@ -256,9 +257,9 @@ namespace Ryujinx.Graphics.Gpu.Memory gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment); - ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size); - _cpStorageBuffers.SetBounds(index, address, size, flags); + _cpStorageBuffers.SetBounds(index, range, flags); } /// @@ -280,15 +281,14 @@ namespace Ryujinx.Graphics.Gpu.Memory gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment); - ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size); - if (buffers.Buffers[index].Address != address || - buffers.Buffers[index].Size != size) + if (!buffers.Buffers[index].Range.Equals(range)) { _gpStorageBuffersDirty = true; } - buffers.SetBounds(index, address, size, flags); + buffers.SetBounds(index, range, flags); } /// @@ -300,9 +300,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Size in bytes of the storage buffer public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size) { - ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); - _cpUniformBuffers.SetBounds(index, address, size); + _cpUniformBuffers.SetBounds(index, range); } /// @@ -315,9 +315,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Size in bytes of the storage buffer public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size) { - ulong address = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); + MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size); - _gpUniformBuffers[stage].SetBounds(index, address, size); + _gpUniformBuffers[stage].SetBounds(index, range); _gpUniformBuffersDirty = true; } @@ -379,7 +379,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < _cpUniformBuffers.Buffers.Length; i++) { - if (_cpUniformBuffers.Buffers[i].Address != 0) + if (!_cpUniformBuffers.Buffers[i].IsUnmapped) { mask |= 1u << i; } @@ -399,7 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < _gpUniformBuffers[stage].Buffers.Length; i++) { - if (_gpUniformBuffers[stage].Buffers[i].Address != 0) + if (!_gpUniformBuffers[stage].Buffers[i].IsUnmapped) { mask |= 1u << i; } @@ -415,7 +415,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The uniform buffer address, or an undefined value if the buffer is not currently bound public ulong GetComputeUniformBufferAddress(int index) { - return _cpUniformBuffers.Buffers[index].Address; + return _cpUniformBuffers.Buffers[index].Range.GetSubRange(0).Address; } /// @@ -426,7 +426,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The uniform buffer address, or an undefined value if the buffer is not currently bound public ulong GetGraphicsUniformBufferAddress(int stage, int index) { - return _gpUniformBuffers[stage].Buffers[index].Address; + return _gpUniformBuffers[stage].Buffers[index].Range.GetSubRange(0).Address; } /// @@ -477,7 +477,7 @@ namespace Ryujinx.Graphics.Gpu.Memory foreach (var binding in _bufferTextures) { var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); - var range = _channel.MemoryManager.Physical.BufferCache.GetBufferRange(binding.Address, binding.Size, isStore); + var range = _channel.MemoryManager.Physical.BufferCache.GetBufferRange(binding.Range, isStore); binding.Texture.SetStorage(range); // The texture must be rebound to use the new storage if it was updated. @@ -511,16 +511,16 @@ namespace Ryujinx.Graphics.Gpu.Memory { _indexBufferDirty = false; - if (_indexBuffer.Address != 0) + if (!_indexBuffer.Range.IsUnmapped) { - BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Address, _indexBuffer.Size); + BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range); _context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type); } } - else if (_indexBuffer.Address != 0) + else if (!_indexBuffer.Range.IsUnmapped) { - bufferCache.SynchronizeBufferRange(_indexBuffer.Address, _indexBuffer.Size); + bufferCache.SynchronizeBufferRange(_indexBuffer.Range); } } else if (_rebind) @@ -540,12 +540,12 @@ namespace Ryujinx.Graphics.Gpu.Memory { VertexBuffer vb = _vertexBuffers[index]; - if (vb.Address == 0) + if (vb.Range.IsUnmapped) { continue; } - BufferRange buffer = bufferCache.GetBufferRange(vb.Address, vb.Size); + BufferRange buffer = bufferCache.GetBufferRange(vb.Range); vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor); } @@ -558,12 +558,12 @@ namespace Ryujinx.Graphics.Gpu.Memory { VertexBuffer vb = _vertexBuffers[index]; - if (vb.Address == 0) + if (vb.Range.IsUnmapped) { continue; } - bufferCache.SynchronizeBufferRange(vb.Address, vb.Size); + bufferCache.SynchronizeBufferRange(vb.Range); } } @@ -579,13 +579,13 @@ namespace Ryujinx.Graphics.Gpu.Memory { BufferBounds tfb = _transformFeedbackBuffers[index]; - if (tfb.Address == 0) + if (tfb.IsUnmapped) { tfbs[index] = BufferRange.Empty; continue; } - tfbs[index] = bufferCache.GetBufferRange(tfb.Address, tfb.Size, write: true); + tfbs[index] = bufferCache.GetBufferRange(tfb.Range, write: true); } _context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs); @@ -600,21 +600,39 @@ namespace Ryujinx.Graphics.Gpu.Memory { BufferBounds tfb = _transformFeedbackBuffers[index]; - if (tfb.Address == 0) + if (tfb.IsUnmapped) { buffers[index] = new BufferAssignment(index, BufferRange.Empty); } else { - ulong endAddress = tfb.Address + tfb.Size; - ulong address = BitUtils.AlignDown(tfb.Address, (ulong)alignment); - ulong size = endAddress - address; + MultiRange range = tfb.Range; + ulong address0 = range.GetSubRange(0).Address; + ulong address = BitUtils.AlignDown(address0, (ulong)alignment); - int tfeOffset = ((int)tfb.Address & (alignment - 1)) / 4; + if (range.Count == 1) + { + range = new MultiRange(address, range.GetSubRange(0).Size + (address0 - address)); + } + else + { + MemoryRange[] subRanges = new MemoryRange[range.Count]; + + subRanges[0] = new MemoryRange(address, range.GetSubRange(0).Size + (address0 - address)); + + for (int i = 1; i < range.Count; i++) + { + subRanges[i] = range.GetSubRange(i); + } + + range = new MultiRange(subRanges); + } + + int tfeOffset = ((int)address0 & (alignment - 1)) / 4; _context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset); - buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(address, size, write: true)); + buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, write: true)); } } @@ -627,12 +645,12 @@ namespace Ryujinx.Graphics.Gpu.Memory { BufferBounds tfb = _transformFeedbackBuffers[index]; - if (tfb.Address == 0) + if (tfb.IsUnmapped) { continue; } - bufferCache.SynchronizeBufferRange(tfb.Address, tfb.Size); + bufferCache.SynchronizeBufferRange(tfb.Range); } } @@ -688,12 +706,12 @@ namespace Ryujinx.Graphics.Gpu.Memory BufferBounds bounds = buffers.Buffers[bindingInfo.Slot]; - if (bounds.Address != 0) + if (!bounds.IsUnmapped) { var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); var range = isStorage - ? bufferCache.GetBufferRangeAligned(bounds.Address, bounds.Size, isWrite) - : bufferCache.GetBufferRange(bounds.Address, bounds.Size); + ? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite) + : bufferCache.GetBufferRange(bounds.Range); ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range); } @@ -725,12 +743,12 @@ namespace Ryujinx.Graphics.Gpu.Memory BufferBounds bounds = buffers.Buffers[bindingInfo.Slot]; - if (bounds.Address != 0) + if (!bounds.IsUnmapped) { var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write); var range = isStorage - ? bufferCache.GetBufferRangeAligned(bounds.Address, bounds.Size, isWrite) - : bufferCache.GetBufferRange(bounds.Address, bounds.Size); + ? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite) + : bufferCache.GetBufferRange(bounds.Range); ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range); } @@ -778,12 +796,12 @@ namespace Ryujinx.Graphics.Gpu.Memory BufferBounds bounds = buffers.Buffers[binding.Slot]; - if (bounds.Address == 0) + if (bounds.IsUnmapped) { continue; } - _channel.MemoryManager.Physical.BufferCache.SynchronizeBufferRange(bounds.Address, bounds.Size); + _channel.MemoryManager.Physical.BufferCache.SynchronizeBufferRange(bounds.Range); } } } @@ -793,23 +811,21 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Shader stage accessing the texture /// Buffer texture - /// Address of the buffer in memory - /// Size of the buffer in bytes + /// Physical ranges of memory where the buffer texture data is located /// Binding info for the buffer texture /// Format of the buffer texture /// Whether the binding is for an image or a sampler public void SetBufferTextureStorage( ShaderStage stage, ITexture texture, - ulong address, - ulong size, + MultiRange range, TextureBindingInfo bindingInfo, Format format, bool isImage) { - _channel.MemoryManager.Physical.BufferCache.CreateBuffer(address, size); + _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range); - _bufferTextures.Add(new BufferTextureBinding(stage, texture, address, size, bindingInfo, format, isImage)); + _bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage)); } /// diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs index c5f0401b7..bf0beffa2 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Shader; +using Ryujinx.Memory.Range; namespace Ryujinx.Graphics.Gpu.Memory { @@ -20,14 +21,9 @@ namespace Ryujinx.Graphics.Gpu.Memory public ITexture Texture { get; } /// - /// The base address of the buffer binding. + /// Physical ranges of memory where the buffer texture data is located. /// - public ulong Address { get; } - - /// - /// The size of the buffer binding in bytes. - /// - public ulong Size { get; } + public MultiRange Range { get; } /// /// The image or sampler binding info for the buffer texture. @@ -49,24 +45,21 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Shader stage accessing the texture /// Buffer texture - /// Base address - /// Size in bytes + /// Physical ranges of memory where the buffer texture data is located /// Binding info /// Binding format /// Whether the binding is for an image or a sampler public BufferTextureBinding( ShaderStage stage, ITexture texture, - ulong address, - ulong size, + MultiRange range, TextureBindingInfo bindingInfo, Format format, bool isImage) { Stage = stage; Texture = texture; - Address = address; - Size = size; + Range = range; BindingInfo = bindingInfo; Format = format; IsImage = isImage; diff --git a/src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs index c72fa50e5..04114a955 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.GAL; +using Ryujinx.Memory.Range; namespace Ryujinx.Graphics.Gpu.Memory { @@ -7,9 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// struct IndexBuffer { - public ulong Address; - public ulong Size; - + public MultiRange Range; public IndexType Type; } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index 6af12de11..5e19bddc3 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// internal PhysicalMemory Physical { get; } + /// + /// Virtual buffer cache. + /// + internal VirtualBufferCache VirtualBufferCache { get; } + /// /// Cache of GPU counters. /// @@ -51,10 +56,12 @@ namespace Ryujinx.Graphics.Gpu.Memory internal MemoryManager(PhysicalMemory physicalMemory) { Physical = physicalMemory; + VirtualBufferCache = new VirtualBufferCache(this); CounterCache = new CounterCache(); _pageTable = new ulong[PtLvl0Size][]; MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler; MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler; + MemoryUnmapped += VirtualBufferCache.MemoryUnmappedHandler; MemoryUnmapped += CounterCache.MemoryUnmappedHandler; } @@ -508,6 +515,11 @@ namespace Ryujinx.Graphics.Gpu.Memory regionSize += Math.Min(endVa - va, PageSize); } + if (regions.Count == 0) + { + return new MultiRange(regionStart, regionSize); + } + regions.Add(new MemoryRange(regionStart, regionSize)); return new MultiRange(regions.ToArray()); diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs new file mode 100644 index 000000000..e039a7a43 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/MultiRangeBuffer.cs @@ -0,0 +1,60 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Memory.Range; +using System; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Buffer, used to store vertex and index data, uniform and storage buffers, and others. + /// + class MultiRangeBuffer : IMultiRangeItem, IDisposable + { + private readonly GpuContext _context; + + /// + /// Host buffer handle. + /// + public BufferHandle Handle { get; } + + /// + /// Range of memory where the data is located. + /// + public MultiRange Range { get; } + + /// + /// Creates a new instance of the buffer. + /// + /// GPU context that the buffer belongs to + /// Range of memory where the data is mapped + /// Backing memory for the buffers + public MultiRangeBuffer(GpuContext context, MultiRange range, ReadOnlySpan storages) + { + _context = context; + Range = range; + Handle = context.Renderer.CreateBufferSparse(storages); + } + + /// + /// Gets a sub-range from the buffer. + /// + /// + /// This can be used to bind and use sub-ranges of the buffer on the host API. + /// + /// Range of memory where the data is mapped + /// The buffer sub-range + public BufferRange GetRange(MultiRange range) + { + int offset = Range.FindOffset(range); + + return new BufferRange(Handle, offset, (int)range.GetSize()); + } + + /// + /// Disposes the host buffer. + /// + public void Dispose() + { + _context.Renderer.DeleteBuffer(Handle); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs index ac334881d..206e1b48c 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs @@ -1,3 +1,5 @@ +using Ryujinx.Memory.Range; + namespace Ryujinx.Graphics.Gpu.Memory { /// @@ -5,8 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// struct VertexBuffer { - public ulong Address; - public ulong Size; + public MultiRange Range; public int Stride; public int Divisor; } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VirtualBufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/VirtualBufferCache.cs new file mode 100644 index 000000000..858c5e3b0 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/VirtualBufferCache.cs @@ -0,0 +1,238 @@ +using Ryujinx.Memory.Range; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// Virtual buffer cache. + /// + class VirtualBufferCache + { + private readonly MemoryManager _memoryManager; + + /// + /// Represents a GPU virtual memory range. + /// + private readonly struct VirtualRange : IRange + { + /// + /// GPU virtual address where the range starts. + /// + public ulong Address { get; } + + /// + /// Size of the range in bytes. + /// + public ulong Size { get; } + + /// + /// GPU virtual address where the range ends. + /// + public ulong EndAddress => Address + Size; + + /// + /// Physical regions where the GPU virtual region is mapped. + /// + public MultiRange Range { get; } + + /// + /// Creates a new virtual memory range. + /// + /// GPU virtual address where the range starts + /// Size of the range in bytes + /// Physical regions where the GPU virtual region is mapped + public VirtualRange(ulong address, ulong size, MultiRange range) + { + Address = address; + Size = size; + Range = range; + } + + /// + /// Checks if a given range overlaps with the buffer. + /// + /// Start address of the range + /// Size in bytes of the range + /// True if the range overlaps, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + } + + private readonly RangeList _virtualRanges; + private VirtualRange[] _virtualRangeOverlaps; + private readonly ConcurrentQueue _deferredUnmaps; + private int _hasDeferredUnmaps; + + /// + /// Creates a new instance of the virtual buffer cache. + /// + /// Memory manager that the virtual buffer cache belongs to + public VirtualBufferCache(MemoryManager memoryManager) + { + _memoryManager = memoryManager; + _virtualRanges = new RangeList(); + _virtualRangeOverlaps = new VirtualRange[BufferCache.OverlapsBufferInitialCapacity]; + _deferredUnmaps = new ConcurrentQueue(); + } + + /// + /// Handles removal of buffers written to a memory region being unmapped. + /// + /// Sender object + /// Event arguments + public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) + { + void EnqueueUnmap() + { + _deferredUnmaps.Enqueue(new VirtualRange(e.Address, e.Size, default)); + + Interlocked.Exchange(ref _hasDeferredUnmaps, 1); + } + + e.AddRemapAction(EnqueueUnmap); + } + + /// + /// Tries to get a existing, cached physical range for the specified virtual region. + /// If no cached range is found, a new one is created and added. + /// + /// GPU virtual address to get the physical range from + /// Size in bytes of the region + /// Indicates host support for sparse buffer mapping of non-contiguous ranges + /// Physical range for the specified GPU virtual region + /// True if the range already existed, false if a new one was created and added + public bool TryGetOrAddRange(ulong gpuVa, ulong size, bool supportsSparse, out MultiRange range) + { + VirtualRange[] overlaps = _virtualRangeOverlaps; + int overlapsCount; + + if (Interlocked.Exchange(ref _hasDeferredUnmaps, 0) != 0) + { + while (_deferredUnmaps.TryDequeue(out VirtualRange unmappedRange)) + { + overlapsCount = _virtualRanges.FindOverlapsNonOverlapping(unmappedRange.Address, unmappedRange.Size, ref overlaps); + + for (int index = 0; index < overlapsCount; index++) + { + _virtualRanges.Remove(overlaps[index]); + } + } + } + + bool found = false; + + ulong originalVa = gpuVa; + + overlapsCount = _virtualRanges.FindOverlapsNonOverlapping(gpuVa, size, ref overlaps); + + if (overlapsCount != 0) + { + // The virtual range already exists. We just need to check if our range fits inside + // the existing one, and if not, we must extend the existing one. + + ulong endAddress = gpuVa + size; + VirtualRange overlap0 = overlaps[0]; + + if (overlap0.Address > gpuVa || overlap0.EndAddress < endAddress) + { + for (int index = 0; index < overlapsCount; index++) + { + VirtualRange virtualRange = overlaps[index]; + + gpuVa = Math.Min(gpuVa, virtualRange.Address); + endAddress = Math.Max(endAddress, virtualRange.EndAddress); + + _virtualRanges.Remove(virtualRange); + } + + ulong newSize = endAddress - gpuVa; + MultiRange newRange = _memoryManager.GetPhysicalRegions(gpuVa, newSize); + + _virtualRanges.Add(new(gpuVa, newSize, newRange)); + + range = newRange.Slice(originalVa - gpuVa, size); + } + else + { + found = true; + range = overlap0.Range.Slice(gpuVa - overlap0.Address, size); + } + } + else + { + // No overlap, just create a new virtual range. + range = _memoryManager.GetPhysicalRegions(gpuVa, size); + + VirtualRange virtualRange = new(gpuVa, size, range); + + _virtualRanges.Add(virtualRange); + } + + ShrinkOverlapsBufferIfNeeded(); + + // If the the range is not properly aligned for sparse mapping, + // or if the host does not support sparse mapping, let's just + // force it to a single range. + // This might cause issues in some applications that uses sparse + // mappings. + if (!IsSparseAligned(range) || !supportsSparse) + { + range = new MultiRange(range.GetSubRange(0).Address, size); + } + + return found; + } + + /// + /// Checks if the physical memory ranges are valid for sparse mapping, + /// which requires all sub-ranges to be 64KB aligned. + /// + /// Range to check + /// True if the range is valid for sparse mapping, false otherwise + private static bool IsSparseAligned(MultiRange range) + { + if (range.Count == 1) + { + return (range.GetSubRange(0).Address & (BufferCache.SparseBufferAlignmentSize - 1)) == 0; + } + + for (int i = 0; i < range.Count; i++) + { + MemoryRange subRange = range.GetSubRange(i); + + // Check if address is aligned. The address of the first sub-range can + // be misaligned as it is at the start. + if (i > 0 && + subRange.Address != MemoryManager.PteUnmapped && + (subRange.Address & (BufferCache.SparseBufferAlignmentSize - 1)) != 0) + { + return false; + } + + // Check if the size is aligned. The size of the last sub-range can + // be misaligned as it is at the end. + if (i < range.Count - 1 && (subRange.Size & (BufferCache.SparseBufferAlignmentSize - 1)) != 0) + { + return false; + } + } + + return true; + } + + /// + /// Resizes the temporary buffer used for range list intersection results, if it has grown too much. + /// + private void ShrinkOverlapsBufferIfNeeded() + { + if (_virtualRangeOverlaps.Length > BufferCache.OverlapsBufferMaxCapacity) + { + Array.Resize(ref _virtualRangeOverlaps, BufferCache.OverlapsBufferMaxCapacity); + } + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 38be262a7..af682e422 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -730,8 +730,8 @@ namespace Ryujinx.Graphics.Gpu.Shader codeA ??= memoryManager.GetSpan(vertexA.Address, vertexA.Size).ToArray(); codeB ??= memoryManager.GetSpan(currentStage.Address, currentStage.Size).ToArray(); - byte[] cb1DataA = memoryManager.Physical.GetSpan(cb1DataAddress, vertexA.Cb1DataSize).ToArray(); - byte[] cb1DataB = memoryManager.Physical.GetSpan(cb1DataAddress, currentStage.Cb1DataSize).ToArray(); + byte[] cb1DataA = ReadArray(memoryManager, cb1DataAddress, vertexA.Cb1DataSize); + byte[] cb1DataB = ReadArray(memoryManager, cb1DataAddress, currentStage.Cb1DataSize); ShaderDumpPaths pathsA = default; ShaderDumpPaths pathsB = default; @@ -770,7 +770,7 @@ namespace Ryujinx.Graphics.Gpu.Shader ? channel.BufferManager.GetComputeUniformBufferAddress(1) : channel.BufferManager.GetGraphicsUniformBufferAddress(StageToStageIndex(context.Stage), 1); - byte[] cb1Data = memoryManager.Physical.GetSpan(cb1DataAddress, context.Cb1DataSize).ToArray(); + byte[] cb1Data = ReadArray(memoryManager, cb1DataAddress, context.Cb1DataSize); code ??= memoryManager.GetSpan(context.Address, context.Size).ToArray(); ShaderDumpPaths paths = dumper?.Dump(code, context.Stage == ShaderStage.Compute) ?? default; @@ -781,6 +781,23 @@ namespace Ryujinx.Graphics.Gpu.Shader return new TranslatedShader(new CachedShaderStage(program.Info, code, cb1Data), program); } + /// + /// Reads data from physical memory, returns an empty array if the memory is unmapped or size is 0. + /// + /// Memory manager with the physical memory to read from + /// Physical address of the region to read + /// Size in bytes of the data + /// An array with the data at the specified memory location + private static byte[] ReadArray(MemoryManager memoryManager, ulong address, int size) + { + if (address == MemoryManager.PteUnmapped || size == 0) + { + return Array.Empty(); + } + + return memoryManager.Physical.GetSpan(address, size).ToArray(); + } + /// /// Gets the index of a stage from a . /// diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index a41f761bd..1477b7382 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -611,7 +611,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, textureBufferIndex); - cachedTextureBuffer = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedTextureBuffer = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(bounds.Range)); cachedTextureBufferIndex = textureBufferIndex; if (samplerBufferIndex == textureBufferIndex) @@ -625,7 +625,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { ref BufferBounds bounds = ref channel.BufferManager.GetUniformBufferBounds(isCompute, stageIndex, samplerBufferIndex); - cachedSamplerBuffer = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size)); + cachedSamplerBuffer = MemoryMarshal.Cast(channel.MemoryManager.Physical.GetSpan(bounds.Range)); cachedSamplerBufferIndex = samplerBufferIndex; } diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index b3c261712..64ba4e3ee 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -57,16 +57,11 @@ namespace Ryujinx.Graphics.OpenGL ResourcePool = new ResourcePool(); } - public BufferHandle CreateBuffer(int size, BufferHandle storageHint) - { - return CreateBuffer(size, GAL.BufferAccess.Default); - } - public BufferHandle CreateBuffer(int size, GAL.BufferAccess access) { BufferCount++; - if (access == GAL.BufferAccess.FlushPersistent) + if (access.HasFlag(GAL.BufferAccess.FlushPersistent)) { BufferHandle handle = Buffer.CreatePersistent(size); @@ -80,11 +75,21 @@ namespace Ryujinx.Graphics.OpenGL } } + public BufferHandle CreateBuffer(int size, GAL.BufferAccess access, BufferHandle storageHint) + { + return CreateBuffer(size, access); + } + public BufferHandle CreateBuffer(nint pointer, int size) { throw new NotSupportedException(); } + public BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers) + { + throw new NotSupportedException(); + } + public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info) { return new Program(shaders, info.FragmentOutputMap); @@ -148,6 +153,7 @@ namespace Ryujinx.Graphics.OpenGL supportsR4G4B4A4Format: true, supportsSnormBufferTextureFormat: false, supports5BitComponentFormat: true, + supportsSparseBuffer: false, supportsBlendEquationAdvanced: HwCapabilities.SupportsBlendEquationAdvanced, supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock, supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering, diff --git a/src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs b/src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs index 7987017e9..345191f16 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs @@ -8,5 +8,6 @@ namespace Ryujinx.Graphics.Vulkan HostMapped, DeviceLocal, DeviceLocalMapped, + Sparse, } } diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs index b1887eaa1..b54ff3ab6 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Vulkan private bool _lastAccessIsWrite; - private readonly BufferAllocationType _baseType; + private BufferAllocationType _baseType; private BufferAllocationType _currentType; private bool _swapQueued; @@ -109,6 +109,22 @@ namespace Ryujinx.Graphics.Vulkan _flushLock = new ReaderWriterLockSlim(); } + public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, int size, Auto[] storageAllocations) + { + _gd = gd; + _device = device; + _waitable = new MultiFenceHolder(size); + _buffer = new Auto(new DisposableBuffer(gd.Api, device, buffer), _waitable, storageAllocations); + _bufferHandle = buffer.Handle; + Size = size; + + _baseType = BufferAllocationType.Sparse; + _currentType = BufferAllocationType.Sparse; + DesiredType = BufferAllocationType.Sparse; + + _flushLock = new ReaderWriterLockSlim(); + } + public bool TryBackingSwap(ref CommandBufferScoped? cbs) { if (_swapQueued && DesiredType != _currentType) @@ -122,7 +138,7 @@ namespace Ryujinx.Graphics.Vulkan var currentBuffer = _buffer; IntPtr currentMap = _map; - (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, _currentType); + (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, false, _currentType); if (buffer.Handle != 0) { @@ -253,6 +269,14 @@ namespace Ryujinx.Graphics.Vulkan } } + public void Pin() + { + if (_baseType == BufferAllocationType.Auto) + { + _baseType = _currentType; + } + } + public unsafe Auto CreateView(VkFormat format, int offset, int size, Action invalidateView) { var bufferViewCreateInfo = new BufferViewCreateInfo @@ -506,6 +530,16 @@ namespace Ryujinx.Graphics.Vulkan } } + public Auto GetAllocation() + { + return _allocationAuto; + } + + public (DeviceMemory, ulong) GetDeviceMemoryAndOffset() + { + return (_allocation.Memory, _allocation.Offset); + } + public void SignalWrite(int offset, int size) { ConsiderBackingSwap(); @@ -1072,7 +1106,7 @@ namespace Ryujinx.Graphics.Vulkan } else { - _allocationAuto.Dispose(); + _allocationAuto?.Dispose(); } _flushLock.EnterWriteLock(); diff --git a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs index 20e003385..e9ac98847 100644 --- a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs @@ -96,25 +96,131 @@ namespace Ryujinx.Graphics.Vulkan return Unsafe.As(ref handle64); } + public unsafe BufferHandle CreateSparse(VulkanRenderer gd, ReadOnlySpan storageBuffers) + { + var usage = DefaultBufferUsageFlags; + + if (gd.Capabilities.SupportsIndirectParameters) + { + usage |= BufferUsageFlags.IndirectBufferBit; + } + + ulong size = 0; + + foreach (BufferRange range in storageBuffers) + { + size += (ulong)range.Size; + } + + var bufferCreateInfo = new BufferCreateInfo() + { + SType = StructureType.BufferCreateInfo, + Size = size, + Usage = usage, + SharingMode = SharingMode.Exclusive, + Flags = BufferCreateFlags.SparseBindingBit | BufferCreateFlags.SparseAliasedBit + }; + + gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); + + var memoryBinds = new SparseMemoryBind[storageBuffers.Length]; + var storageAllocations = new Auto[storageBuffers.Length]; + int storageAllocationsCount = 0; + + ulong dstOffset = 0; + + for (int index = 0; index < storageBuffers.Length; index++) + { + BufferRange range = storageBuffers[index]; + + if (TryGetBuffer(range.Handle, out var existingHolder)) + { + // Since this buffer now also owns the memory from the referenced buffer, + // we pin it to ensure the memory location will not change. + existingHolder.Pin(); + + (var memory, var offset) = existingHolder.GetDeviceMemoryAndOffset(); + + memoryBinds[index] = new SparseMemoryBind() + { + ResourceOffset = dstOffset, + Size = (ulong)range.Size, + Memory = memory, + MemoryOffset = offset + (ulong)range.Offset, + Flags = SparseMemoryBindFlags.None + }; + + storageAllocations[storageAllocationsCount++] = existingHolder.GetAllocation(); + } + else + { + memoryBinds[index] = new SparseMemoryBind() + { + ResourceOffset = dstOffset, + Size = (ulong)range.Size, + Memory = default, + MemoryOffset = 0UL, + Flags = SparseMemoryBindFlags.None + }; + } + + dstOffset += (ulong)range.Size; + } + + if (storageAllocations.Length != storageAllocationsCount) + { + Array.Resize(ref storageAllocations, storageAllocationsCount); + } + + fixed (SparseMemoryBind* pMemoryBinds = memoryBinds) + { + SparseBufferMemoryBindInfo bufferBind = new SparseBufferMemoryBindInfo() + { + Buffer = buffer, + BindCount = (uint)memoryBinds.Length, + PBinds = pMemoryBinds + }; + + BindSparseInfo bindSparseInfo = new BindSparseInfo() + { + SType = StructureType.BindSparseInfo, + BufferBindCount = 1, + PBufferBinds = &bufferBind + }; + + gd.Api.QueueBindSparse(gd.Queue, 1, bindSparseInfo, default).ThrowOnError(); + } + + var holder = new BufferHolder(gd, _device, buffer, (int)size, storageAllocations); + + BufferCount++; + + ulong handle64 = (uint)_buffers.Add(holder); + + return Unsafe.As(ref handle64); + } + public BufferHandle CreateWithHandle( VulkanRenderer gd, int size, + bool sparseCompatible = false, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default, bool forceMirrors = false) { - return CreateWithHandle(gd, size, out _, baseType, storageHint, forceMirrors); + return CreateWithHandle(gd, size, out _, sparseCompatible, baseType, storageHint, forceMirrors); } public BufferHandle CreateWithHandle( VulkanRenderer gd, int size, out BufferHolder holder, + bool sparseCompatible = false, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default, bool forceMirrors = false) { - holder = Create(gd, size, baseType: baseType, storageHint: storageHint); + holder = Create(gd, size, forConditionalRendering: false, sparseCompatible, baseType, storageHint); if (holder == null) { return BufferHandle.Null; @@ -163,6 +269,7 @@ namespace Ryujinx.Graphics.Vulkan int size, BufferAllocationType type, bool forConditionalRendering = false, + bool sparseCompatible = false, BufferAllocationType fallbackType = BufferAllocationType.Auto) { var usage = DefaultBufferUsageFlags; @@ -187,6 +294,11 @@ namespace Ryujinx.Graphics.Vulkan gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements); + if (sparseCompatible) + { + requirements.Alignment = Math.Max(requirements.Alignment, Constants.SparseBufferAlignment); + } + MemoryAllocation allocation; do @@ -227,6 +339,7 @@ namespace Ryujinx.Graphics.Vulkan VulkanRenderer gd, int size, bool forConditionalRendering = false, + bool sparseCompatible = false, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default) { @@ -255,7 +368,7 @@ namespace Ryujinx.Graphics.Vulkan } (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = - CreateBacking(gd, size, type, forConditionalRendering); + CreateBacking(gd, size, type, forConditionalRendering, sparseCompatible); if (buffer.Handle != 0) { diff --git a/src/Ryujinx.Graphics.Vulkan/Constants.cs b/src/Ryujinx.Graphics.Vulkan/Constants.cs index 1bf8c5805..cd6122112 100644 --- a/src/Ryujinx.Graphics.Vulkan/Constants.cs +++ b/src/Ryujinx.Graphics.Vulkan/Constants.cs @@ -16,5 +16,7 @@ namespace Ryujinx.Graphics.Vulkan public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages; public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages; public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages; + + public const ulong SparseBufferAlignment = 0x10000; } } diff --git a/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs b/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs index b7787601d..e10027057 100644 --- a/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs +++ b/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs @@ -424,12 +424,12 @@ namespace Ryujinx.Graphics.Vulkan public static BufferAllocationType Convert(this BufferAccess access) { - return access switch + if (access.HasFlag(BufferAccess.FlushPersistent) || access.HasFlag(BufferAccess.Stream)) { - BufferAccess.FlushPersistent => BufferAllocationType.HostMapped, - BufferAccess.Stream => BufferAllocationType.HostMapped, - _ => BufferAllocationType.Auto, - }; + return BufferAllocationType.HostMapped; + } + + return BufferAllocationType.Auto; } private static T2 LogInvalidAndReturn(T1 value, string name, T2 defaultValue = default) diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 7240bcade..893ecf1a9 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -392,6 +392,8 @@ namespace Ryujinx.Graphics.Vulkan LoadFeatures(maxQueueCount, queueFamilyIndex); + QueueFamilyIndex = queueFamilyIndex; + _window = new Window(this, _surface, _physicalDevice.PhysicalDevice, _device); _initialized = true; @@ -399,12 +401,12 @@ namespace Ryujinx.Graphics.Vulkan public BufferHandle CreateBuffer(int size, BufferAccess access) { - return BufferManager.CreateWithHandle(this, size, access.Convert(), default, access == BufferAccess.Stream); + return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), default, access == BufferAccess.Stream); } - public BufferHandle CreateBuffer(int size, BufferHandle storageHint) + public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint) { - return BufferManager.CreateWithHandle(this, size, BufferAllocationType.Auto, storageHint); + return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), storageHint); } public BufferHandle CreateBuffer(nint pointer, int size) @@ -412,6 +414,11 @@ namespace Ryujinx.Graphics.Vulkan return BufferManager.CreateHostImported(this, pointer, size); } + public BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers) + { + return BufferManager.CreateSparse(this, storageBuffers); + } + public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info) { bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute; @@ -571,6 +578,7 @@ namespace Ryujinx.Graphics.Vulkan Api.GetPhysicalDeviceFeatures2(_physicalDevice.PhysicalDevice, &features2); var limits = _physicalDevice.PhysicalDeviceProperties.Limits; + var mainQueueProperties = _physicalDevice.QueueFamilyProperties[QueueFamilyIndex]; return new Capabilities( api: TargetApi.Vulkan, @@ -590,6 +598,7 @@ namespace Ryujinx.Graphics.Vulkan supportsR4G4B4A4Format: supportsR4G4B4A4Format, supportsSnormBufferTextureFormat: true, supports5BitComponentFormat: supports5BitComponentFormat, + supportsSparseBuffer: features2.Features.SparseBinding && mainQueueProperties.QueueFlags.HasFlag(QueueFlags.SparseBindingBit), supportsBlendEquationAdvanced: Capabilities.SupportsBlendEquationAdvanced, supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock, supportsFragmentShaderOrderingIntel: false, diff --git a/src/Ryujinx.Memory/Range/MultiRange.cs b/src/Ryujinx.Memory/Range/MultiRange.cs index 798bc9119..093e21903 100644 --- a/src/Ryujinx.Memory/Range/MultiRange.cs +++ b/src/Ryujinx.Memory/Range/MultiRange.cs @@ -15,6 +15,11 @@ namespace Ryujinx.Memory.Range private bool HasSingleRange => _ranges == null; + /// + /// Indicates that the range is fully unmapped. + /// + public bool IsUnmapped => HasSingleRange && _singleRange.Address == InvalidAddress; + /// /// Total of physical sub-ranges on the virtual memory region. /// @@ -38,8 +43,18 @@ namespace Ryujinx.Memory.Range /// is null public MultiRange(MemoryRange[] ranges) { - _singleRange = MemoryRange.Empty; - _ranges = ranges ?? throw new ArgumentNullException(nameof(ranges)); + ArgumentNullException.ThrowIfNull(ranges); + + if (ranges.Length == 1) + { + _singleRange = ranges[0]; + _ranges = null; + } + else + { + _singleRange = MemoryRange.Empty; + _ranges = ranges; + } } /// @@ -91,7 +106,7 @@ namespace Ryujinx.Memory.Range offset -= range.Size; } - return new MultiRange(ranges.ToArray()); + return ranges.Count == 1 ? new MultiRange(ranges[0].Address, ranges[0].Size) : new MultiRange(ranges.ToArray()); } }