diff --git a/Ryujinx.Graphics.Vulkan/Auto.cs b/Ryujinx.Graphics.Vulkan/Auto.cs index d6b1aaab4..e5f64df8e 100644 --- a/Ryujinx.Graphics.Vulkan/Auto.cs +++ b/Ryujinx.Graphics.Vulkan/Auto.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Threading; namespace Ryujinx.Graphics.Vulkan { @@ -66,6 +67,16 @@ namespace Ryujinx.Graphics.Vulkan return _value; } + public bool HasCommandBufferDependency(CommandBufferScoped cbs) + { + return _cbOwnership.IsSet(cbs.CommandBufferIndex); + } + + public bool HasRentedCommandBufferDependency(CommandBufferPool cbp) + { + return _cbOwnership.AnySet(); + } + public void AddCommandBufferDependencies(CommandBufferScoped cbs) { // We don't want to add a reference to this object to the command buffer @@ -94,11 +105,11 @@ namespace Ryujinx.Graphics.Vulkan public void IncrementReferenceCount() { - if (_referenceCount == 0) + if (Interlocked.Increment(ref _referenceCount) == 1) { + Interlocked.Decrement(ref _referenceCount); throw new Exception("Attempted to inc ref of dead object."); } - _referenceCount++; } public void DecrementReferenceCount(int cbIndex) @@ -109,7 +120,7 @@ namespace Ryujinx.Graphics.Vulkan public void DecrementReferenceCount() { - if (--_referenceCount == 0) + if (Interlocked.Decrement(ref _referenceCount) == 0) { _value.Dispose(); _value = default; diff --git a/Ryujinx.Graphics.Vulkan/BackgroundResources.cs b/Ryujinx.Graphics.Vulkan/BackgroundResources.cs new file mode 100644 index 000000000..cd9c91b14 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/BackgroundResources.cs @@ -0,0 +1,107 @@ +using System.Threading; +using System.Collections.Generic; +using System; +using Silk.NET.Vulkan; + +namespace Ryujinx.Graphics.Vulkan +{ + class BackgroundResource : IDisposable + { + private VulkanGraphicsDevice _gd; + private Device _device; + + private CommandBufferPool _pool; + private PersistentFlushBuffer _flushBuffer; + + public BackgroundResource(VulkanGraphicsDevice gd, Device device) + { + _gd = gd; + _device = device; + } + + public CommandBufferPool GetPool() + { + if (_pool == null) + { + _pool = new CommandBufferPool(_gd.Api, _device, _gd.BackgroundQueue, _gd.QueueFamilyIndex, isLight: true); + } + + return _pool; + } + + public PersistentFlushBuffer GetFlushBuffer() + { + if (_flushBuffer == null) + { + _flushBuffer = new PersistentFlushBuffer(_gd); + } + + return _flushBuffer; + } + + public void Dispose() + { + _pool?.Dispose(); + _flushBuffer?.Dispose(); + } + } + + class BackgroundResources : IDisposable + { + private VulkanGraphicsDevice _gd; + private Device _device; + + private Dictionary _resources; + + public BackgroundResources(VulkanGraphicsDevice gd, Device device) + { + _gd = gd; + _device = device; + + _resources = new Dictionary(); + } + + private void Cleanup() + { + foreach (KeyValuePair tuple in _resources) + { + if (!tuple.Key.IsAlive) + { + tuple.Value.Dispose(); + _resources.Remove(tuple.Key); + } + } + } + + public BackgroundResource Get() + { + Thread thread = Thread.CurrentThread; + + lock (_resources) + { + BackgroundResource resource; + if (!_resources.TryGetValue(thread, out resource)) + { + Cleanup(); + + resource = new BackgroundResource(_gd, _device); + + _resources[thread] = resource; + } + + return resource; + } + } + + public void Dispose() + { + lock (_resources) + { + foreach (var resource in _resources.Values) + { + resource.Dispose(); + } + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/BitMap.cs b/Ryujinx.Graphics.Vulkan/BitMap.cs index 181d6636e..19d852e62 100644 --- a/Ryujinx.Graphics.Vulkan/BitMap.cs +++ b/Ryujinx.Graphics.Vulkan/BitMap.cs @@ -12,6 +12,29 @@ _masks = new long[(count + IntMask) / IntSize]; } + public bool AnySet() + { + for (int i = 0; i < _masks.Length; i++) + { + if (_masks[i] != 0) + { + return true; + } + } + + return false; + } + + public bool IsSet(int bit) + { + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + return (_masks[wordIndex] & wordMask) != 0; + } + public bool Set(int bit) { int wordIndex = bit / IntSize; diff --git a/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/Ryujinx.Graphics.Vulkan/BufferHolder.cs index 2f1d2b489..e171d3c21 100644 --- a/Ryujinx.Graphics.Vulkan/BufferHolder.cs +++ b/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -129,7 +129,32 @@ namespace Ryujinx.Graphics.Vulkan public unsafe ReadOnlySpan GetData(int offset, int size) { - return GetDataStorage(offset, size); + if (_map != IntPtr.Zero) + { + return GetDataStorage(offset, size); + } + else + { + BackgroundResource resource = _gd.BackgroundResources.Get(); + + if (_gd.CommandBufferPool.OwnedByCurrentThread) + { + _gd.FlushAllCommands(); + + return resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size); + } + else if (_gd.BackgroundQueue.Handle != 0) + { + lock (_gd.BackgroundQueueLock) + { + return resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size); + } + } + else + { + return new byte[size]; + } + } } public unsafe Span GetDataStorage(int offset, int size) @@ -152,28 +177,37 @@ namespace Ryujinx.Graphics.Vulkan return; } - bool needsFlush = _gd.CommandBufferPool.HasWaitableOnRentedCommandBuffer(_waitable, offset, dataSize); - bool needsWait = needsFlush || MayWait(offset, dataSize); + if (_map != IntPtr.Zero) + { + // If persistently mapped, set the data directly if the buffer is not currently in use. + //bool needsFlush = _gd.CommandBufferPool.HasWaitableOnRentedCommandBuffer(_waitable, offset, dataSize); + bool needsFlush = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);// (_waitable, offset, dataSize); + + if (!needsFlush) + { + WaitForFences(offset, dataSize); + + data.Slice(0, dataSize).CopyTo(new Span((void*)(_map + offset), dataSize)); + + return; + } + } + + if (cbs != null && !_buffer.HasCommandBufferDependency(cbs.Value)) + { + // If the buffer hasn't been used on the command buffer yet, try to preload the data. + // This avoids ending and beginning render passes on each buffer data upload. + + cbs = _gd.PipelineInternal.GetPreloadCommandBuffer(); + endRenderPass = null; + } if (cbs == null || - !needsWait || !VulkanConfiguration.UseFastBufferUpdates || + data.Length > MaxUpdateBufferSize || !TryPushData(cbs.Value, endRenderPass, offset, data)) { - // Some pending command might access the buffer, - // so we need to ensure they are all submited to the GPU before waiting. - // The flush below forces the submission of all pending commands. - if (needsFlush) - { - _gd.FlushAllCommands(); - } - - WaitForFences(offset, dataSize); - - if (_map != IntPtr.Zero) - { - data.Slice(0, dataSize).CopyTo(new Span((void*)(_map + offset), dataSize)); - } + _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data); } } @@ -189,6 +223,10 @@ namespace Ryujinx.Graphics.Vulkan { data.Slice(0, dataSize).CopyTo(new Span((void*)(_map + offset), dataSize)); } + else + { + _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, null, null, this, offset, data); + } } public void SetDataInline(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan data) @@ -206,7 +244,7 @@ namespace Ryujinx.Graphics.Vulkan return false; } - endRenderPass(); + endRenderPass?.Invoke(); var dstBuffer = GetBuffer(cbs.CommandBuffer, true).Get(cbs, dstOffset, data.Length).Value; diff --git a/Ryujinx.Graphics.Vulkan/BufferManager.cs b/Ryujinx.Graphics.Vulkan/BufferManager.cs index cf7a90456..8315ba4a0 100644 --- a/Ryujinx.Graphics.Vulkan/BufferManager.cs +++ b/Ryujinx.Graphics.Vulkan/BufferManager.cs @@ -15,6 +15,14 @@ namespace Ryujinx.Graphics.Vulkan MemoryPropertyFlags.MemoryPropertyHostCoherentBit | MemoryPropertyFlags.MemoryPropertyHostCachedBit; + private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags = + MemoryPropertyFlags.MemoryPropertyDeviceLocalBit; + + private const MemoryPropertyFlags FlushableDeviceLocalBufferMemoryFlags = + MemoryPropertyFlags.MemoryPropertyHostVisibleBit | + MemoryPropertyFlags.MemoryPropertyHostCoherentBit | + MemoryPropertyFlags.MemoryPropertyDeviceLocalBit; + private const BufferUsageFlags DefaultBufferUsageFlags = BufferUsageFlags.BufferUsageTransferSrcBit | BufferUsageFlags.BufferUsageTransferDstBit | @@ -41,9 +49,9 @@ namespace Ryujinx.Graphics.Vulkan StagingBuffer = new StagingBuffer(gd, this); } - public BufferHandle CreateWithHandle(VulkanGraphicsDevice gd, int size) + public BufferHandle CreateWithHandle(VulkanGraphicsDevice gd, int size, bool deviceLocal) { - var holder = Create(gd, size); + var holder = Create(gd, size, deviceLocal: deviceLocal); if (holder == null) { return BufferHandle.Null; @@ -58,7 +66,7 @@ namespace Ryujinx.Graphics.Vulkan return handle; } - public unsafe BufferHolder Create(VulkanGraphicsDevice gd, int size, bool forConditionalRendering = false) + public unsafe BufferHolder Create(VulkanGraphicsDevice gd, int size, bool forConditionalRendering = false, bool deviceLocal = false) { var usage = DefaultBufferUsageFlags; @@ -81,7 +89,10 @@ namespace Ryujinx.Graphics.Vulkan gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements); - var allocation = gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, requirements, DefaultBufferMemoryFlags); + + var allocateFlags = deviceLocal ? DeviceLocalBufferMemoryFlags : DefaultBufferMemoryFlags; + + var allocation = gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, requirements, allocateFlags); if (allocation.Memory.Handle == 0UL) { diff --git a/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs b/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs index fdc27e9a0..b9fcc6ba9 100644 --- a/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs +++ b/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Vulkan { class CommandBufferPool : IDisposable { - public const int MaxCommandBuffers = 8; + public const int MaxCommandBuffers = 16; private int _totalCommandBuffers; private int _totalCommandBuffersMask; @@ -19,12 +19,15 @@ namespace Ryujinx.Graphics.Vulkan private readonly CommandPool _pool; private readonly Thread _owner; + public int PerFrame = 0; + public bool OwnedByCurrentThread => _owner == Thread.CurrentThread; private struct ReservedCommandBuffer { public bool InUse; public bool InConsumption; + public bool Active; public CommandBuffer CommandBuffer; public FenceHolder Fence; public SemaphoreHolder Semaphore; @@ -48,6 +51,8 @@ namespace Ryujinx.Graphics.Vulkan Dependants = new List(); Waitables = new HashSet(); Dependencies = new HashSet(); + + Active = true; // Fence should be refreshed. } } @@ -120,6 +125,12 @@ namespace Ryujinx.Graphics.Vulkan entry.Waitables.Add(waitable); } + public bool HasWaitable(int cbIndex, MultiFenceHolder waitable) + { + ref var entry = ref _commandBuffers[cbIndex]; + return entry.Waitables.Contains(waitable); + } + public bool HasWaitableOnRentedCommandBuffer(MultiFenceHolder waitable, int offset, int size) { lock (_commandBuffers) @@ -140,11 +151,47 @@ namespace Ryujinx.Graphics.Vulkan return false; } + public bool IsFenceOnRentedCommandBuffer(FenceHolder fence) + { + lock (_commandBuffers) + { + for (int i = 0; i < _totalCommandBuffers; i++) + { + ref var entry = ref _commandBuffers[i]; + + if (entry.InUse && entry.Fence == fence) + { + return true; + } + } + } + + return false; + } + public FenceHolder GetFence(int cbIndex) { return _commandBuffers[cbIndex].Fence; } + private void CheckConsumption(int startAt) + { + int wrapBefore = _totalCommandBuffers + 1; + int index = (startAt + wrapBefore) % _totalCommandBuffers; + + for (int i = 0; i < _totalCommandBuffers - 1; i++) + { + ref var entry = ref _commandBuffers[index]; + + if (!entry.InUse && entry.InConsumption && entry.Fence.IsSignaled()) + { + WaitAndDecrementRef(index); + } + + index = (index + wrapBefore) % _totalCommandBuffers; + } + } + public CommandBufferScoped ReturnAndRent(CommandBufferScoped cbs) { Return(cbs); @@ -153,8 +200,11 @@ namespace Ryujinx.Graphics.Vulkan public CommandBufferScoped Rent() { + PerFrame++; lock (_commandBuffers) { + CheckConsumption(_cursor); + for (int i = 0; i < _totalCommandBuffers; i++) { int currentIndex = _cursor; @@ -165,6 +215,7 @@ namespace Ryujinx.Graphics.Vulkan { WaitAndDecrementRef(currentIndex); entry.InUse = true; + entry.Active = true; var commandBufferBeginInfo = new CommandBufferBeginInfo() { @@ -241,6 +292,13 @@ namespace Ryujinx.Graphics.Vulkan { ref var entry = ref _commandBuffers[cbIndex]; + if (!entry.Active) + { + return; + } + + entry.Active = false; + if (entry.InConsumption) { entry.Fence.Wait(); diff --git a/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs b/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs index 372950a88..702d41ebd 100644 --- a/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs +++ b/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs @@ -31,6 +31,11 @@ namespace Ryujinx.Graphics.Vulkan _pool.AddDependency(CommandBufferIndex, dependencyCbs); } + public bool HasWaitable(MultiFenceHolder waitable) + { + return _pool.HasWaitable(CommandBufferIndex, waitable); + } + public FenceHolder GetFence() { return _pool.GetFence(CommandBufferIndex); diff --git a/Ryujinx.Graphics.Vulkan/HelperShader.cs b/Ryujinx.Graphics.Vulkan/HelperShader.cs index 5623aa186..f7f0e77ba 100644 --- a/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -169,7 +169,7 @@ void main() region[3] = temp; } - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize); + var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false); gd.BufferManager.SetData(bufferHandle, 0, region); @@ -251,7 +251,7 @@ void main() region[3] = temp; } - var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize); + var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false); gd.BufferManager.SetData(bufferHandle, 0, region); diff --git a/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs b/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs new file mode 100644 index 000000000..8e035aa32 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs @@ -0,0 +1,73 @@ +using System; + +namespace Ryujinx.Graphics.Vulkan +{ + internal class PersistentFlushBuffer : IDisposable + { + private VulkanGraphicsDevice _gd; + + private BufferHolder _flushStorage; + + public PersistentFlushBuffer(VulkanGraphicsDevice gd) + { + _gd = gd; + } + + private BufferHolder ResizeIfNeeded(int size) + { + var flushStorage = _flushStorage; + + if (flushStorage == null || size > _flushStorage.Size) + { + if (flushStorage != null) + { + flushStorage.Dispose(); + } + + flushStorage = _gd.BufferManager.Create(_gd, size); + _flushStorage = flushStorage; + } + + return flushStorage; + } + + public Span GetBufferData(CommandBufferPool cbp, BufferHolder buffer, int offset, int size) + { + var flushStorage = ResizeIfNeeded(size); + + using (var cbs = cbp.Rent()) + { + var srcBuffer = buffer.GetBuffer(cbs.CommandBuffer); + var dstBuffer = flushStorage.GetBuffer(cbs.CommandBuffer); + + BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, 0, size); + } + + flushStorage.WaitForFences(); + return flushStorage.GetDataStorage(0, size); + } + + public Span GetTextureData(CommandBufferPool cbp, TextureView view, int size) + { + GAL.TextureCreateInfo info = view.Info; + + var flushStorage = ResizeIfNeeded(size); + + using (var cbs = cbp.Rent()) + { + var buffer = flushStorage.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; + var image = view.GetImage().Get(cbs).Value; + + view.CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, info.GetLayers(), info.Levels, singleSlice: false); + } + + flushStorage.WaitForFences(); + return flushStorage.GetDataStorage(0, size); + } + + public void Dispose() + { + _flushStorage.Dispose(); + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 007cb9c83..01a79e131 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -34,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan private PipelineCache _pipelineCache; protected CommandBufferScoped Cbs; + protected CommandBufferScoped? PreloadCbs; protected CommandBuffer CommandBuffer; public CommandBufferScoped CurrentCommandBuffer => Cbs; diff --git a/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/Ryujinx.Graphics.Vulkan/PipelineFull.cs index 545e8fc0c..0f092bc0c 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -225,6 +225,16 @@ namespace Ryujinx.Graphics.Vulkan } } + public CommandBufferScoped GetPreloadCommandBuffer() + { + if (PreloadCbs == null) + { + PreloadCbs = Gd.CommandBufferPool.Rent(); + } + + return PreloadCbs.Value; + } + public void FlushCommandsImpl([System.Runtime.CompilerServices.CallerMemberName] string caller = "") { // System.Console.WriteLine("flush by " + caller); @@ -237,6 +247,12 @@ namespace Ryujinx.Graphics.Vulkan Gd.Api.CmdEndQuery(CommandBuffer, queryPool, 0); } + if (PreloadCbs != null) + { + PreloadCbs.Value.Dispose(); + PreloadCbs = null; + } + CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer; // Restore per-command buffer state. diff --git a/Ryujinx.Graphics.Vulkan/StagingBuffer.cs b/Ryujinx.Graphics.Vulkan/StagingBuffer.cs index 0942b381a..3231fd0dc 100644 --- a/Ryujinx.Graphics.Vulkan/StagingBuffer.cs +++ b/Ryujinx.Graphics.Vulkan/StagingBuffer.cs @@ -37,25 +37,55 @@ namespace Ryujinx.Graphics.Vulkan _freeSize = BufferSize; } - public unsafe bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) + public unsafe void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) { - if (data.Length > BufferSize) - { - return false; - } + bool isRender = cbs != null; + CommandBufferScoped scoped = cbs ?? cbp.Rent(); - if (_freeSize < data.Length) - { - FreeCompleted(); + // Must push all data to the buffer. If it can't fit, split it up. + endRenderPass?.Invoke(); + + while (data.Length > 0) + { if (_freeSize < data.Length) { - return false; + FreeCompleted(); } + + while (_freeSize == 0) + { + if (!WaitFreeCompleted(cbp)) + { + if (isRender) + { + _gd.FlushAllCommands(); + scoped = cbp.Rent(); + isRender = false; + } + else + { + scoped = cbp.ReturnAndRent(scoped); + } + } + } + + int chunkSize = Math.Min(_freeSize, data.Length); + + PushDataImpl(scoped, dst, dstOffset, data.Slice(0, chunkSize)); + + dstOffset += chunkSize; + data = data.Slice(chunkSize); } - endRenderPass(); + if (!isRender) + { + scoped.Dispose(); + } + } + private void PushDataImpl(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan data) + { var srcBuffer = _buffer.GetBuffer(); var dstBuffer = dst.GetBuffer(); @@ -81,14 +111,61 @@ namespace Ryujinx.Graphics.Vulkan Debug.Assert(_freeSize >= 0); _pendingCopies.Enqueue(new PendingCopy(cbs.GetFence(), data.Length)); + } + + public unsafe bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan data) + { + if (data.Length > BufferSize) + { + return false; + } + + if (_freeSize < data.Length) + { + FreeCompleted(); + + if (_freeSize < data.Length) + { + return false; + } + } + + endRenderPass(); + + PushDataImpl(cbs, dst, dstOffset, data); + + return true; + } + + private bool WaitFreeCompleted(CommandBufferPool cbp) + { + if (_pendingCopies.TryPeek(out var pc)) + { + if (!pc.Fence.IsSignaled()) + { + if (cbp.IsFenceOnRentedCommandBuffer(pc.Fence)) + { + return false; + } + + pc.Fence.Wait(); + } + + var dequeued = _pendingCopies.Dequeue(); + Debug.Assert(dequeued.Fence == pc.Fence); + _freeSize += pc.Size; + pc.Fence.Put(); + } return true; } private void FreeCompleted() { - while (_pendingCopies.TryPeek(out var pc) && pc.Fence.IsSignaled()) + FenceHolder signalledFence = null; + while (_pendingCopies.TryPeek(out var pc) && (pc.Fence == signalledFence || pc.Fence.IsSignaled())) { + signalledFence = pc.Fence; // Already checked - don't need to do it again. var dequeued = _pendingCopies.Dequeue(); Debug.Assert(dequeued.Fence == pc.Fence); _freeSize += pc.Size; diff --git a/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/Ryujinx.Graphics.Vulkan/TextureStorage.cs index 4e351bcd6..406dfbe39 100644 --- a/Ryujinx.Graphics.Vulkan/TextureStorage.cs +++ b/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -228,9 +228,41 @@ namespace Ryujinx.Graphics.Vulkan return _image; } + public bool HasCommandBufferDependency(CommandBufferScoped cbs) + { + if (_foreignAllocationAuto != null) + { + return _foreignAllocationAuto.HasCommandBufferDependency(cbs); + } + else if (_allocationAuto != null) + { + return _allocationAuto.HasCommandBufferDependency(cbs); + } + + return false; + } + private unsafe void InitialTransition(ImageLayout srcLayout, ImageLayout dstLayout) { - using var cbs = _gd.CommandBufferPool.Rent(); + CommandBufferScoped cbs; + bool useTempCbs = !_gd.CommandBufferPool.OwnedByCurrentThread; + + if (useTempCbs) + { + cbs = _gd.BackgroundResources.Get().GetPool().Rent(); + } + else + { + if (_gd.PipelineInternal != null) + { + cbs = _gd.PipelineInternal.GetPreloadCommandBuffer(); + } + else + { + cbs = _gd.CommandBufferPool.Rent(); + useTempCbs = true; + } + } var aspectFlags = _info.Format.ConvertAspectFlags(); @@ -260,6 +292,11 @@ namespace Ryujinx.Graphics.Vulkan null, 1, barrier); + + if (useTempCbs) + { + cbs.Dispose(); + } } private static SampleCountFlags ConvertToSampleCountFlags(uint samples) diff --git a/Ryujinx.Graphics.Vulkan/TextureView.cs b/Ryujinx.Graphics.Vulkan/TextureView.cs index 8c6dbc604..e8928bd13 100644 --- a/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -237,12 +237,7 @@ namespace Ryujinx.Graphics.Vulkan { lock (_gd.BackgroundQueueLock) { - using var cbp = new CommandBufferPool( - _gd.Api, - _device, - _gd.BackgroundQueue, - _gd.QueueFamilyIndex, - isLight: true); + var cbp = _gd.BackgroundResources.Get().GetPool(); using var cbs = cbp.Rent(); @@ -426,8 +421,8 @@ namespace Ryujinx.Graphics.Vulkan dstSize += dstTemp.Info.GetMipSize2D(l); } - using var srcTempBuffer = gd.BufferManager.Create(gd, srcSize); - using var dstTempBuffer = gd.BufferManager.Create(gd, dstSize); + using var srcTempBuffer = gd.BufferManager.Create(gd, srcSize, deviceLocal: true); + using var dstTempBuffer = gd.BufferManager.Create(gd, dstSize, deviceLocal: true); src.Storage.CopyFromOrToBuffer( cbs.CommandBuffer, @@ -669,30 +664,25 @@ namespace Ryujinx.Graphics.Vulkan bufferHolder.WaitForFences(); byte[] bitmap = new byte[size]; - GetDataFromBuffer(bufferHolder.GetDataStorage(0, size)).CopyTo(bitmap); + GetDataFromBuffer(bufferHolder.GetDataStorage(0, size), size, Span.Empty).CopyTo(bitmap); return bitmap; } public ReadOnlySpan GetData() { + BackgroundResource resources = _gd.BackgroundResources.Get(); + if (_gd.CommandBufferPool.OwnedByCurrentThread) { _gd.FlushAllCommands(); - return GetData(_gd.CommandBufferPool); + return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer()); } else if (_gd.BackgroundQueue.Handle != 0) { lock (_gd.BackgroundQueueLock) { - using var cbp = new CommandBufferPool( - _gd.Api, - _device, - _gd.BackgroundQueue, - _gd.QueueFamilyIndex, - isLight: true); - - return GetData(cbp); + return GetData(resources.GetPool(), resources.GetFlushBuffer()); } } else @@ -714,46 +704,19 @@ namespace Ryujinx.Graphics.Vulkan throw new NotImplementedException(); } - private ReadOnlySpan GetData(CommandBufferPool cbp) + private ReadOnlySpan GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer) { - int size; - var bufferHolder = _flushStorage; + int size = 0; - if (bufferHolder == null) + for (int level = 0; level < Info.Levels; level++) { - size = 0; - - for (int level = 0; level < Info.Levels; level++) - { - size += Info.GetMipSize(level); - } - - size = GetBufferDataLength(size); - - bufferHolder = _gd.BufferManager.Create(_gd, size); - - var existingStorage = Interlocked.CompareExchange(ref _flushStorage, bufferHolder, null); - if (existingStorage != null) - { - bufferHolder.Dispose(); - bufferHolder = existingStorage; - } - } - else - { - size = bufferHolder.Size; + size += Info.GetMipSize(level); } - using (var cbs = cbp.Rent()) - { - var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; - var image = GetImage().Get(cbs).Value; + size = GetBufferDataLength(size); - CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false); - } - - bufferHolder.WaitForFences(); - return GetDataFromBuffer(bufferHolder.GetDataStorage(0, size)); + Span result = flushBuffer.GetTextureData(cbp, this, size); + return GetDataFromBuffer(result, size, result); } public void SetData(ReadOnlySpan data) @@ -772,12 +735,24 @@ namespace Ryujinx.Graphics.Vulkan using var bufferHolder = _gd.BufferManager.Create(_gd, bufferDataLength); - using var cbs = _gd.CommandBufferPool.Rent(); + Auto imageAuto = GetImage(); + + // Load texture data inline if the texture has been used on the current command buffer. + + bool loadInline = Storage.HasCommandBufferDependency(_gd.PipelineInternal.CurrentCommandBuffer); + + var cbs = loadInline ? _gd.PipelineInternal.CurrentCommandBuffer : _gd.PipelineInternal.GetPreloadCommandBuffer(); + + if (loadInline) + { + _gd.PipelineInternal.EndRenderPass(); + Common.Logging.Logger.Error?.PrintMsg(Common.Logging.LogClass.Gpu, "Loaded inline!"); + } CopyDataToBuffer(bufferHolder.GetDataStorage(0, bufferDataLength), data); var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value; - var image = GetImage().Get(cbs).Value; + var image = imageAuto.Get(cbs).Value; CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice); } @@ -813,11 +788,15 @@ namespace Ryujinx.Graphics.Vulkan input.CopyTo(storage); } - private ReadOnlySpan GetDataFromBuffer(ReadOnlySpan storage) + private ReadOnlySpan GetDataFromBuffer(ReadOnlySpan storage, int size, Span output) { if (NeedsD24S8Conversion()) { - byte[] output = new byte[GetBufferDataLength(storage.Length)]; + if (output.IsEmpty) + { + output = new byte[GetBufferDataLength(size)]; + } + FormatConverter.ConvertD32FS8ToD24S8(output, storage); return output; } @@ -830,7 +809,7 @@ namespace Ryujinx.Graphics.Vulkan return Info.Format == GAL.Format.D24UnormS8Uint && VkFormat == VkFormat.D32SfloatS8Uint; } - private void CopyFromOrToBuffer( + public void CopyFromOrToBuffer( CommandBuffer commandBuffer, VkBuffer buffer, Image image, diff --git a/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs b/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs index 00ffb327b..88a6097e0 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanGraphicsDevice.cs @@ -49,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan internal CommandBufferPool CommandBufferPool { get; private set; } internal DescriptorSetManager DescriptorSetManager { get; private set; } internal PipelineLayoutCache PipelineLayoutCache { get; private set; } + internal BackgroundResources BackgroundResources { get; private set; } internal BufferManager BufferManager { get; private set; } @@ -170,6 +171,8 @@ namespace Ryujinx.Graphics.Vulkan PipelineLayoutCache = new PipelineLayoutCache(); + BackgroundResources = new BackgroundResources(this, _device); + BufferManager = new BufferManager(this, _physicalDevice, _device); _syncManager = new SyncManager(this, _device); @@ -194,7 +197,7 @@ namespace Ryujinx.Graphics.Vulkan public BufferHandle CreateBuffer(int size) { - return BufferManager.CreateWithHandle(this, size); + return BufferManager.CreateWithHandle(this, size, true); } public IProgram CreateProgram(IShader[] shaders, ShaderInfo info) @@ -386,6 +389,7 @@ namespace Ryujinx.Graphics.Vulkan public unsafe void Dispose() { CommandBufferPool.Dispose(); + BackgroundResources.Dispose(); _counters.Dispose(); _window.Dispose(); HelperShader.Dispose();