aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Vulkan
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Vulkan')
-rw-r--r--Ryujinx.Graphics.Vulkan/Auto.cs154
-rw-r--r--Ryujinx.Graphics.Vulkan/BackgroundResources.cs117
-rw-r--r--Ryujinx.Graphics.Vulkan/BitMap.cs157
-rw-r--r--Ryujinx.Graphics.Vulkan/BufferHolder.cs388
-rw-r--r--Ryujinx.Graphics.Vulkan/BufferManager.cs201
-rw-r--r--Ryujinx.Graphics.Vulkan/BufferState.cs83
-rw-r--r--Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs67
-rw-r--r--Ryujinx.Graphics.Vulkan/CacheByRange.cs54
-rw-r--r--Ryujinx.Graphics.Vulkan/CommandBufferPool.cs352
-rw-r--r--Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs44
-rw-r--r--Ryujinx.Graphics.Vulkan/Constants.cs20
-rw-r--r--Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs246
-rw-r--r--Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs201
-rw-r--r--Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs607
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposableBuffer.cs25
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposableBufferView.cs25
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs25
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposableImage.cs25
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposableImageView.cs25
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposableMemory.cs24
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposablePipeline.cs25
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs25
-rw-r--r--Ryujinx.Graphics.Vulkan/DisposableSampler.cs25
-rw-r--r--Ryujinx.Graphics.Vulkan/EnumConversion.cs307
-rw-r--r--Ryujinx.Graphics.Vulkan/FenceHelper.cs30
-rw-r--r--Ryujinx.Graphics.Vulkan/FenceHolder.cs79
-rw-r--r--Ryujinx.Graphics.Vulkan/FormatCapabilities.cs93
-rw-r--r--Ryujinx.Graphics.Vulkan/FormatConverter.cs49
-rw-r--r--Ryujinx.Graphics.Vulkan/FormatTable.cs182
-rw-r--r--Ryujinx.Graphics.Vulkan/FramebufferParams.cs203
-rw-r--r--Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs63
-rw-r--r--Ryujinx.Graphics.Vulkan/HashTableSlim.cs111
-rw-r--r--Ryujinx.Graphics.Vulkan/HelperShader.cs352
-rw-r--r--Ryujinx.Graphics.Vulkan/IdList.cs115
-rw-r--r--Ryujinx.Graphics.Vulkan/ImageWindow.cs361
-rw-r--r--Ryujinx.Graphics.Vulkan/MemoryAllocation.cs37
-rw-r--r--Ryujinx.Graphics.Vulkan/MemoryAllocator.cs84
-rw-r--r--Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs280
-rw-r--r--Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs212
-rw-r--r--Ryujinx.Graphics.Vulkan/NativeArray.cs45
-rw-r--r--Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs89
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineBase.cs1220
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineConverter.cs278
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs138
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineFull.cs288
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs44
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs58
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs112
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs253
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineState.cs579
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineUid.cs133
-rw-r--r--Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs206
-rw-r--r--Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs224
-rw-r--r--Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs167
-rw-r--r--Ryujinx.Graphics.Vulkan/Queries/Counters.cs58
-rw-r--r--Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj31
-rw-r--r--Ryujinx.Graphics.Vulkan/SamplerHolder.cs117
-rw-r--r--Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs60
-rw-r--r--Ryujinx.Graphics.Vulkan/Shader.cs167
-rw-r--r--Ryujinx.Graphics.Vulkan/ShaderCollection.cs406
-rw-r--r--Ryujinx.Graphics.Vulkan/Shaders/ColorBlitClearAlphaFragmentShaderSource.frag11
-rw-r--r--Ryujinx.Graphics.Vulkan/Shaders/ColorBlitFragmentShaderSource.frag11
-rw-r--r--Ryujinx.Graphics.Vulkan/Shaders/ColorBlitVertexShaderSource.vert20
-rw-r--r--Ryujinx.Graphics.Vulkan/Shaders/ColorClearFragmentShaderSource.frag9
-rw-r--r--Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert19
-rw-r--r--Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs314
-rw-r--r--Ryujinx.Graphics.Vulkan/StagingBuffer.cs194
-rw-r--r--Ryujinx.Graphics.Vulkan/SyncManager.cs122
-rw-r--r--Ryujinx.Graphics.Vulkan/TextureBuffer.cs150
-rw-r--r--Ryujinx.Graphics.Vulkan/TextureCopy.cs359
-rw-r--r--Ryujinx.Graphics.Vulkan/TextureStorage.cs504
-rw-r--r--Ryujinx.Graphics.Vulkan/TextureView.cs1150
-rw-r--r--Ryujinx.Graphics.Vulkan/Vendor.cs51
-rw-r--r--Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs11
-rw-r--r--Ryujinx.Graphics.Vulkan/VulkanException.cs41
-rw-r--r--Ryujinx.Graphics.Vulkan/VulkanInitialization.cs596
-rw-r--r--Ryujinx.Graphics.Vulkan/VulkanRenderer.cs609
-rw-r--r--Ryujinx.Graphics.Vulkan/Window.cs432
-rw-r--r--Ryujinx.Graphics.Vulkan/WindowBase.cs14
79 files changed, 14763 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Vulkan/Auto.cs b/Ryujinx.Graphics.Vulkan/Auto.cs
new file mode 100644
index 00000000..77261de9
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Auto.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ interface IAuto
+ {
+ bool HasCommandBufferDependency(CommandBufferScoped cbs);
+
+ void IncrementReferenceCount();
+ void DecrementReferenceCount(int cbIndex);
+ void DecrementReferenceCount();
+ }
+
+ interface IAutoPrivate : IAuto
+ {
+ void AddCommandBufferDependencies(CommandBufferScoped cbs);
+ }
+
+ class Auto<T> : IAutoPrivate, IDisposable where T : IDisposable
+ {
+ private int _referenceCount;
+ private T _value;
+
+ private readonly BitMap _cbOwnership;
+ private readonly MultiFenceHolder _waitable;
+ private readonly IAutoPrivate[] _referencedObjs;
+
+ private bool _disposed;
+ private bool _destroyed;
+
+ public Auto(T value)
+ {
+ _referenceCount = 1;
+ _value = value;
+ _cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers);
+ }
+
+ public Auto(T value, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value)
+ {
+ _waitable = waitable;
+ _referencedObjs = referencedObjs;
+
+ for (int i = 0; i < referencedObjs.Length; i++)
+ {
+ referencedObjs[i].IncrementReferenceCount();
+ }
+ }
+
+ public T Get(CommandBufferScoped cbs, int offset, int size)
+ {
+ _waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size);
+ return Get(cbs);
+ }
+
+ public T GetUnsafe()
+ {
+ return _value;
+ }
+
+ public T Get(CommandBufferScoped cbs)
+ {
+ if (!_destroyed)
+ {
+ AddCommandBufferDependencies(cbs);
+ }
+
+ 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
+ // more than once, so if we detect that the command buffer already has ownership
+ // of this object, then we can just return without doing anything else.
+ if (_cbOwnership.Set(cbs.CommandBufferIndex))
+ {
+ if (_waitable != null)
+ {
+ cbs.AddWaitable(_waitable);
+ }
+
+ cbs.AddDependant(this);
+
+ // We need to add a dependency on the command buffer to all objects this object
+ // references aswell.
+ if (_referencedObjs != null)
+ {
+ for (int i = 0; i < _referencedObjs.Length; i++)
+ {
+ _referencedObjs[i].AddCommandBufferDependencies(cbs);
+ }
+ }
+ }
+ }
+
+ public void IncrementReferenceCount()
+ {
+ if (Interlocked.Increment(ref _referenceCount) == 1)
+ {
+ Interlocked.Decrement(ref _referenceCount);
+ throw new InvalidOperationException("Attempted to increment the reference count of an object that was already destroyed.");
+ }
+ }
+
+ public void DecrementReferenceCount(int cbIndex)
+ {
+ _cbOwnership.Clear(cbIndex);
+ DecrementReferenceCount();
+ }
+
+ public void DecrementReferenceCount()
+ {
+ if (Interlocked.Decrement(ref _referenceCount) == 0)
+ {
+ _value.Dispose();
+ _value = default;
+ _destroyed = true;
+
+ // Value is no longer in use by the GPU, dispose all other
+ // resources that it references.
+ if (_referencedObjs != null)
+ {
+ for (int i = 0; i < _referencedObjs.Length; i++)
+ {
+ _referencedObjs[i].DecrementReferenceCount();
+ }
+ }
+ }
+
+ Debug.Assert(_referenceCount >= 0);
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ DecrementReferenceCount();
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/BackgroundResources.cs b/Ryujinx.Graphics.Vulkan/BackgroundResources.cs
new file mode 100644
index 00000000..30972f92
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BackgroundResources.cs
@@ -0,0 +1,117 @@
+using System.Threading;
+using System.Collections.Generic;
+using System;
+using Silk.NET.Vulkan;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class BackgroundResource : IDisposable
+ {
+ private VulkanRenderer _gd;
+ private Device _device;
+
+ private CommandBufferPool _pool;
+ private PersistentFlushBuffer _flushBuffer;
+
+ public BackgroundResource(VulkanRenderer gd, Device device)
+ {
+ _gd = gd;
+ _device = device;
+ }
+
+ public CommandBufferPool GetPool()
+ {
+ if (_pool == null)
+ {
+ bool useBackground = _gd.BackgroundQueue.Handle != 0 && _gd.Vendor != Vendor.Amd;
+ Queue queue = useBackground ? _gd.BackgroundQueue : _gd.Queue;
+ object queueLock = useBackground ? _gd.BackgroundQueueLock : _gd.QueueLock;
+
+ lock (queueLock)
+ {
+ _pool = new CommandBufferPool(_gd.Api, _device, queue, queueLock, _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 VulkanRenderer _gd;
+ private Device _device;
+
+ private Dictionary<Thread, BackgroundResource> _resources;
+
+ public BackgroundResources(VulkanRenderer gd, Device device)
+ {
+ _gd = gd;
+ _device = device;
+
+ _resources = new Dictionary<Thread, BackgroundResource>();
+ }
+
+ private void Cleanup()
+ {
+ lock (_resources)
+ {
+ foreach (KeyValuePair<Thread, BackgroundResource> 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
new file mode 100644
index 00000000..ee3c3c93
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BitMap.cs
@@ -0,0 +1,157 @@
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct BitMap
+ {
+ public const int IntSize = 64;
+
+ private const int IntShift = 6;
+ private const int IntMask = IntSize - 1;
+
+ private readonly long[] _masks;
+
+ public BitMap(int count)
+ {
+ _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 >> IntShift;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ return (_masks[wordIndex] & wordMask) != 0;
+ }
+
+ public bool IsSet(int start, int end)
+ {
+ if (start == end)
+ {
+ return IsSet(start);
+ }
+
+ int startIndex = start >> IntShift;
+ int startBit = start & IntMask;
+ long startMask = -1L << startBit;
+
+ int endIndex = end >> IntShift;
+ int endBit = end & IntMask;
+ long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
+
+ if (startIndex == endIndex)
+ {
+ return (_masks[startIndex] & startMask & endMask) != 0;
+ }
+
+ if ((_masks[startIndex] & startMask) != 0)
+ {
+ return true;
+ }
+
+ for (int i = startIndex + 1; i < endIndex; i++)
+ {
+ if (_masks[i] != 0)
+ {
+ return true;
+ }
+ }
+
+ if ((_masks[endIndex] & endMask) != 0)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool Set(int bit)
+ {
+ int wordIndex = bit >> IntShift;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ if ((_masks[wordIndex] & wordMask) != 0)
+ {
+ return false;
+ }
+
+ _masks[wordIndex] |= wordMask;
+
+ return true;
+ }
+
+ public void SetRange(int start, int end)
+ {
+ if (start == end)
+ {
+ Set(start);
+ return;
+ }
+
+ int startIndex = start >> IntShift;
+ int startBit = start & IntMask;
+ long startMask = -1L << startBit;
+
+ int endIndex = end >> IntShift;
+ int endBit = end & IntMask;
+ long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
+
+ if (startIndex == endIndex)
+ {
+ _masks[startIndex] |= startMask & endMask;
+ }
+ else
+ {
+ _masks[startIndex] |= startMask;
+
+ for (int i = startIndex + 1; i < endIndex; i++)
+ {
+ _masks[i] |= -1;
+ }
+
+ _masks[endIndex] |= endMask;
+ }
+ }
+
+ public void Clear(int bit)
+ {
+ int wordIndex = bit >> IntShift;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ _masks[wordIndex] &= ~wordMask;
+ }
+
+ public void Clear()
+ {
+ for (int i = 0; i < _masks.Length; i++)
+ {
+ _masks[i] = 0;
+ }
+ }
+
+ public void ClearInt(int start, int end)
+ {
+ for (int i = start; i <= end; i++)
+ {
+ _masks[i] = 0;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
new file mode 100644
index 00000000..a366e4ac
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
@@ -0,0 +1,388 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Runtime.CompilerServices;
+using VkBuffer = Silk.NET.Vulkan.Buffer;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class BufferHolder : IDisposable
+ {
+ private const int MaxUpdateBufferSize = 0x10000;
+
+ public const AccessFlags DefaultAccessFlags =
+ AccessFlags.AccessShaderReadBit |
+ AccessFlags.AccessShaderWriteBit |
+ AccessFlags.AccessTransferReadBit |
+ AccessFlags.AccessTransferWriteBit |
+ AccessFlags.AccessUniformReadBit |
+ AccessFlags.AccessShaderReadBit |
+ AccessFlags.AccessShaderWriteBit;
+
+ private readonly VulkanRenderer _gd;
+ private readonly Device _device;
+ private readonly MemoryAllocation _allocation;
+ private readonly Auto<DisposableBuffer> _buffer;
+ private readonly Auto<MemoryAllocation> _allocationAuto;
+ private readonly ulong _bufferHandle;
+
+ private CacheByRange<BufferHolder> _cachedConvertedIndexBuffers;
+
+ public int Size { get; }
+
+ private IntPtr _map;
+
+ private readonly MultiFenceHolder _waitable;
+
+ private bool _lastAccessIsWrite;
+
+ public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size)
+ {
+ _gd = gd;
+ _device = device;
+ _allocation = allocation;
+ _allocationAuto = new Auto<MemoryAllocation>(allocation);
+ _waitable = new MultiFenceHolder(size);
+ _buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
+ _bufferHandle = buffer.Handle;
+ Size = size;
+ _map = allocation.HostPointer;
+ }
+
+ public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size)
+ {
+ var bufferViewCreateInfo = new BufferViewCreateInfo()
+ {
+ SType = StructureType.BufferViewCreateInfo,
+ Buffer = new VkBuffer(_bufferHandle),
+ Format = format,
+ Offset = (uint)offset,
+ Range = (uint)size
+ };
+
+ _gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
+
+ return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
+ }
+
+ public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
+ {
+ // If the last access is write, we always need a barrier to be sure we will read or modify
+ // the correct data.
+ // If the last access is read, and current one is a write, we need to wait until the
+ // read finishes to avoid overwriting data still in use.
+ // Otherwise, if the last access is a read and the current one too, we don't need barriers.
+ bool needsBarrier = isWrite || _lastAccessIsWrite;
+
+ _lastAccessIsWrite = isWrite;
+
+ if (needsBarrier)
+ {
+ MemoryBarrier memoryBarrier = new MemoryBarrier()
+ {
+ SType = StructureType.MemoryBarrier,
+ SrcAccessMask = DefaultAccessFlags,
+ DstAccessMask = DefaultAccessFlags
+ };
+
+ _gd.Api.CmdPipelineBarrier(
+ commandBuffer,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ DependencyFlags.DependencyDeviceGroupBit,
+ 1,
+ memoryBarrier,
+ 0,
+ null,
+ 0,
+ null);
+ }
+ }
+
+ public Auto<DisposableBuffer> GetBuffer()
+ {
+ return _buffer;
+ }
+
+ public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false)
+ {
+ if (isWrite)
+ {
+ _cachedConvertedIndexBuffers.Clear();
+ }
+
+ return _buffer;
+ }
+
+ public BufferHandle GetHandle()
+ {
+ var handle = _bufferHandle;
+ return Unsafe.As<ulong, BufferHandle>(ref handle);
+ }
+
+ public unsafe IntPtr Map(int offset, int mappingSize)
+ {
+ return _map;
+ }
+
+ public unsafe ReadOnlySpan<byte> GetData(int offset, int 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
+ {
+ return resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
+ }
+ }
+ }
+
+ public unsafe Span<byte> GetDataStorage(int offset, int size)
+ {
+ int mappingSize = Math.Min(size, Size - offset);
+
+ if (_map != IntPtr.Zero)
+ {
+ return new Span<byte>((void*)(_map + offset), mappingSize);
+ }
+
+ throw new InvalidOperationException("The buffer is not host mapped.");
+ }
+
+ public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null)
+ {
+ int dataSize = Math.Min(data.Length, Size - offset);
+ if (dataSize == 0)
+ {
+ return;
+ }
+
+ if (_map != IntPtr.Zero)
+ {
+ // If persistently mapped, set the data directly if the buffer is not currently in use.
+ bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
+
+ // If the buffer is rented, take a little more time and check if the use overlaps this handle.
+ bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize);
+
+ if (!needsFlush)
+ {
+ WaitForFences(offset, dataSize);
+
+ data.Slice(0, dataSize).CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
+
+ return;
+ }
+ }
+
+ if (cbs != null && !(_buffer.HasCommandBufferDependency(cbs.Value) && _waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize)))
+ {
+ // 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 ||
+ !VulkanConfiguration.UseFastBufferUpdates ||
+ data.Length > MaxUpdateBufferSize ||
+ !TryPushData(cbs.Value, endRenderPass, offset, data))
+ {
+ _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data);
+ }
+ }
+
+ public unsafe void SetDataUnchecked(int offset, ReadOnlySpan<byte> data)
+ {
+ int dataSize = Math.Min(data.Length, Size - offset);
+ if (dataSize == 0)
+ {
+ return;
+ }
+
+ if (_map != IntPtr.Zero)
+ {
+ data.Slice(0, dataSize).CopyTo(new Span<byte>((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<byte> data)
+ {
+ if (!TryPushData(cbs, endRenderPass, dstOffset, data))
+ {
+ throw new ArgumentException($"Invalid offset 0x{dstOffset:X} or data size 0x{data.Length:X}.");
+ }
+ }
+
+ private unsafe bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan<byte> data)
+ {
+ if ((dstOffset & 3) != 0 || (data.Length & 3) != 0)
+ {
+ return false;
+ }
+
+ endRenderPass?.Invoke();
+
+ var dstBuffer = GetBuffer(cbs.CommandBuffer, true).Get(cbs, dstOffset, data.Length).Value;
+
+ InsertBufferBarrier(
+ _gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ BufferHolder.DefaultAccessFlags,
+ AccessFlags.AccessTransferWriteBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ dstOffset,
+ data.Length);
+
+ fixed (byte* pData = data)
+ {
+ for (ulong offset = 0; offset < (ulong)data.Length;)
+ {
+ ulong size = Math.Min(MaxUpdateBufferSize, (ulong)data.Length - offset);
+ _gd.Api.CmdUpdateBuffer(cbs.CommandBuffer, dstBuffer, (ulong)dstOffset + offset, size, pData + offset);
+ offset += size;
+ }
+ }
+
+ InsertBufferBarrier(
+ _gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ AccessFlags.AccessTransferWriteBit,
+ BufferHolder.DefaultAccessFlags,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ dstOffset,
+ data.Length);
+
+ return true;
+ }
+
+ public static unsafe void Copy(
+ VulkanRenderer gd,
+ CommandBufferScoped cbs,
+ Auto<DisposableBuffer> src,
+ Auto<DisposableBuffer> dst,
+ int srcOffset,
+ int dstOffset,
+ int size)
+ {
+ var srcBuffer = src.Get(cbs, srcOffset, size).Value;
+ var dstBuffer = dst.Get(cbs, dstOffset, size).Value;
+
+ InsertBufferBarrier(
+ gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ BufferHolder.DefaultAccessFlags,
+ AccessFlags.AccessTransferWriteBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ dstOffset,
+ size);
+
+ var region = new BufferCopy((ulong)srcOffset, (ulong)dstOffset, (ulong)size);
+
+ gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, 1, &region);
+
+ InsertBufferBarrier(
+ gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ AccessFlags.AccessTransferWriteBit,
+ BufferHolder.DefaultAccessFlags,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ dstOffset,
+ size);
+ }
+
+ public static unsafe void InsertBufferBarrier(
+ VulkanRenderer gd,
+ CommandBuffer commandBuffer,
+ VkBuffer buffer,
+ AccessFlags srcAccessMask,
+ AccessFlags dstAccessMask,
+ PipelineStageFlags srcStageMask,
+ PipelineStageFlags dstStageMask,
+ int offset,
+ int size)
+ {
+ BufferMemoryBarrier memoryBarrier = new BufferMemoryBarrier()
+ {
+ SType = StructureType.BufferMemoryBarrier,
+ SrcAccessMask = srcAccessMask,
+ DstAccessMask = dstAccessMask,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Buffer = buffer,
+ Offset = (ulong)offset,
+ Size = (ulong)size
+ };
+
+ gd.Api.CmdPipelineBarrier(
+ commandBuffer,
+ srcStageMask,
+ dstStageMask,
+ 0,
+ 0,
+ null,
+ 1,
+ memoryBarrier,
+ 0,
+ null);
+ }
+
+ public void WaitForFences()
+ {
+ _waitable.WaitForFences(_gd.Api, _device);
+ }
+
+ public void WaitForFences(int offset, int size)
+ {
+ _waitable.WaitForFences(_gd.Api, _device, offset, size);
+ }
+
+ public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
+ {
+ if (!_cachedConvertedIndexBuffers.TryGetValue(offset, size, out var holder))
+ {
+ holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
+
+ _gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
+
+ _cachedConvertedIndexBuffers.Add(offset, size, holder);
+ }
+
+ return holder.GetBuffer();
+ }
+
+ public void Dispose()
+ {
+ _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
+
+ _buffer.Dispose();
+ _allocationAuto.Dispose();
+ _cachedConvertedIndexBuffers.Dispose();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/BufferManager.cs b/Ryujinx.Graphics.Vulkan/BufferManager.cs
new file mode 100644
index 00000000..77f60db9
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BufferManager.cs
@@ -0,0 +1,201 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class BufferManager : IDisposable
+ {
+ private const MemoryPropertyFlags DefaultBufferMemoryFlags =
+ MemoryPropertyFlags.MemoryPropertyHostVisibleBit |
+ 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 |
+ BufferUsageFlags.BufferUsageUniformTexelBufferBit |
+ BufferUsageFlags.BufferUsageStorageTexelBufferBit |
+ BufferUsageFlags.BufferUsageUniformBufferBit |
+ BufferUsageFlags.BufferUsageStorageBufferBit |
+ BufferUsageFlags.BufferUsageIndexBufferBit |
+ BufferUsageFlags.BufferUsageVertexBufferBit |
+ BufferUsageFlags.BufferUsageTransformFeedbackBufferBitExt;
+
+ private readonly PhysicalDevice _physicalDevice;
+ private readonly Device _device;
+
+ private readonly IdList<BufferHolder> _buffers;
+
+ public StagingBuffer StagingBuffer { get; }
+
+ public BufferManager(VulkanRenderer gd, PhysicalDevice physicalDevice, Device device)
+ {
+ _physicalDevice = physicalDevice;
+ _device = device;
+ _buffers = new IdList<BufferHolder>();
+ StagingBuffer = new StagingBuffer(gd, this);
+ }
+
+ public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, bool deviceLocal)
+ {
+ var holder = Create(gd, size, deviceLocal: deviceLocal);
+ if (holder == null)
+ {
+ return BufferHandle.Null;
+ }
+
+ ulong handle64 = (uint)_buffers.Add(holder);
+
+ return Unsafe.As<ulong, BufferHandle>(ref handle64);
+ }
+
+ public unsafe BufferHolder Create(VulkanRenderer gd, int size, bool forConditionalRendering = false, bool deviceLocal = false)
+ {
+ var usage = DefaultBufferUsageFlags;
+
+ if (forConditionalRendering && gd.Capabilities.SupportsConditionalRendering)
+ {
+ usage |= BufferUsageFlags.BufferUsageConditionalRenderingBitExt;
+ }
+ else if (gd.Capabilities.SupportsIndirectParameters)
+ {
+ usage |= BufferUsageFlags.BufferUsageIndirectBufferBit;
+ }
+
+ var bufferCreateInfo = new BufferCreateInfo()
+ {
+ SType = StructureType.BufferCreateInfo,
+ Size = (ulong)size,
+ Usage = usage,
+ SharingMode = SharingMode.Exclusive
+ };
+
+ gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
+ gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
+
+ var allocateFlags = deviceLocal ? DeviceLocalBufferMemoryFlags : DefaultBufferMemoryFlags;
+
+ var allocation = gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, requirements, allocateFlags);
+
+ if (allocation.Memory.Handle == 0UL)
+ {
+ gd.Api.DestroyBuffer(_device, buffer, null);
+ return null;
+ }
+
+ gd.Api.BindBufferMemory(_device, buffer, allocation.Memory, allocation.Offset);
+
+ return new BufferHolder(gd, _device, buffer, allocation, size);
+ }
+
+ public Auto<DisposableBufferView> CreateView(BufferHandle handle, VkFormat format, int offset, int size)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ return holder.CreateView(format, offset, size);
+ }
+
+ return null;
+ }
+
+ public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ return holder.GetBuffer(commandBuffer, isWrite);
+ }
+
+ return null;
+ }
+
+ public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, BufferHandle handle, int offset, int size)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ return holder.GetBufferI8ToI16(cbs, offset, size);
+ }
+
+ return null;
+ }
+
+ public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, out int size)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ size = holder.Size;
+ return holder.GetBuffer(commandBuffer, isWrite);
+ }
+
+ size = 0;
+ return null;
+ }
+
+ public ReadOnlySpan<byte> GetData(BufferHandle handle, int offset, int size)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ return holder.GetData(offset, size);
+ }
+
+ return ReadOnlySpan<byte>.Empty;
+ }
+
+ public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
+ {
+ SetData(handle, offset, MemoryMarshal.Cast<T, byte>(data), null, null);
+ }
+
+ public void SetData(BufferHandle handle, int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs, Action endRenderPass)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ holder.SetData(offset, data, cbs, endRenderPass);
+ }
+ }
+
+ public void Delete(BufferHandle handle)
+ {
+ if (TryGetBuffer(handle, out var holder))
+ {
+ holder.Dispose();
+ _buffers.Remove((int)Unsafe.As<BufferHandle, ulong>(ref handle));
+ }
+ }
+
+ private bool TryGetBuffer(BufferHandle handle, out BufferHolder holder)
+ {
+ return _buffers.TryGetValue((int)Unsafe.As<BufferHandle, ulong>(ref handle), out holder);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ foreach (BufferHolder buffer in _buffers)
+ {
+ buffer.Dispose();
+ }
+
+ _buffers.Clear();
+ StagingBuffer.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/BufferState.cs b/Ryujinx.Graphics.Vulkan/BufferState.cs
new file mode 100644
index 00000000..c91ed7a1
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BufferState.cs
@@ -0,0 +1,83 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct BufferState : IDisposable
+ {
+ public static BufferState Null => new BufferState(null, 0, 0);
+
+ private readonly Auto<DisposableBuffer> _buffer;
+ private readonly int _offset;
+ private readonly int _size;
+ private readonly ulong _stride;
+ private readonly IndexType _type;
+
+ public BufferState(Auto<DisposableBuffer> buffer, int offset, int size, IndexType type)
+ {
+ _buffer = buffer;
+ _offset = offset;
+ _size = size;
+ _stride = 0;
+ _type = type;
+ buffer?.IncrementReferenceCount();
+ }
+
+ public BufferState(Auto<DisposableBuffer> buffer, int offset, int size, ulong stride = 0UL)
+ {
+ _buffer = buffer;
+ _offset = offset;
+ _size = size;
+ _stride = stride;
+ _type = IndexType.Uint16;
+ buffer?.IncrementReferenceCount();
+ }
+
+ public void BindIndexBuffer(Vk api, CommandBufferScoped cbs)
+ {
+ if (_buffer != null)
+ {
+ api.CmdBindIndexBuffer(cbs.CommandBuffer, _buffer.Get(cbs, _offset, _size).Value, (ulong)_offset, _type);
+ }
+ }
+
+ public void BindTransformFeedbackBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding)
+ {
+ if (_buffer != null)
+ {
+ var buffer = _buffer.Get(cbs, _offset, _size).Value;
+
+ gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset, (ulong)_size);
+ }
+ }
+
+ public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding)
+ {
+ if (_buffer != null)
+ {
+ var buffer = _buffer.Get(cbs, _offset, _size).Value;
+
+ if (gd.Capabilities.SupportsExtendedDynamicState)
+ {
+ gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
+ cbs.CommandBuffer,
+ binding,
+ 1,
+ buffer,
+ (ulong)_offset,
+ (ulong)_size,
+ _stride);
+ }
+ else
+ {
+ gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _buffer?.DecrementReferenceCount();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs b/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs
new file mode 100644
index 00000000..3242b9fc
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs
@@ -0,0 +1,67 @@
+namespace Ryujinx.Graphics.Vulkan
+{
+ internal class BufferUsageBitmap
+ {
+ private BitMap _bitmap;
+ private int _size;
+ private int _granularity;
+ private int _bits;
+
+ private int _intsPerCb;
+ private int _bitsPerCb;
+
+ public BufferUsageBitmap(int size, int granularity)
+ {
+ _size = size;
+ _granularity = granularity;
+ _bits = (size + (granularity - 1)) / granularity;
+
+ _intsPerCb = (_bits + (BitMap.IntSize - 1)) / BitMap.IntSize;
+ _bitsPerCb = _intsPerCb * BitMap.IntSize;
+
+ _bitmap = new BitMap(_bitsPerCb * CommandBufferPool.MaxCommandBuffers);
+ }
+
+ public void Add(int cbIndex, int offset, int size)
+ {
+ // Some usages can be out of bounds (vertex buffer on amd), so bound if necessary.
+ if (offset + size > _size)
+ {
+ size = _size - offset;
+ }
+
+ int cbBase = cbIndex * _bitsPerCb;
+ int start = cbBase + offset / _granularity;
+ int end = cbBase + (offset + size - 1) / _granularity;
+
+ _bitmap.SetRange(start, end);
+ }
+
+ public bool OverlapsWith(int cbIndex, int offset, int size)
+ {
+ int cbBase = cbIndex * _bitsPerCb;
+ int start = cbBase + offset / _granularity;
+ int end = cbBase + (offset + size - 1) / _granularity;
+
+ return _bitmap.IsSet(start, end);
+ }
+
+ public bool OverlapsWith(int offset, int size)
+ {
+ for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
+ {
+ if (OverlapsWith(i, offset, size))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void Clear(int cbIndex)
+ {
+ _bitmap.ClearInt(cbIndex * _intsPerCb, (cbIndex + 1) * _intsPerCb - 1);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/CacheByRange.cs b/Ryujinx.Graphics.Vulkan/CacheByRange.cs
new file mode 100644
index 00000000..f3f503da
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/CacheByRange.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct CacheByRange<T> where T : IDisposable
+ {
+ private Dictionary<ulong, T> _ranges;
+
+ public void Add(int offset, int size, T value)
+ {
+ EnsureInitialized();
+ _ranges.Add(PackRange(offset, size), value);
+ }
+
+ public bool TryGetValue(int offset, int size, out T value)
+ {
+ EnsureInitialized();
+ return _ranges.TryGetValue(PackRange(offset, size), out value);
+ }
+
+ public void Clear()
+ {
+ if (_ranges != null)
+ {
+ foreach (T value in _ranges.Values)
+ {
+ value.Dispose();
+ }
+
+ _ranges.Clear();
+ _ranges = null;
+ }
+ }
+
+ private void EnsureInitialized()
+ {
+ if (_ranges == null)
+ {
+ _ranges = new Dictionary<ulong, T>();
+ }
+ }
+
+ private static ulong PackRange(int offset, int size)
+ {
+ return (uint)offset | ((ulong)size << 32);
+ }
+
+ public void Dispose()
+ {
+ Clear();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs b/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
new file mode 100644
index 00000000..a27b63a0
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
@@ -0,0 +1,352 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Thread = System.Threading.Thread;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class CommandBufferPool : IDisposable
+ {
+ public const int MaxCommandBuffers = 16;
+
+ private int _totalCommandBuffers;
+ private int _totalCommandBuffersMask;
+
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly Queue _queue;
+ private readonly object _queueLock;
+ private readonly CommandPool _pool;
+ private readonly Thread _owner;
+
+ public bool OwnedByCurrentThread => _owner == Thread.CurrentThread;
+
+ private struct ReservedCommandBuffer
+ {
+ public bool InUse;
+ public bool InConsumption;
+ public CommandBuffer CommandBuffer;
+ public FenceHolder Fence;
+ public SemaphoreHolder Semaphore;
+
+ public List<IAuto> Dependants;
+ public HashSet<MultiFenceHolder> Waitables;
+ public HashSet<SemaphoreHolder> Dependencies;
+
+ public void Initialize(Vk api, Device device, CommandPool pool)
+ {
+ var allocateInfo = new CommandBufferAllocateInfo()
+ {
+ SType = StructureType.CommandBufferAllocateInfo,
+ CommandBufferCount = 1,
+ CommandPool = pool,
+ Level = CommandBufferLevel.Primary
+ };
+
+ api.AllocateCommandBuffers(device, allocateInfo, out CommandBuffer);
+
+ Dependants = new List<IAuto>();
+ Waitables = new HashSet<MultiFenceHolder>();
+ Dependencies = new HashSet<SemaphoreHolder>();
+ }
+ }
+
+ private readonly ReservedCommandBuffer[] _commandBuffers;
+
+ private readonly int[] _queuedIndexes;
+ private int _queuedIndexesPtr;
+ private int _queuedCount;
+ private int _inUseCount;
+
+ public unsafe CommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex, bool isLight = false)
+ {
+ _api = api;
+ _device = device;
+ _queue = queue;
+ _queueLock = queueLock;
+ _owner = Thread.CurrentThread;
+
+ var commandPoolCreateInfo = new CommandPoolCreateInfo()
+ {
+ SType = StructureType.CommandPoolCreateInfo,
+ QueueFamilyIndex = queueFamilyIndex,
+ Flags = CommandPoolCreateFlags.CommandPoolCreateTransientBit |
+ CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit
+ };
+
+ api.CreateCommandPool(device, commandPoolCreateInfo, null, out _pool).ThrowOnError();
+
+ // We need at least 2 command buffers to get texture data in some cases.
+ _totalCommandBuffers = isLight ? 2 : MaxCommandBuffers;
+ _totalCommandBuffersMask = _totalCommandBuffers - 1;
+
+ _commandBuffers = new ReservedCommandBuffer[_totalCommandBuffers];
+
+ _queuedIndexes = new int[_totalCommandBuffers];
+ _queuedIndexesPtr = 0;
+ _queuedCount = 0;
+
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ _commandBuffers[i].Initialize(api, device, _pool);
+ WaitAndDecrementRef(i);
+ }
+ }
+
+ public void AddDependant(int cbIndex, IAuto dependant)
+ {
+ dependant.IncrementReferenceCount();
+ _commandBuffers[cbIndex].Dependants.Add(dependant);
+ }
+
+ public void AddWaitable(MultiFenceHolder waitable)
+ {
+ lock (_commandBuffers)
+ {
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ ref var entry = ref _commandBuffers[i];
+
+ if (entry.InConsumption)
+ {
+ AddWaitable(i, waitable);
+ }
+ }
+ }
+ }
+
+ public void AddDependency(int cbIndex, CommandBufferScoped dependencyCbs)
+ {
+ Debug.Assert(_commandBuffers[cbIndex].InUse);
+ var semaphoreHolder = _commandBuffers[dependencyCbs.CommandBufferIndex].Semaphore;
+ semaphoreHolder.Get();
+ _commandBuffers[cbIndex].Dependencies.Add(semaphoreHolder);
+ }
+
+ public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
+ {
+ ref var entry = ref _commandBuffers[cbIndex];
+ waitable.AddFence(cbIndex, entry.Fence);
+ entry.Waitables.Add(waitable);
+ }
+
+ public bool HasWaitableOnRentedCommandBuffer(MultiFenceHolder waitable, int offset, int size)
+ {
+ lock (_commandBuffers)
+ {
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ ref var entry = ref _commandBuffers[i];
+
+ if (entry.InUse &&
+ entry.Waitables.Contains(waitable) &&
+ waitable.IsBufferRangeInUse(i, offset, size))
+ {
+ return true;
+ }
+ }
+ }
+
+ 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 int FreeConsumed(bool wait)
+ {
+ int freeEntry = 0;
+
+ while (_queuedCount > 0)
+ {
+ int index = _queuedIndexes[_queuedIndexesPtr];
+
+ ref var entry = ref _commandBuffers[index];
+
+ if (wait || !entry.InConsumption || entry.Fence.IsSignaled())
+ {
+ WaitAndDecrementRef(index);
+
+ wait = false;
+ freeEntry = index;
+
+ _queuedCount--;
+ _queuedIndexesPtr = (_queuedIndexesPtr + 1) % _totalCommandBuffers;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return freeEntry;
+ }
+
+ public CommandBufferScoped ReturnAndRent(CommandBufferScoped cbs)
+ {
+ Return(cbs);
+ return Rent();
+ }
+
+ public CommandBufferScoped Rent()
+ {
+ lock (_commandBuffers)
+ {
+ int cursor = FreeConsumed(_inUseCount + _queuedCount == _totalCommandBuffers);
+
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ ref var entry = ref _commandBuffers[cursor];
+
+ if (!entry.InUse && !entry.InConsumption)
+ {
+ entry.InUse = true;
+
+ _inUseCount++;
+
+ var commandBufferBeginInfo = new CommandBufferBeginInfo()
+ {
+ SType = StructureType.CommandBufferBeginInfo
+ };
+
+ _api.BeginCommandBuffer(entry.CommandBuffer, commandBufferBeginInfo).ThrowOnError();
+
+ return new CommandBufferScoped(this, entry.CommandBuffer, cursor);
+ }
+
+ cursor = (cursor + 1) & _totalCommandBuffersMask;
+ }
+ }
+
+ throw new InvalidOperationException($"Out of command buffers (In use: {_inUseCount}, queued: {_queuedCount}, total: {_totalCommandBuffers})");
+ }
+
+ public void Return(CommandBufferScoped cbs)
+ {
+ Return(cbs, null, null, null);
+ }
+
+ public unsafe void Return(
+ CommandBufferScoped cbs,
+ ReadOnlySpan<Semaphore> waitSemaphores,
+ ReadOnlySpan<PipelineStageFlags> waitDstStageMask,
+ ReadOnlySpan<Semaphore> signalSemaphores)
+ {
+ lock (_commandBuffers)
+ {
+ int cbIndex = cbs.CommandBufferIndex;
+
+ ref var entry = ref _commandBuffers[cbIndex];
+
+ Debug.Assert(entry.InUse);
+ Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle);
+ entry.InUse = false;
+ entry.InConsumption = true;
+ _inUseCount--;
+
+ var commandBuffer = entry.CommandBuffer;
+
+ _api.EndCommandBuffer(commandBuffer).ThrowOnError();
+
+ fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
+ {
+ fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask)
+ {
+ SubmitInfo sInfo = new SubmitInfo()
+ {
+ SType = StructureType.SubmitInfo,
+ WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0,
+ PWaitSemaphores = pWaitSemaphores,
+ PWaitDstStageMask = pWaitDstStageMask,
+ CommandBufferCount = 1,
+ PCommandBuffers = &commandBuffer,
+ SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0,
+ PSignalSemaphores = pSignalSemaphores
+ };
+
+ lock (_queueLock)
+ {
+ _api.QueueSubmit(_queue, 1, sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
+ }
+ }
+ }
+
+ int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers;
+ _queuedIndexes[ptr] = cbIndex;
+ _queuedCount++;
+ }
+ }
+
+ private void WaitAndDecrementRef(int cbIndex, bool refreshFence = true)
+ {
+ ref var entry = ref _commandBuffers[cbIndex];
+
+ if (entry.InConsumption)
+ {
+ entry.Fence.Wait();
+ entry.InConsumption = false;
+ }
+
+ foreach (var dependant in entry.Dependants)
+ {
+ dependant.DecrementReferenceCount(cbIndex);
+ }
+
+ foreach (var waitable in entry.Waitables)
+ {
+ waitable.RemoveFence(cbIndex, entry.Fence);
+ waitable.RemoveBufferUses(cbIndex);
+ }
+
+ foreach (var dependency in entry.Dependencies)
+ {
+ dependency.Put();
+ }
+
+ entry.Dependants.Clear();
+ entry.Waitables.Clear();
+ entry.Dependencies.Clear();
+ entry.Fence?.Dispose();
+
+ if (refreshFence)
+ {
+ entry.Fence = new FenceHolder(_api, _device);
+ }
+ else
+ {
+ entry.Fence = null;
+ }
+ }
+
+ public unsafe void Dispose()
+ {
+ for (int i = 0; i < _totalCommandBuffers; i++)
+ {
+ WaitAndDecrementRef(i, refreshFence: false);
+ }
+
+ _api.DestroyCommandPool(_device, _pool, null);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs b/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs
new file mode 100644
index 00000000..372950a8
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs
@@ -0,0 +1,44 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct CommandBufferScoped : IDisposable
+ {
+ private readonly CommandBufferPool _pool;
+ public CommandBuffer CommandBuffer { get; }
+ public int CommandBufferIndex { get; }
+
+ public CommandBufferScoped(CommandBufferPool pool, CommandBuffer commandBuffer, int commandBufferIndex)
+ {
+ _pool = pool;
+ CommandBuffer = commandBuffer;
+ CommandBufferIndex = commandBufferIndex;
+ }
+
+ public void AddDependant(IAuto dependant)
+ {
+ _pool.AddDependant(CommandBufferIndex, dependant);
+ }
+
+ public void AddWaitable(MultiFenceHolder waitable)
+ {
+ _pool.AddWaitable(CommandBufferIndex, waitable);
+ }
+
+ public void AddDependency(CommandBufferScoped dependencyCbs)
+ {
+ _pool.AddDependency(CommandBufferIndex, dependencyCbs);
+ }
+
+ public FenceHolder GetFence()
+ {
+ return _pool.GetFence(CommandBufferIndex);
+ }
+
+ public void Dispose()
+ {
+ _pool?.Return(this);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Constants.cs b/Ryujinx.Graphics.Vulkan/Constants.cs
new file mode 100644
index 00000000..f43d815a
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Constants.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class Constants
+ {
+ public const int MaxVertexAttributes = 32;
+ public const int MaxVertexBuffers = 32;
+ public const int MaxTransformFeedbackBuffers = 4;
+ public const int MaxRenderTargets = 8;
+ public const int MaxViewports = 16;
+ public const int MaxShaderStages = 5;
+ public const int MaxUniformBuffersPerStage = 18;
+ public const int MaxStorageBuffersPerStage = 16;
+ public const int MaxTexturesPerStage = 64;
+ public const int MaxImagesPerStage = 16;
+ public const int MaxUniformBufferBindings = MaxUniformBuffersPerStage * MaxShaderStages;
+ public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
+ public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
+ public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages;
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs b/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs
new file mode 100644
index 00000000..c57cb1a9
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs
@@ -0,0 +1,246 @@
+using Silk.NET.Vulkan;
+using System;
+using VkBuffer = Silk.NET.Vulkan.Buffer;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DescriptorSetCollection : IDisposable
+ {
+ private DescriptorSetManager.DescriptorPoolHolder _holder;
+ private readonly DescriptorSet[] _descriptorSets;
+ public int SetsCount => _descriptorSets.Length;
+
+ public DescriptorSetCollection(DescriptorSetManager.DescriptorPoolHolder holder, DescriptorSet[] descriptorSets)
+ {
+ _holder = holder;
+ _descriptorSets = descriptorSets;
+ }
+
+ public void InitializeBuffers(int setIndex, int baseBinding, int countPerUnit, DescriptorType type, VkBuffer dummyBuffer)
+ {
+ Span<DescriptorBufferInfo> infos = stackalloc DescriptorBufferInfo[countPerUnit];
+
+ infos.Fill(new DescriptorBufferInfo()
+ {
+ Buffer = dummyBuffer,
+ Range = Vk.WholeSize
+ });
+
+ UpdateBuffers(setIndex, baseBinding, infos, type);
+ }
+
+ public unsafe void UpdateBuffer(int setIndex, int bindingIndex, DescriptorBufferInfo bufferInfo, DescriptorType type)
+ {
+ if (bufferInfo.Buffer.Handle != 0UL)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)bindingIndex,
+ DescriptorType = type,
+ DescriptorCount = 1,
+ PBufferInfo = &bufferInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateBuffers(int setIndex, int baseBinding, ReadOnlySpan<DescriptorBufferInfo> bufferInfo, DescriptorType type)
+ {
+ if (bufferInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)baseBinding,
+ DescriptorType = type,
+ DescriptorCount = (uint)bufferInfo.Length,
+ PBufferInfo = pBufferInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateStorageBuffers(int setIndex, int baseBinding, ReadOnlySpan<DescriptorBufferInfo> bufferInfo)
+ {
+ if (bufferInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)(baseBinding & ~(Constants.MaxStorageBuffersPerStage - 1)),
+ DstArrayElement = (uint)(baseBinding & (Constants.MaxStorageBuffersPerStage - 1)),
+ DescriptorType = DescriptorType.StorageBuffer,
+ DescriptorCount = (uint)bufferInfo.Length,
+ PBufferInfo = pBufferInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateImage(int setIndex, int bindingIndex, DescriptorImageInfo imageInfo, DescriptorType type)
+ {
+ if (imageInfo.ImageView.Handle != 0UL)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)bindingIndex,
+ DescriptorType = type,
+ DescriptorCount = 1,
+ PImageInfo = &imageInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateImages(int setIndex, int baseBinding, ReadOnlySpan<DescriptorImageInfo> imageInfo, DescriptorType type)
+ {
+ if (imageInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorImageInfo* pImageInfo = imageInfo)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)baseBinding,
+ DescriptorType = type,
+ DescriptorCount = (uint)imageInfo.Length,
+ PImageInfo = pImageInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateImagesCombined(int setIndex, int baseBinding, ReadOnlySpan<DescriptorImageInfo> imageInfo, DescriptorType type)
+ {
+ if (imageInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorImageInfo* pImageInfo = imageInfo)
+ {
+ for (int i = 0; i < imageInfo.Length; i++)
+ {
+ bool nonNull = imageInfo[i].ImageView.Handle != 0 && imageInfo[i].Sampler.Handle != 0;
+ if (nonNull)
+ {
+ int count = 1;
+
+ while (i + count < imageInfo.Length &&
+ imageInfo[i + count].ImageView.Handle != 0 &&
+ imageInfo[i + count].Sampler.Handle != 0)
+ {
+ count++;
+ }
+
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)(baseBinding + i),
+ DescriptorType = DescriptorType.CombinedImageSampler,
+ DescriptorCount = (uint)count,
+ PImageInfo = pImageInfo
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+
+ i += count - 1;
+ }
+ }
+ }
+ }
+
+ public unsafe void UpdateBufferImage(int setIndex, int bindingIndex, BufferView texelBufferView, DescriptorType type)
+ {
+ if (texelBufferView.Handle != 0UL)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)bindingIndex,
+ DescriptorType = type,
+ DescriptorCount = 1,
+ PTexelBufferView = &texelBufferView
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+ }
+
+ public unsafe void UpdateBufferImages(int setIndex, int baseBinding, ReadOnlySpan<BufferView> texelBufferView, DescriptorType type)
+ {
+ if (texelBufferView.Length == 0)
+ {
+ return;
+ }
+
+ fixed (BufferView* pTexelBufferView = texelBufferView)
+ {
+ for (uint i = 0; i < texelBufferView.Length;)
+ {
+ uint count = 1;
+
+ if (texelBufferView[(int)i].Handle != 0UL)
+ {
+ while (i + count < texelBufferView.Length && texelBufferView[(int)(i + count)].Handle != 0UL)
+ {
+ count++;
+ }
+
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstSet = _descriptorSets[setIndex],
+ DstBinding = (uint)baseBinding + i,
+ DescriptorType = type,
+ DescriptorCount = count,
+ PTexelBufferView = pTexelBufferView + i
+ };
+
+ _holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
+ }
+
+ i += count;
+ }
+ }
+ }
+
+ public DescriptorSet[] GetSets()
+ {
+ return _descriptorSets;
+ }
+
+ public void Dispose()
+ {
+ _holder?.FreeDescriptorSets(this);
+ _holder = null;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs b/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
new file mode 100644
index 00000000..a88bb7b1
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
@@ -0,0 +1,201 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class DescriptorSetManager : IDisposable
+ {
+ private const uint DescriptorPoolMultiplier = 16;
+
+ public class DescriptorPoolHolder : IDisposable
+ {
+ public Vk Api { get; }
+ public Device Device { get; }
+
+ private readonly DescriptorPool _pool;
+ private readonly uint _capacity;
+ private int _totalSets;
+ private int _setsInUse;
+ private bool _done;
+
+ public unsafe DescriptorPoolHolder(Vk api, Device device)
+ {
+ Api = api;
+ Device = device;
+
+ var poolSizes = new DescriptorPoolSize[]
+ {
+ new DescriptorPoolSize(DescriptorType.UniformBuffer, (1 + Constants.MaxUniformBufferBindings) * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.StorageBuffer, Constants.MaxStorageBufferBindings * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.CombinedImageSampler, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.StorageImage, Constants.MaxImageBindings * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.UniformTexelBuffer, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
+ new DescriptorPoolSize(DescriptorType.StorageTexelBuffer, Constants.MaxImageBindings * DescriptorPoolMultiplier)
+ };
+
+ uint maxSets = (uint)poolSizes.Length * DescriptorPoolMultiplier;
+
+ _capacity = maxSets;
+
+ fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
+ {
+ var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo()
+ {
+ SType = StructureType.DescriptorPoolCreateInfo,
+ MaxSets = maxSets,
+ PoolSizeCount = (uint)poolSizes.Length,
+ PPoolSizes = pPoolsSize
+ };
+
+ Api.CreateDescriptorPool(device, descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
+ }
+ }
+
+ public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts)
+ {
+ TryAllocateDescriptorSets(layouts, isTry: false, out var dsc);
+ return dsc;
+ }
+
+ public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, out DescriptorSetCollection dsc)
+ {
+ return TryAllocateDescriptorSets(layouts, isTry: true, out dsc);
+ }
+
+ private unsafe bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, bool isTry, out DescriptorSetCollection dsc)
+ {
+ Debug.Assert(!_done);
+
+ DescriptorSet[] descriptorSets = new DescriptorSet[layouts.Length];
+
+ fixed (DescriptorSet* pDescriptorSets = descriptorSets)
+ {
+ fixed (DescriptorSetLayout* pLayouts = layouts)
+ {
+ var descriptorSetAllocateInfo = new DescriptorSetAllocateInfo()
+ {
+ SType = StructureType.DescriptorSetAllocateInfo,
+ DescriptorPool = _pool,
+ DescriptorSetCount = (uint)layouts.Length,
+ PSetLayouts = pLayouts
+ };
+
+ var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets);
+ if (isTry && result == Result.ErrorOutOfPoolMemory)
+ {
+ _totalSets = (int)_capacity;
+ _done = true;
+ DestroyIfDone();
+ dsc = default;
+ return false;
+ }
+
+ result.ThrowOnError();
+ }
+ }
+
+ _totalSets += layouts.Length;
+ _setsInUse += layouts.Length;
+
+ dsc = new DescriptorSetCollection(this, descriptorSets);
+ return true;
+ }
+
+ public void FreeDescriptorSets(DescriptorSetCollection dsc)
+ {
+ _setsInUse -= dsc.SetsCount;
+ Debug.Assert(_setsInUse >= 0);
+ DestroyIfDone();
+ }
+
+ public bool CanFit(int count)
+ {
+ if (_totalSets + count <= _capacity)
+ {
+ return true;
+ }
+
+ _done = true;
+ DestroyIfDone();
+ return false;
+ }
+
+ private unsafe void DestroyIfDone()
+ {
+ if (_done && _setsInUse == 0)
+ {
+ Api.DestroyDescriptorPool(Device, _pool, null);
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ unsafe
+ {
+ Api.DestroyDescriptorPool(Device, _pool, null);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+
+ private readonly Device _device;
+ private DescriptorPoolHolder _currentPool;
+
+ public DescriptorSetManager(Device device)
+ {
+ _device = device;
+ }
+
+ public Auto<DescriptorSetCollection> AllocateDescriptorSet(Vk api, DescriptorSetLayout layout)
+ {
+ Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
+ layouts[0] = layout;
+ return AllocateDescriptorSets(api, layouts);
+ }
+
+ public Auto<DescriptorSetCollection> AllocateDescriptorSets(Vk api, ReadOnlySpan<DescriptorSetLayout> layouts)
+ {
+ // If we fail the first time, just create a new pool and try again.
+ if (!GetPool(api, layouts.Length).TryAllocateDescriptorSets(layouts, out var dsc))
+ {
+ dsc = GetPool(api, layouts.Length).AllocateDescriptorSets(layouts);
+ }
+
+ return new Auto<DescriptorSetCollection>(dsc);
+ }
+
+ private DescriptorPoolHolder GetPool(Vk api, int requiredCount)
+ {
+ if (_currentPool == null || !_currentPool.CanFit(requiredCount))
+ {
+ _currentPool = new DescriptorPoolHolder(api, _device);
+ }
+
+ return _currentPool;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ unsafe
+ {
+ _currentPool?.Dispose();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
new file mode 100644
index 00000000..f708f794
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
@@ -0,0 +1,607 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Silk.NET.Vulkan;
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class DescriptorSetUpdater
+ {
+ private readonly VulkanRenderer _gd;
+ private readonly PipelineBase _pipeline;
+
+ private ShaderCollection _program;
+
+ private Auto<DisposableBuffer>[] _uniformBufferRefs;
+ private Auto<DisposableBuffer>[] _storageBufferRefs;
+ private Auto<DisposableImageView>[] _textureRefs;
+ private Auto<DisposableSampler>[] _samplerRefs;
+ private Auto<DisposableImageView>[] _imageRefs;
+ private TextureBuffer[] _bufferTextureRefs;
+ private TextureBuffer[] _bufferImageRefs;
+ private GAL.Format[] _bufferImageFormats;
+
+ private DescriptorBufferInfo[] _uniformBuffers;
+ private DescriptorBufferInfo[] _storageBuffers;
+ private DescriptorImageInfo[] _textures;
+ private DescriptorImageInfo[] _images;
+ private BufferView[] _bufferTextures;
+ private BufferView[] _bufferImages;
+
+ private bool[] _uniformSet;
+ private bool[] _storageSet;
+ private Silk.NET.Vulkan.Buffer _cachedSupportBuffer;
+
+ [Flags]
+ private enum DirtyFlags
+ {
+ None = 0,
+ Uniform = 1 << 0,
+ Storage = 1 << 1,
+ Texture = 1 << 2,
+ Image = 1 << 3,
+ All = Uniform | Storage | Texture | Image
+ }
+
+ private DirtyFlags _dirty;
+
+ private readonly BufferHolder _dummyBuffer;
+ private readonly TextureView _dummyTexture;
+ private readonly SamplerHolder _dummySampler;
+
+ public DescriptorSetUpdater(VulkanRenderer gd, PipelineBase pipeline)
+ {
+ _gd = gd;
+ _pipeline = pipeline;
+
+ // Some of the bindings counts needs to be multiplied by 2 because we have buffer and
+ // regular textures/images interleaved on the same descriptor set.
+
+ _uniformBufferRefs = new Auto<DisposableBuffer>[Constants.MaxUniformBufferBindings];
+ _storageBufferRefs = new Auto<DisposableBuffer>[Constants.MaxStorageBufferBindings];
+ _textureRefs = new Auto<DisposableImageView>[Constants.MaxTextureBindings * 2];
+ _samplerRefs = new Auto<DisposableSampler>[Constants.MaxTextureBindings * 2];
+ _imageRefs = new Auto<DisposableImageView>[Constants.MaxImageBindings * 2];
+ _bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
+ _bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
+ _bufferImageFormats = new GAL.Format[Constants.MaxImageBindings * 2];
+
+ _uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings];
+ _storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings];
+ _textures = new DescriptorImageInfo[Constants.MaxTexturesPerStage];
+ _images = new DescriptorImageInfo[Constants.MaxImagesPerStage];
+ _bufferTextures = new BufferView[Constants.MaxTexturesPerStage];
+ _bufferImages = new BufferView[Constants.MaxImagesPerStage];
+
+ var initialImageInfo = new DescriptorImageInfo()
+ {
+ ImageLayout = ImageLayout.General
+ };
+
+ _textures.AsSpan().Fill(initialImageInfo);
+ _images.AsSpan().Fill(initialImageInfo);
+
+ _uniformSet = new bool[Constants.MaxUniformBufferBindings];
+ _storageSet = new bool[Constants.MaxStorageBufferBindings];
+
+ if (gd.Capabilities.SupportsNullDescriptors)
+ {
+ // If null descriptors are supported, we can pass null as the handle.
+ _dummyBuffer = null;
+ }
+ else
+ {
+ // If null descriptors are not supported, we need to pass the handle of a dummy buffer on unused bindings.
+ _dummyBuffer = gd.BufferManager.Create(gd, 0x10000, forConditionalRendering: false, deviceLocal: true);
+ }
+
+ _dummyTexture = gd.CreateTextureView(new GAL.TextureCreateInfo(
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 4,
+ GAL.Format.R8G8B8A8Unorm,
+ DepthStencilMode.Depth,
+ Target.Texture2D,
+ SwizzleComponent.Red,
+ SwizzleComponent.Green,
+ SwizzleComponent.Blue,
+ SwizzleComponent.Alpha), 1f);
+
+ _dummySampler = (SamplerHolder)gd.CreateSampler(new GAL.SamplerCreateInfo(
+ MinFilter.Nearest,
+ MagFilter.Nearest,
+ false,
+ AddressMode.Repeat,
+ AddressMode.Repeat,
+ AddressMode.Repeat,
+ CompareMode.None,
+ GAL.CompareOp.Always,
+ new ColorF(0, 0, 0, 0),
+ 0,
+ 0,
+ 0,
+ 1f));
+ }
+
+ public void SetProgram(ShaderCollection program)
+ {
+ _program = program;
+ _dirty = DirtyFlags.All;
+ }
+
+ public void SetImage(int binding, ITexture image, GAL.Format imageFormat)
+ {
+ if (image == null)
+ {
+ return;
+ }
+
+ if (image is TextureBuffer imageBuffer)
+ {
+ _bufferImageRefs[binding] = imageBuffer;
+ _bufferImageFormats[binding] = imageFormat;
+ }
+ else if (image is TextureView view)
+ {
+ _imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView();
+ }
+
+ SignalDirty(DirtyFlags.Image);
+ }
+
+ public void SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<BufferRange> buffers)
+ {
+ for (int i = 0; i < buffers.Length; i++)
+ {
+ var buffer = buffers[i];
+ int index = first + i;
+
+ Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
+ ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
+
+ DescriptorBufferInfo info = new DescriptorBufferInfo()
+ {
+ Offset = (ulong)buffer.Offset,
+ Range = (ulong)buffer.Size
+ };
+ ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index];
+
+ if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range)
+ {
+ _storageSet[index] = false;
+
+ currentInfo = info;
+ currentVkBuffer = vkBuffer;
+ }
+ }
+
+ SignalDirty(DirtyFlags.Storage);
+ }
+
+ public void SetTextureAndSampler(CommandBufferScoped cbs, ShaderStage stage, int binding, ITexture texture, ISampler sampler)
+ {
+ if (texture == null)
+ {
+ return;
+ }
+
+ if (texture is TextureBuffer textureBuffer)
+ {
+ _bufferTextureRefs[binding] = textureBuffer;
+ }
+ else
+ {
+ TextureView view = (TextureView)texture;
+
+ view.Storage.InsertBarrier(cbs, AccessFlags.AccessShaderReadBit, stage.ConvertToPipelineStageFlags());
+
+ _textureRefs[binding] = view.GetImageView();
+ _samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
+ }
+
+ SignalDirty(DirtyFlags.Texture);
+ }
+
+ public void SetUniformBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<BufferRange> buffers)
+ {
+ for (int i = 0; i < buffers.Length; i++)
+ {
+ var buffer = buffers[i];
+ int index = first + i;
+
+ Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
+ ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index];
+
+ DescriptorBufferInfo info = new DescriptorBufferInfo()
+ {
+ Offset = (ulong)buffer.Offset,
+ Range = (ulong)buffer.Size
+ };
+ ref DescriptorBufferInfo currentInfo = ref _uniformBuffers[index];
+
+ if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range)
+ {
+ _uniformSet[index] = false;
+
+ currentInfo = info;
+ currentVkBuffer = vkBuffer;
+ }
+ }
+
+ SignalDirty(DirtyFlags.Uniform);
+ }
+
+ private void SignalDirty(DirtyFlags flag)
+ {
+ _dirty |= flag;
+ }
+
+ public void UpdateAndBindDescriptorSets(CommandBufferScoped cbs, PipelineBindPoint pbp)
+ {
+ if ((_dirty & DirtyFlags.All) == 0)
+ {
+ return;
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Uniform))
+ {
+ if (_program.UsePushDescriptors)
+ {
+ UpdateAndBindUniformBufferPd(cbs, pbp);
+ }
+ else
+ {
+ UpdateAndBind(cbs, PipelineBase.UniformSetIndex, pbp);
+ }
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Storage))
+ {
+ UpdateAndBind(cbs, PipelineBase.StorageSetIndex, pbp);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Texture))
+ {
+ UpdateAndBind(cbs, PipelineBase.TextureSetIndex, pbp);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Image))
+ {
+ UpdateAndBind(cbs, PipelineBase.ImageSetIndex, pbp);
+ }
+
+ _dirty = DirtyFlags.None;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void UpdateBuffer(
+ CommandBufferScoped cbs,
+ ref DescriptorBufferInfo info,
+ Auto<DisposableBuffer> buffer,
+ Auto<DisposableBuffer> dummyBuffer)
+ {
+ info.Buffer = buffer?.Get(cbs, (int)info.Offset, (int)info.Range).Value ?? default;
+
+ // The spec requires that buffers with null handle have offset as 0 and range as VK_WHOLE_SIZE.
+ if (info.Buffer.Handle == 0)
+ {
+ info.Buffer = dummyBuffer?.Get(cbs).Value ?? default;
+ info.Offset = 0;
+ info.Range = Vk.WholeSize;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPoint pbp)
+ {
+ var program = _program;
+ int stagesCount = program.Bindings[setIndex].Length;
+ if (stagesCount == 0 && setIndex != PipelineBase.UniformSetIndex)
+ {
+ return;
+ }
+
+ var dummyBuffer = _dummyBuffer?.GetBuffer();
+
+ var dsc = program.GetNewDescriptorSetCollection(_gd, cbs.CommandBufferIndex, setIndex, out var isNew).Get(cbs);
+
+ if (!program.HasMinimalLayout)
+ {
+ if (isNew)
+ {
+ Initialize(cbs, setIndex, dsc);
+ }
+
+ if (setIndex == PipelineBase.UniformSetIndex)
+ {
+ Span<DescriptorBufferInfo> uniformBuffer = stackalloc DescriptorBufferInfo[1];
+
+ if (!_uniformSet[0])
+ {
+ _cachedSupportBuffer = _gd.BufferManager.GetBuffer(cbs.CommandBuffer, _pipeline.SupportBufferUpdater.Handle, false).Get(cbs, 0, SupportBuffer.RequiredSize).Value;
+ _uniformSet[0] = true;
+ }
+
+ uniformBuffer[0] = new DescriptorBufferInfo()
+ {
+ Offset = 0,
+ Range = (ulong)SupportBuffer.RequiredSize,
+ Buffer = _cachedSupportBuffer
+ };
+
+ dsc.UpdateBuffers(0, 0, uniformBuffer, DescriptorType.UniformBuffer);
+ }
+ }
+
+ for (int stageIndex = 0; stageIndex < stagesCount; stageIndex++)
+ {
+ var stageBindings = program.Bindings[setIndex][stageIndex];
+ int bindingsCount = stageBindings.Length;
+ int count;
+
+ for (int bindingIndex = 0; bindingIndex < bindingsCount; bindingIndex += count)
+ {
+ int binding = stageBindings[bindingIndex];
+ count = 1;
+
+ while (bindingIndex + count < bindingsCount && stageBindings[bindingIndex + count] == binding + count)
+ {
+ count++;
+ }
+
+ if (setIndex == PipelineBase.UniformSetIndex)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ int index = binding + i;
+
+ if (!_uniformSet[index])
+ {
+ UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
+
+ _uniformSet[index] = true;
+ }
+ }
+
+ ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
+ dsc.UpdateBuffers(0, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
+ }
+ else if (setIndex == PipelineBase.StorageSetIndex)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ int index = binding + i;
+
+ if (!_storageSet[index])
+ {
+ UpdateBuffer(cbs, ref _storageBuffers[index], _storageBufferRefs[index], dummyBuffer);
+
+ _storageSet[index] = true;
+ }
+ }
+
+ ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
+ dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count));
+ }
+ else if (setIndex == PipelineBase.TextureSetIndex)
+ {
+ if (((uint)binding % (Constants.MaxTexturesPerStage * 2)) < Constants.MaxTexturesPerStage || program.HasMinimalLayout)
+ {
+ Span<DescriptorImageInfo> textures = _textures;
+
+ for (int i = 0; i < count; i++)
+ {
+ ref var texture = ref textures[i];
+
+ texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
+ texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
+
+ if (texture.ImageView.Handle == 0)
+ {
+ texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
+ }
+
+ if (texture.Sampler.Handle == 0)
+ {
+ texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
+ }
+ }
+
+ dsc.UpdateImages(0, binding, textures.Slice(0, count), DescriptorType.CombinedImageSampler);
+ }
+ else
+ {
+ Span<BufferView> bufferTextures = _bufferTextures;
+
+ for (int i = 0; i < count; i++)
+ {
+ bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default;
+ }
+
+ dsc.UpdateBufferImages(0, binding, bufferTextures.Slice(0, count), DescriptorType.UniformTexelBuffer);
+ }
+ }
+ else if (setIndex == PipelineBase.ImageSetIndex)
+ {
+ if (((uint)binding % (Constants.MaxImagesPerStage * 2)) < Constants.MaxImagesPerStage || program.HasMinimalLayout)
+ {
+ Span<DescriptorImageInfo> images = _images;
+
+ for (int i = 0; i < count; i++)
+ {
+ images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
+ }
+
+ dsc.UpdateImages(0, binding, images.Slice(0, count), DescriptorType.StorageImage);
+ }
+ else
+ {
+ Span<BufferView> bufferImages = _bufferImages;
+
+ for (int i = 0; i < count; i++)
+ {
+ bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i]) ?? default;
+ }
+
+ dsc.UpdateBufferImages(0, binding, bufferImages.Slice(0, count), DescriptorType.StorageTexelBuffer);
+ }
+ }
+ }
+ }
+
+ var sets = dsc.GetSets();
+
+ _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
+ }
+
+ private unsafe void UpdateBuffers(
+ CommandBufferScoped cbs,
+ PipelineBindPoint pbp,
+ int baseBinding,
+ ReadOnlySpan<DescriptorBufferInfo> bufferInfo,
+ DescriptorType type)
+ {
+ if (bufferInfo.Length == 0)
+ {
+ return;
+ }
+
+ fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
+ {
+ var writeDescriptorSet = new WriteDescriptorSet
+ {
+ SType = StructureType.WriteDescriptorSet,
+ DstBinding = (uint)baseBinding,
+ DescriptorType = type,
+ DescriptorCount = (uint)bufferInfo.Length,
+ PBufferInfo = pBufferInfo
+ };
+
+ _gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, _program.PipelineLayout, 0, 1, &writeDescriptorSet);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
+ {
+ var dummyBuffer = _dummyBuffer?.GetBuffer();
+ int stagesCount = _program.Bindings[PipelineBase.UniformSetIndex].Length;
+
+ if (!_uniformSet[0])
+ {
+ Span<DescriptorBufferInfo> uniformBuffer = stackalloc DescriptorBufferInfo[1];
+
+ uniformBuffer[0] = new DescriptorBufferInfo()
+ {
+ Offset = 0,
+ Range = (ulong)SupportBuffer.RequiredSize,
+ Buffer = _gd.BufferManager.GetBuffer(cbs.CommandBuffer, _pipeline.SupportBufferUpdater.Handle, false).Get(cbs, 0, SupportBuffer.RequiredSize).Value
+ };
+
+ _uniformSet[0] = true;
+
+ UpdateBuffers(cbs, pbp, 0, uniformBuffer, DescriptorType.UniformBuffer);
+ }
+
+ for (int stageIndex = 0; stageIndex < stagesCount; stageIndex++)
+ {
+ var stageBindings = _program.Bindings[PipelineBase.UniformSetIndex][stageIndex];
+ int bindingsCount = stageBindings.Length;
+ int count;
+
+ for (int bindingIndex = 0; bindingIndex < bindingsCount; bindingIndex += count)
+ {
+ int binding = stageBindings[bindingIndex];
+ count = 1;
+
+ while (bindingIndex + count < bindingsCount && stageBindings[bindingIndex + count] == binding + count)
+ {
+ count++;
+ }
+
+ bool doUpdate = false;
+
+ for (int i = 0; i < count; i++)
+ {
+ int index = binding + i;
+
+ if (!_uniformSet[index])
+ {
+ UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
+ _uniformSet[index] = true;
+ doUpdate = true;
+ }
+ }
+
+ if (doUpdate)
+ {
+ ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
+ UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
+ }
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
+ {
+ var dummyBuffer = _dummyBuffer?.GetBuffer().Get(cbs).Value ?? default;
+
+ uint stages = _program.Stages;
+
+ while (stages != 0)
+ {
+ int stage = BitOperations.TrailingZeroCount(stages);
+ stages &= ~(1u << stage);
+
+ if (setIndex == PipelineBase.UniformSetIndex)
+ {
+ dsc.InitializeBuffers(
+ 0,
+ 1 + stage * Constants.MaxUniformBuffersPerStage,
+ Constants.MaxUniformBuffersPerStage,
+ DescriptorType.UniformBuffer,
+ dummyBuffer);
+ }
+ else if (setIndex == PipelineBase.StorageSetIndex)
+ {
+ dsc.InitializeBuffers(
+ 0,
+ stage * Constants.MaxStorageBuffersPerStage,
+ Constants.MaxStorageBuffersPerStage,
+ DescriptorType.StorageBuffer,
+ dummyBuffer);
+ }
+ }
+ }
+
+ public void SignalCommandBufferChange()
+ {
+ _dirty = DirtyFlags.All;
+
+ Array.Clear(_uniformSet);
+ Array.Clear(_storageSet);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _dummyTexture.Dispose();
+ _dummySampler.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs b/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs
new file mode 100644
index 00000000..6d227ca2
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableBuffer : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Silk.NET.Vulkan.Buffer Value { get; }
+
+ public DisposableBuffer(Vk api, Device device, Silk.NET.Vulkan.Buffer buffer)
+ {
+ _api = api;
+ _device = device;
+ Value = buffer;
+ }
+
+ public void Dispose()
+ {
+ _api.DestroyBuffer(_device, Value, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs b/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs
new file mode 100644
index 00000000..7d3fe6ee
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableBufferView : System.IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public BufferView Value { get; }
+
+ public DisposableBufferView(Vk api, Device device, BufferView bufferView)
+ {
+ _api = api;
+ _device = device;
+ Value = bufferView;
+ }
+
+ public void Dispose()
+ {
+ _api.DestroyBufferView(_device, Value, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs b/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs
new file mode 100644
index 00000000..5f219a4a
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableFramebuffer : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Framebuffer Value { get; }
+
+ public DisposableFramebuffer(Vk api, Device device, Framebuffer framebuffer)
+ {
+ _api = api;
+ _device = device;
+ Value = framebuffer;
+ }
+
+ public void Dispose()
+ {
+ _api.DestroyFramebuffer(_device, Value, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableImage.cs b/Ryujinx.Graphics.Vulkan/DisposableImage.cs
new file mode 100644
index 00000000..4e9b3bd4
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableImage.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableImage : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Image Value { get; }
+
+ public DisposableImage(Vk api, Device device, Image image)
+ {
+ _api = api;
+ _device = device;
+ Value = image;
+ }
+
+ public void Dispose()
+ {
+ _api.DestroyImage(_device, Value, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableImageView.cs b/Ryujinx.Graphics.Vulkan/DisposableImageView.cs
new file mode 100644
index 00000000..3509858e
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableImageView.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableImageView : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public ImageView Value { get; }
+
+ public DisposableImageView(Vk api, Device device, ImageView imageView)
+ {
+ _api = api;
+ _device = device;
+ Value = imageView;
+ }
+
+ public void Dispose()
+ {
+ _api.DestroyImageView(_device, Value, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableMemory.cs b/Ryujinx.Graphics.Vulkan/DisposableMemory.cs
new file mode 100644
index 00000000..e0b5f099
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableMemory.cs
@@ -0,0 +1,24 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableMemory : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly DeviceMemory _memory;
+
+ public DisposableMemory(Vk api, Device device, DeviceMemory memory)
+ {
+ _api = api;
+ _device = device;
+ _memory = memory;
+ }
+
+ public void Dispose()
+ {
+ _api.FreeMemory(_device, _memory, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs b/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs
new file mode 100644
index 00000000..ff069f7e
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposablePipeline : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Pipeline Value { get; }
+
+ public DisposablePipeline(Vk api, Device device, Pipeline pipeline)
+ {
+ _api = api;
+ _device = device;
+ Value = pipeline;
+ }
+
+ public void Dispose()
+ {
+ _api.DestroyPipeline(_device, Value, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs b/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs
new file mode 100644
index 00000000..f561912a
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableRenderPass : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public RenderPass Value { get; }
+
+ public DisposableRenderPass(Vk api, Device device, RenderPass renderPass)
+ {
+ _api = api;
+ _device = device;
+ Value = renderPass;
+ }
+
+ public void Dispose()
+ {
+ _api.DestroyRenderPass(_device, Value, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/DisposableSampler.cs b/Ryujinx.Graphics.Vulkan/DisposableSampler.cs
new file mode 100644
index 00000000..0b93528f
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/DisposableSampler.cs
@@ -0,0 +1,25 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct DisposableSampler : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public Sampler Value { get; }
+
+ public DisposableSampler(Vk api, Device device, Sampler sampler)
+ {
+ _api = api;
+ _device = device;
+ Value = sampler;
+ }
+
+ public void Dispose()
+ {
+ _api.DestroySampler(_device, Value, Span<AllocationCallbacks>.Empty);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/EnumConversion.cs b/Ryujinx.Graphics.Vulkan/EnumConversion.cs
new file mode 100644
index 00000000..ab40cb10
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/EnumConversion.cs
@@ -0,0 +1,307 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Silk.NET.Vulkan;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class EnumConversion
+ {
+ public static ShaderStageFlags Convert(this ShaderStage stage)
+ {
+ return stage switch
+ {
+ ShaderStage.Vertex => ShaderStageFlags.ShaderStageVertexBit,
+ ShaderStage.Geometry => ShaderStageFlags.ShaderStageGeometryBit,
+ ShaderStage.TessellationControl => ShaderStageFlags.ShaderStageTessellationControlBit,
+ ShaderStage.TessellationEvaluation => ShaderStageFlags.ShaderStageTessellationEvaluationBit,
+ ShaderStage.Fragment => ShaderStageFlags.ShaderStageFragmentBit,
+ ShaderStage.Compute => ShaderStageFlags.ShaderStageComputeBit,
+ _ => LogInvalidAndReturn(stage, nameof(ShaderStage), (ShaderStageFlags)0)
+ };
+ }
+
+ public static PipelineStageFlags ConvertToPipelineStageFlags(this ShaderStage stage)
+ {
+ return stage switch
+ {
+ ShaderStage.Vertex => PipelineStageFlags.PipelineStageVertexShaderBit,
+ ShaderStage.Geometry => PipelineStageFlags.PipelineStageGeometryShaderBit,
+ ShaderStage.TessellationControl => PipelineStageFlags.PipelineStageTessellationControlShaderBit,
+ ShaderStage.TessellationEvaluation => PipelineStageFlags.PipelineStageTessellationEvaluationShaderBit,
+ ShaderStage.Fragment => PipelineStageFlags.PipelineStageFragmentShaderBit,
+ ShaderStage.Compute => PipelineStageFlags.PipelineStageComputeShaderBit,
+ _ => LogInvalidAndReturn(stage, nameof(ShaderStage), (PipelineStageFlags)0)
+ };
+ }
+
+ public static SamplerAddressMode Convert(this AddressMode mode)
+ {
+ return mode switch
+ {
+ AddressMode.Clamp => SamplerAddressMode.ClampToEdge, // TODO: Should be clamp.
+ AddressMode.Repeat => SamplerAddressMode.Repeat,
+ AddressMode.MirrorClamp => SamplerAddressMode.ClampToEdge, // TODO: Should be mirror clamp.
+ AddressMode.MirrorClampToEdge => SamplerAddressMode.MirrorClampToEdgeKhr,
+ AddressMode.MirrorClampToBorder => SamplerAddressMode.ClampToBorder, // TODO: Should be mirror clamp to border.
+ AddressMode.ClampToBorder => SamplerAddressMode.ClampToBorder,
+ AddressMode.MirroredRepeat => SamplerAddressMode.MirroredRepeat,
+ AddressMode.ClampToEdge => SamplerAddressMode.ClampToEdge,
+ _ => LogInvalidAndReturn(mode, nameof(AddressMode), SamplerAddressMode.ClampToEdge) // TODO: Should be clamp.
+ };
+ }
+
+ public static Silk.NET.Vulkan.BlendFactor Convert(this GAL.BlendFactor factor)
+ {
+ return factor switch
+ {
+ GAL.BlendFactor.Zero or GAL.BlendFactor.ZeroGl => Silk.NET.Vulkan.BlendFactor.Zero,
+ GAL.BlendFactor.One or GAL.BlendFactor.OneGl => Silk.NET.Vulkan.BlendFactor.One,
+ GAL.BlendFactor.SrcColor or GAL.BlendFactor.SrcColorGl => Silk.NET.Vulkan.BlendFactor.SrcColor,
+ GAL.BlendFactor.OneMinusSrcColor or GAL.BlendFactor.OneMinusSrcColorGl => Silk.NET.Vulkan.BlendFactor.OneMinusSrcColor,
+ GAL.BlendFactor.SrcAlpha or GAL.BlendFactor.SrcAlphaGl => Silk.NET.Vulkan.BlendFactor.SrcAlpha,
+ GAL.BlendFactor.OneMinusSrcAlpha or GAL.BlendFactor.OneMinusSrcAlphaGl => Silk.NET.Vulkan.BlendFactor.OneMinusSrcAlpha,
+ GAL.BlendFactor.DstAlpha or GAL.BlendFactor.DstAlphaGl => Silk.NET.Vulkan.BlendFactor.DstAlpha,
+ GAL.BlendFactor.OneMinusDstAlpha or GAL.BlendFactor.OneMinusDstAlphaGl => Silk.NET.Vulkan.BlendFactor.OneMinusDstAlpha,
+ GAL.BlendFactor.DstColor or GAL.BlendFactor.DstColorGl => Silk.NET.Vulkan.BlendFactor.DstColor,
+ GAL.BlendFactor.OneMinusDstColor or GAL.BlendFactor.OneMinusDstColorGl => Silk.NET.Vulkan.BlendFactor.OneMinusDstColor,
+ GAL.BlendFactor.SrcAlphaSaturate or GAL.BlendFactor.SrcAlphaSaturateGl => Silk.NET.Vulkan.BlendFactor.SrcAlphaSaturate,
+ GAL.BlendFactor.Src1Color or GAL.BlendFactor.Src1ColorGl => Silk.NET.Vulkan.BlendFactor.Src1Color,
+ GAL.BlendFactor.OneMinusSrc1Color or GAL.BlendFactor.OneMinusSrc1ColorGl => Silk.NET.Vulkan.BlendFactor.OneMinusSrc1Color,
+ GAL.BlendFactor.Src1Alpha or GAL.BlendFactor.Src1AlphaGl => Silk.NET.Vulkan.BlendFactor.Src1Alpha,
+ GAL.BlendFactor.OneMinusSrc1Alpha or GAL.BlendFactor.OneMinusSrc1AlphaGl => Silk.NET.Vulkan.BlendFactor.OneMinusSrc1Alpha,
+ GAL.BlendFactor.ConstantColor => Silk.NET.Vulkan.BlendFactor.ConstantColor,
+ GAL.BlendFactor.OneMinusConstantColor => Silk.NET.Vulkan.BlendFactor.OneMinusConstantColor,
+ GAL.BlendFactor.ConstantAlpha => Silk.NET.Vulkan.BlendFactor.ConstantAlpha,
+ GAL.BlendFactor.OneMinusConstantAlpha => Silk.NET.Vulkan.BlendFactor.OneMinusConstantAlpha,
+ _ => LogInvalidAndReturn(factor, nameof(GAL.BlendFactor), Silk.NET.Vulkan.BlendFactor.Zero)
+ };
+ }
+
+ public static Silk.NET.Vulkan.BlendOp Convert(this GAL.BlendOp op)
+ {
+ return op switch
+ {
+ GAL.BlendOp.Add or GAL.BlendOp.AddGl => Silk.NET.Vulkan.BlendOp.Add,
+ GAL.BlendOp.Subtract or GAL.BlendOp.SubtractGl => Silk.NET.Vulkan.BlendOp.Subtract,
+ GAL.BlendOp.ReverseSubtract or GAL.BlendOp.ReverseSubtractGl => Silk.NET.Vulkan.BlendOp.ReverseSubtract,
+ GAL.BlendOp.Minimum or GAL.BlendOp.MinimumGl => Silk.NET.Vulkan.BlendOp.Min,
+ GAL.BlendOp.Maximum or GAL.BlendOp.MaximumGl => Silk.NET.Vulkan.BlendOp.Max,
+ _ => LogInvalidAndReturn(op, nameof(GAL.BlendOp), Silk.NET.Vulkan.BlendOp.Add)
+ };
+ }
+
+ public static Silk.NET.Vulkan.CompareOp Convert(this GAL.CompareOp op)
+ {
+ return op switch
+ {
+ GAL.CompareOp.Never or GAL.CompareOp.NeverGl => Silk.NET.Vulkan.CompareOp.Never,
+ GAL.CompareOp.Less or GAL.CompareOp.LessGl => Silk.NET.Vulkan.CompareOp.Less,
+ GAL.CompareOp.Equal or GAL.CompareOp.EqualGl => Silk.NET.Vulkan.CompareOp.Equal,
+ GAL.CompareOp.LessOrEqual or GAL.CompareOp.LessOrEqualGl => Silk.NET.Vulkan.CompareOp.LessOrEqual,
+ GAL.CompareOp.Greater or GAL.CompareOp.GreaterGl => Silk.NET.Vulkan.CompareOp.Greater,
+ GAL.CompareOp.NotEqual or GAL.CompareOp.NotEqualGl => Silk.NET.Vulkan.CompareOp.NotEqual,
+ GAL.CompareOp.GreaterOrEqual or GAL.CompareOp.GreaterOrEqualGl => Silk.NET.Vulkan.CompareOp.GreaterOrEqual,
+ GAL.CompareOp.Always or GAL.CompareOp.AlwaysGl => Silk.NET.Vulkan.CompareOp.Always,
+ _ => LogInvalidAndReturn(op, nameof(GAL.CompareOp), Silk.NET.Vulkan.CompareOp.Never)
+ };
+ }
+
+ public static CullModeFlags Convert(this Face face)
+ {
+ return face switch
+ {
+ Face.Back => CullModeFlags.CullModeBackBit,
+ Face.Front => CullModeFlags.CullModeFrontBit,
+ Face.FrontAndBack => CullModeFlags.CullModeFrontAndBack,
+ _ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.CullModeBackBit)
+ };
+ }
+
+ public static Silk.NET.Vulkan.FrontFace Convert(this GAL.FrontFace frontFace)
+ {
+ // Flipped to account for origin differences.
+ return frontFace switch
+ {
+ GAL.FrontFace.Clockwise => Silk.NET.Vulkan.FrontFace.CounterClockwise,
+ GAL.FrontFace.CounterClockwise => Silk.NET.Vulkan.FrontFace.Clockwise,
+ _ => LogInvalidAndReturn(frontFace, nameof(GAL.FrontFace), Silk.NET.Vulkan.FrontFace.Clockwise)
+ };
+ }
+
+ public static Silk.NET.Vulkan.IndexType Convert(this GAL.IndexType type)
+ {
+ return type switch
+ {
+ GAL.IndexType.UByte => Silk.NET.Vulkan.IndexType.Uint8Ext,
+ GAL.IndexType.UShort => Silk.NET.Vulkan.IndexType.Uint16,
+ GAL.IndexType.UInt => Silk.NET.Vulkan.IndexType.Uint32,
+ _ => LogInvalidAndReturn(type, nameof(GAL.IndexType), Silk.NET.Vulkan.IndexType.Uint16)
+ };
+ }
+
+ public static Filter Convert(this MagFilter filter)
+ {
+ return filter switch
+ {
+ MagFilter.Nearest => Filter.Nearest,
+ MagFilter.Linear => Filter.Linear,
+ _ => LogInvalidAndReturn(filter, nameof(MagFilter), Filter.Nearest)
+ };
+ }
+
+ public static (Filter, SamplerMipmapMode) Convert(this MinFilter filter)
+ {
+ return filter switch
+ {
+ MinFilter.Nearest => (Filter.Nearest, SamplerMipmapMode.Nearest),
+ MinFilter.Linear => (Filter.Linear, SamplerMipmapMode.Nearest),
+ MinFilter.NearestMipmapNearest => (Filter.Nearest, SamplerMipmapMode.Nearest),
+ MinFilter.LinearMipmapNearest => (Filter.Linear, SamplerMipmapMode.Nearest),
+ MinFilter.NearestMipmapLinear => (Filter.Nearest, SamplerMipmapMode.Linear),
+ MinFilter.LinearMipmapLinear => (Filter.Linear, SamplerMipmapMode.Linear),
+ _ => LogInvalidAndReturn(filter, nameof(MinFilter), (Filter.Nearest, SamplerMipmapMode.Nearest))
+ };
+ }
+
+ public static Silk.NET.Vulkan.PrimitiveTopology Convert(this GAL.PrimitiveTopology topology)
+ {
+ return topology switch
+ {
+ GAL.PrimitiveTopology.Points => Silk.NET.Vulkan.PrimitiveTopology.PointList,
+ GAL.PrimitiveTopology.Lines => Silk.NET.Vulkan.PrimitiveTopology.LineList,
+ GAL.PrimitiveTopology.LineStrip => Silk.NET.Vulkan.PrimitiveTopology.LineStrip,
+ GAL.PrimitiveTopology.Triangles => Silk.NET.Vulkan.PrimitiveTopology.TriangleList,
+ GAL.PrimitiveTopology.TriangleStrip => Silk.NET.Vulkan.PrimitiveTopology.TriangleStrip,
+ GAL.PrimitiveTopology.TriangleFan => Silk.NET.Vulkan.PrimitiveTopology.TriangleFan,
+ GAL.PrimitiveTopology.LinesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.LineListWithAdjacency,
+ GAL.PrimitiveTopology.LineStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.LineStripWithAdjacency,
+ GAL.PrimitiveTopology.TrianglesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency,
+ GAL.PrimitiveTopology.TriangleStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency,
+ GAL.PrimitiveTopology.Patches => Silk.NET.Vulkan.PrimitiveTopology.PatchList,
+ GAL.PrimitiveTopology.Quads => Silk.NET.Vulkan.PrimitiveTopology.TriangleFan, // Emulated with triangle fans
+ GAL.PrimitiveTopology.QuadStrip => Silk.NET.Vulkan.PrimitiveTopology.TriangleStrip, // Emulated with triangle strips
+ _ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), Silk.NET.Vulkan.PrimitiveTopology.TriangleList)
+ };
+ }
+
+ public static Silk.NET.Vulkan.StencilOp Convert(this GAL.StencilOp op)
+ {
+ return op switch
+ {
+ GAL.StencilOp.Keep or GAL.StencilOp.KeepGl => Silk.NET.Vulkan.StencilOp.Keep,
+ GAL.StencilOp.Zero or GAL.StencilOp.ZeroGl => Silk.NET.Vulkan.StencilOp.Zero,
+ GAL.StencilOp.Replace or GAL.StencilOp.ReplaceGl => Silk.NET.Vulkan.StencilOp.Replace,
+ GAL.StencilOp.IncrementAndClamp or GAL.StencilOp.IncrementAndClampGl => Silk.NET.Vulkan.StencilOp.IncrementAndClamp,
+ GAL.StencilOp.DecrementAndClamp or GAL.StencilOp.DecrementAndClampGl => Silk.NET.Vulkan.StencilOp.DecrementAndClamp,
+ GAL.StencilOp.Invert or GAL.StencilOp.InvertGl => Silk.NET.Vulkan.StencilOp.Invert,
+ GAL.StencilOp.IncrementAndWrap or GAL.StencilOp.IncrementAndWrapGl => Silk.NET.Vulkan.StencilOp.IncrementAndWrap,
+ GAL.StencilOp.DecrementAndWrap or GAL.StencilOp.DecrementAndWrapGl => Silk.NET.Vulkan.StencilOp.DecrementAndWrap,
+ _ => LogInvalidAndReturn(op, nameof(GAL.StencilOp), Silk.NET.Vulkan.StencilOp.Keep)
+ };
+ }
+
+ public static ComponentSwizzle Convert(this SwizzleComponent swizzleComponent)
+ {
+ return swizzleComponent switch
+ {
+ SwizzleComponent.Zero => ComponentSwizzle.Zero,
+ SwizzleComponent.One => ComponentSwizzle.One,
+ SwizzleComponent.Red => ComponentSwizzle.R,
+ SwizzleComponent.Green => ComponentSwizzle.G,
+ SwizzleComponent.Blue => ComponentSwizzle.B,
+ SwizzleComponent.Alpha => ComponentSwizzle.A,
+ _ => LogInvalidAndReturn(swizzleComponent, nameof(SwizzleComponent), ComponentSwizzle.Zero)
+ };
+ }
+
+ public static ImageType Convert(this Target target)
+ {
+ return target switch
+ {
+ Target.Texture1D or
+ Target.Texture1DArray or
+ Target.TextureBuffer => ImageType.ImageType1D,
+ Target.Texture2D or
+ Target.Texture2DArray or
+ Target.Texture2DMultisample or
+ Target.Cubemap or
+ Target.CubemapArray => ImageType.ImageType2D,
+ Target.Texture3D => ImageType.ImageType3D,
+ _ => LogInvalidAndReturn(target, nameof(Target), ImageType.ImageType2D)
+ };
+ }
+
+ public static ImageViewType ConvertView(this Target target)
+ {
+ return target switch
+ {
+ Target.Texture1D => ImageViewType.ImageViewType1D,
+ Target.Texture2D or Target.Texture2DMultisample => ImageViewType.ImageViewType2D,
+ Target.Texture3D => ImageViewType.ImageViewType3D,
+ Target.Texture1DArray => ImageViewType.ImageViewType1DArray,
+ Target.Texture2DArray => ImageViewType.ImageViewType2DArray,
+ Target.Cubemap => ImageViewType.Cube,
+ Target.CubemapArray => ImageViewType.CubeArray,
+ _ => LogInvalidAndReturn(target, nameof(Target), ImageViewType.ImageViewType2D)
+ };
+ }
+
+ public static ImageAspectFlags ConvertAspectFlags(this GAL.Format format)
+ {
+ return format switch
+ {
+ GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.ImageAspectDepthBit,
+ GAL.Format.S8Uint => ImageAspectFlags.ImageAspectStencilBit,
+ GAL.Format.D24UnormS8Uint or
+ GAL.Format.D32FloatS8Uint or
+ GAL.Format.S8UintD24Unorm => ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit,
+ _ => ImageAspectFlags.ImageAspectColorBit
+ };
+ }
+
+ public static ImageAspectFlags ConvertAspectFlags(this GAL.Format format, DepthStencilMode depthStencilMode)
+ {
+ return format switch
+ {
+ GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.ImageAspectDepthBit,
+ GAL.Format.S8Uint => ImageAspectFlags.ImageAspectStencilBit,
+ GAL.Format.D24UnormS8Uint or
+ GAL.Format.D32FloatS8Uint or
+ GAL.Format.S8UintD24Unorm => depthStencilMode == DepthStencilMode.Stencil ? ImageAspectFlags.ImageAspectStencilBit : ImageAspectFlags.ImageAspectDepthBit,
+ _ => ImageAspectFlags.ImageAspectColorBit
+ };
+ }
+
+ public static LogicOp Convert(this LogicalOp op)
+ {
+ return op switch
+ {
+ LogicalOp.Clear => LogicOp.Clear,
+ LogicalOp.And => LogicOp.And,
+ LogicalOp.AndReverse => LogicOp.AndReverse,
+ LogicalOp.Copy => LogicOp.Copy,
+ LogicalOp.AndInverted => LogicOp.AndInverted,
+ LogicalOp.Noop => LogicOp.NoOp,
+ LogicalOp.Xor => LogicOp.Xor,
+ LogicalOp.Or => LogicOp.Or,
+ LogicalOp.Nor => LogicOp.Nor,
+ LogicalOp.Equiv => LogicOp.Equivalent,
+ LogicalOp.Invert => LogicOp.Invert,
+ LogicalOp.OrReverse => LogicOp.OrReverse,
+ LogicalOp.CopyInverted => LogicOp.CopyInverted,
+ LogicalOp.OrInverted => LogicOp.OrInverted,
+ LogicalOp.Nand => LogicOp.Nand,
+ LogicalOp.Set => LogicOp.Set,
+ _ => LogInvalidAndReturn(op, nameof(LogicalOp), LogicOp.Copy)
+ };
+ }
+
+ private static T2 LogInvalidAndReturn<T1, T2>(T1 value, string name, T2 defaultValue = default)
+ {
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {name} enum value: {value}.");
+
+ return defaultValue;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FenceHelper.cs b/Ryujinx.Graphics.Vulkan/FenceHelper.cs
new file mode 100644
index 00000000..d6731c0e
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FenceHelper.cs
@@ -0,0 +1,30 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class FenceHelper
+ {
+ private const ulong DefaultTimeout = 100000000; // 100ms
+
+ public static bool AnySignaled(Vk api, Device device, ReadOnlySpan<Fence> fences, ulong timeout = 0)
+ {
+ return api.WaitForFences(device, (uint)fences.Length, fences, false, timeout) == Result.Success;
+ }
+
+ public static bool AllSignaled(Vk api, Device device, ReadOnlySpan<Fence> fences, ulong timeout = 0)
+ {
+ return api.WaitForFences(device, (uint)fences.Length, fences, true, timeout) == Result.Success;
+ }
+
+ public static void WaitAllIndefinitely(Vk api, Device device, ReadOnlySpan<Fence> fences)
+ {
+ Result result;
+ while ((result = api.WaitForFences(device, (uint)fences.Length, fences, true, DefaultTimeout)) == Result.Timeout)
+ {
+ // Keep waiting while the fence is not signaled.
+ }
+ result.ThrowOnError();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FenceHolder.cs b/Ryujinx.Graphics.Vulkan/FenceHolder.cs
new file mode 100644
index 00000000..1c1e6240
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FenceHolder.cs
@@ -0,0 +1,79 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class FenceHolder : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+ private Fence _fence;
+ private int _referenceCount;
+ private bool _disposed;
+
+ public unsafe FenceHolder(Vk api, Device device)
+ {
+ _api = api;
+ _device = device;
+
+ var fenceCreateInfo = new FenceCreateInfo()
+ {
+ SType = StructureType.FenceCreateInfo
+ };
+
+ api.CreateFence(device, in fenceCreateInfo, null, out _fence).ThrowOnError();
+
+ _referenceCount = 1;
+ }
+
+ public Fence GetUnsafe()
+ {
+ return _fence;
+ }
+
+ public Fence Get()
+ {
+ Interlocked.Increment(ref _referenceCount);
+ return _fence;
+ }
+
+ public void Put()
+ {
+ if (Interlocked.Decrement(ref _referenceCount) == 0)
+ {
+ _api.DestroyFence(_device, _fence, Span<AllocationCallbacks>.Empty);
+ _fence = default;
+ }
+ }
+
+ public void Wait()
+ {
+ Span<Fence> fences = stackalloc Fence[]
+ {
+ _fence
+ };
+
+ FenceHelper.WaitAllIndefinitely(_api, _device, fences);
+ }
+
+ public bool IsSignaled()
+ {
+ Span<Fence> fences = stackalloc Fence[]
+ {
+ _fence
+ };
+
+ return FenceHelper.AllSignaled(_api, _device, fences);
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ Put();
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs b/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
new file mode 100644
index 00000000..6159f2cc
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
@@ -0,0 +1,93 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class FormatCapabilities
+ {
+ private readonly FormatFeatureFlags[] _table;
+
+ private readonly Vk _api;
+ private readonly PhysicalDevice _physicalDevice;
+
+ public FormatCapabilities(Vk api, PhysicalDevice physicalDevice)
+ {
+ _api = api;
+ _physicalDevice = physicalDevice;
+ _table = new FormatFeatureFlags[Enum.GetNames(typeof(GAL.Format)).Length];
+ }
+
+ public bool FormatsSupports(FormatFeatureFlags flags, params GAL.Format[] formats)
+ {
+ foreach (GAL.Format format in formats)
+ {
+ if (!FormatSupports(flags, format))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public bool FormatSupports(FormatFeatureFlags flags, GAL.Format format)
+ {
+ var formatFeatureFlags = _table[(int)format];
+
+ if (formatFeatureFlags == 0)
+ {
+ _api.GetPhysicalDeviceFormatProperties(_physicalDevice, FormatTable.GetFormat(format), out var fp);
+ formatFeatureFlags = fp.OptimalTilingFeatures;
+ _table[(int)format] = formatFeatureFlags;
+ }
+
+ return (formatFeatureFlags & flags) == flags;
+ }
+
+ public VkFormat ConvertToVkFormat(GAL.Format srcFormat)
+ {
+ var format = FormatTable.GetFormat(srcFormat);
+
+ var requiredFeatures = FormatFeatureFlags.FormatFeatureSampledImageBit |
+ FormatFeatureFlags.FormatFeatureTransferSrcBit |
+ FormatFeatureFlags.FormatFeatureTransferDstBit;
+
+ if (srcFormat.IsDepthOrStencil())
+ {
+ requiredFeatures |= FormatFeatureFlags.FormatFeatureDepthStencilAttachmentBit;
+ }
+ else if (srcFormat.IsRtColorCompatible())
+ {
+ requiredFeatures |= FormatFeatureFlags.FormatFeatureColorAttachmentBit;
+ }
+
+ if (srcFormat.IsImageCompatible())
+ {
+ requiredFeatures |= FormatFeatureFlags.FormatFeatureStorageImageBit;
+ }
+
+ if (!FormatSupports(requiredFeatures, srcFormat) || (IsD24S8(srcFormat) && VulkanConfiguration.ForceD24S8Unsupported))
+ {
+ // The format is not supported. Can we convert it to a higher precision format?
+ if (IsD24S8(srcFormat))
+ {
+ format = VkFormat.D32SfloatS8Uint;
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Format {srcFormat} is not supported by the host.");
+ }
+ }
+
+ return format;
+ }
+
+ public static bool IsD24S8(GAL.Format format)
+ {
+ return format == GAL.Format.D24UnormS8Uint || format == GAL.Format.S8UintD24Unorm;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FormatConverter.cs b/Ryujinx.Graphics.Vulkan/FormatConverter.cs
new file mode 100644
index 00000000..33472ae4
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FormatConverter.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class FormatConverter
+ {
+ public static void ConvertD24S8ToD32FS8(Span<byte> output, ReadOnlySpan<byte> input)
+ {
+ const float UnormToFloat = 1f / 0xffffff;
+
+ Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output);
+ ReadOnlySpan<uint> inputUint = MemoryMarshal.Cast<byte, uint>(input);
+
+ int i = 0;
+
+ for (; i < inputUint.Length; i++)
+ {
+ uint depthStencil = inputUint[i];
+ uint depth = depthStencil >> 8;
+ uint stencil = depthStencil & 0xff;
+
+ int j = i * 2;
+
+ outputUint[j] = (uint)BitConverter.SingleToInt32Bits(depth * UnormToFloat);
+ outputUint[j + 1] = stencil;
+ }
+ }
+
+ public static void ConvertD32FS8ToD24S8(Span<byte> output, ReadOnlySpan<byte> input)
+ {
+ Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output);
+ ReadOnlySpan<uint> inputUint = MemoryMarshal.Cast<byte, uint>(input);
+
+ int i = 0;
+
+ for (; i < inputUint.Length; i += 2)
+ {
+ float depth = BitConverter.Int32BitsToSingle((int)inputUint[i]);
+ uint stencil = inputUint[i + 1];
+ uint depthStencil = (Math.Clamp((uint)(depth * 0xffffff), 0, 0xffffff) << 8) | (stencil & 0xff);
+
+ int j = i >> 1;
+
+ outputUint[j] = depthStencil;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FormatTable.cs b/Ryujinx.Graphics.Vulkan/FormatTable.cs
new file mode 100644
index 00000000..439d492c
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FormatTable.cs
@@ -0,0 +1,182 @@
+using Ryujinx.Graphics.GAL;
+using System;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class FormatTable
+ {
+ private static readonly VkFormat[] Table;
+
+ static FormatTable()
+ {
+ Table = new VkFormat[Enum.GetNames(typeof(Format)).Length];
+
+ Add(Format.R8Unorm, VkFormat.R8Unorm);
+ Add(Format.R8Snorm, VkFormat.R8SNorm);
+ Add(Format.R8Uint, VkFormat.R8Uint);
+ Add(Format.R8Sint, VkFormat.R8Sint);
+ Add(Format.R16Float, VkFormat.R16Sfloat);
+ Add(Format.R16Unorm, VkFormat.R16Unorm);
+ Add(Format.R16Snorm, VkFormat.R16SNorm);
+ Add(Format.R16Uint, VkFormat.R16Uint);
+ Add(Format.R16Sint, VkFormat.R16Sint);
+ Add(Format.R32Float, VkFormat.R32Sfloat);
+ Add(Format.R32Uint, VkFormat.R32Uint);
+ Add(Format.R32Sint, VkFormat.R32Sint);
+ Add(Format.R8G8Unorm, VkFormat.R8G8Unorm);
+ Add(Format.R8G8Snorm, VkFormat.R8G8SNorm);
+ Add(Format.R8G8Uint, VkFormat.R8G8Uint);
+ Add(Format.R8G8Sint, VkFormat.R8G8Sint);
+ Add(Format.R16G16Float, VkFormat.R16G16Sfloat);
+ Add(Format.R16G16Unorm, VkFormat.R16G16Unorm);
+ Add(Format.R16G16Snorm, VkFormat.R16G16SNorm);
+ Add(Format.R16G16Uint, VkFormat.R16G16Uint);
+ Add(Format.R16G16Sint, VkFormat.R16G16Sint);
+ Add(Format.R32G32Float, VkFormat.R32G32Sfloat);
+ Add(Format.R32G32Uint, VkFormat.R32G32Uint);
+ Add(Format.R32G32Sint, VkFormat.R32G32Sint);
+ Add(Format.R8G8B8Unorm, VkFormat.R8G8B8Unorm);
+ Add(Format.R8G8B8Snorm, VkFormat.R8G8B8SNorm);
+ Add(Format.R8G8B8Uint, VkFormat.R8G8B8Uint);
+ Add(Format.R8G8B8Sint, VkFormat.R8G8B8Sint);
+ Add(Format.R16G16B16Float, VkFormat.R16G16B16Sfloat);
+ Add(Format.R16G16B16Unorm, VkFormat.R16G16B16Unorm);
+ Add(Format.R16G16B16Snorm, VkFormat.R16G16B16SNorm);
+ Add(Format.R16G16B16Uint, VkFormat.R16G16B16Uint);
+ Add(Format.R16G16B16Sint, VkFormat.R16G16B16Sint);
+ Add(Format.R32G32B32Float, VkFormat.R32G32B32Sfloat);
+ Add(Format.R32G32B32Uint, VkFormat.R32G32B32Uint);
+ Add(Format.R32G32B32Sint, VkFormat.R32G32B32Sint);
+ Add(Format.R8G8B8A8Unorm, VkFormat.R8G8B8A8Unorm);
+ Add(Format.R8G8B8A8Snorm, VkFormat.R8G8B8A8SNorm);
+ Add(Format.R8G8B8A8Uint, VkFormat.R8G8B8A8Uint);
+ Add(Format.R8G8B8A8Sint, VkFormat.R8G8B8A8Sint);
+ Add(Format.R16G16B16A16Float, VkFormat.R16G16B16A16Sfloat);
+ Add(Format.R16G16B16A16Unorm, VkFormat.R16G16B16A16Unorm);
+ Add(Format.R16G16B16A16Snorm, VkFormat.R16G16B16A16SNorm);
+ Add(Format.R16G16B16A16Uint, VkFormat.R16G16B16A16Uint);
+ Add(Format.R16G16B16A16Sint, VkFormat.R16G16B16A16Sint);
+ Add(Format.R32G32B32A32Float, VkFormat.R32G32B32A32Sfloat);
+ Add(Format.R32G32B32A32Uint, VkFormat.R32G32B32A32Uint);
+ Add(Format.R32G32B32A32Sint, VkFormat.R32G32B32A32Sint);
+ Add(Format.S8Uint, VkFormat.S8Uint);
+ Add(Format.D16Unorm, VkFormat.D16Unorm);
+ Add(Format.S8UintD24Unorm, VkFormat.D24UnormS8Uint);
+ Add(Format.D32Float, VkFormat.D32Sfloat);
+ Add(Format.D24UnormS8Uint, VkFormat.D24UnormS8Uint);
+ Add(Format.D32FloatS8Uint, VkFormat.D32SfloatS8Uint);
+ Add(Format.R8G8B8X8Srgb, VkFormat.R8G8B8Srgb);
+ Add(Format.R8G8B8A8Srgb, VkFormat.R8G8B8A8Srgb);
+ Add(Format.R4G4Unorm, VkFormat.R4G4UnormPack8);
+ Add(Format.R4G4B4A4Unorm, VkFormat.R4G4B4A4UnormPack16);
+ Add(Format.R5G5B5X1Unorm, VkFormat.A1R5G5B5UnormPack16);
+ Add(Format.R5G5B5A1Unorm, VkFormat.A1R5G5B5UnormPack16);
+ Add(Format.R5G6B5Unorm, VkFormat.R5G6B5UnormPack16);
+ Add(Format.R10G10B10A2Unorm, VkFormat.A2B10G10R10UnormPack32);
+ Add(Format.R10G10B10A2Uint, VkFormat.A2B10G10R10UintPack32);
+ Add(Format.R11G11B10Float, VkFormat.B10G11R11UfloatPack32);
+ Add(Format.R9G9B9E5Float, VkFormat.E5B9G9R9UfloatPack32);
+ Add(Format.Bc1RgbaUnorm, VkFormat.BC1RgbaUnormBlock);
+ Add(Format.Bc2Unorm, VkFormat.BC2UnormBlock);
+ Add(Format.Bc3Unorm, VkFormat.BC3UnormBlock);
+ Add(Format.Bc1RgbaSrgb, VkFormat.BC1RgbaSrgbBlock);
+ Add(Format.Bc2Srgb, VkFormat.BC2SrgbBlock);
+ Add(Format.Bc3Srgb, VkFormat.BC3SrgbBlock);
+ Add(Format.Bc4Unorm, VkFormat.BC4UnormBlock);
+ Add(Format.Bc4Snorm, VkFormat.BC4SNormBlock);
+ Add(Format.Bc5Unorm, VkFormat.BC5UnormBlock);
+ Add(Format.Bc5Snorm, VkFormat.BC5SNormBlock);
+ Add(Format.Bc7Unorm, VkFormat.BC7UnormBlock);
+ Add(Format.Bc7Srgb, VkFormat.BC7SrgbBlock);
+ Add(Format.Bc6HSfloat, VkFormat.BC6HSfloatBlock);
+ Add(Format.Bc6HUfloat, VkFormat.BC6HUfloatBlock);
+ Add(Format.R8Uscaled, VkFormat.R8Uscaled);
+ Add(Format.R8Sscaled, VkFormat.R8Sscaled);
+ Add(Format.R16Uscaled, VkFormat.R16Uscaled);
+ Add(Format.R16Sscaled, VkFormat.R16Sscaled);
+ // Add(Format.R32Uscaled, VkFormat.R32Uscaled);
+ // Add(Format.R32Sscaled, VkFormat.R32Sscaled);
+ Add(Format.R8G8Uscaled, VkFormat.R8G8Uscaled);
+ Add(Format.R8G8Sscaled, VkFormat.R8G8Sscaled);
+ Add(Format.R16G16Uscaled, VkFormat.R16G16Uscaled);
+ Add(Format.R16G16Sscaled, VkFormat.R16G16Sscaled);
+ // Add(Format.R32G32Uscaled, VkFormat.R32G32Uscaled);
+ // Add(Format.R32G32Sscaled, VkFormat.R32G32Sscaled);
+ Add(Format.R8G8B8Uscaled, VkFormat.R8G8B8Uscaled);
+ Add(Format.R8G8B8Sscaled, VkFormat.R8G8B8Sscaled);
+ Add(Format.R16G16B16Uscaled, VkFormat.R16G16B16Uscaled);
+ Add(Format.R16G16B16Sscaled, VkFormat.R16G16B16Sscaled);
+ // Add(Format.R32G32B32Uscaled, VkFormat.R32G32B32Uscaled);
+ // Add(Format.R32G32B32Sscaled, VkFormat.R32G32B32Sscaled);
+ Add(Format.R8G8B8A8Uscaled, VkFormat.R8G8B8A8Uscaled);
+ Add(Format.R8G8B8A8Sscaled, VkFormat.R8G8B8A8Sscaled);
+ Add(Format.R16G16B16A16Uscaled, VkFormat.R16G16B16A16Uscaled);
+ Add(Format.R16G16B16A16Sscaled, VkFormat.R16G16B16A16Sscaled);
+ // Add(Format.R32G32B32A32Uscaled, VkFormat.R32G32B32A32Uscaled);
+ // Add(Format.R32G32B32A32Sscaled, VkFormat.R32G32B32A32Sscaled);
+ Add(Format.R10G10B10A2Snorm, VkFormat.A2B10G10R10SNormPack32);
+ Add(Format.R10G10B10A2Sint, VkFormat.A2B10G10R10SintPack32);
+ Add(Format.R10G10B10A2Uscaled, VkFormat.A2B10G10R10UscaledPack32);
+ Add(Format.R10G10B10A2Sscaled, VkFormat.A2B10G10R10SscaledPack32);
+ Add(Format.R8G8B8X8Unorm, VkFormat.R8G8B8Unorm);
+ Add(Format.R8G8B8X8Snorm, VkFormat.R8G8B8SNorm);
+ Add(Format.R8G8B8X8Uint, VkFormat.R8G8B8Uint);
+ Add(Format.R8G8B8X8Sint, VkFormat.R8G8B8Sint);
+ Add(Format.R16G16B16X16Float, VkFormat.R16G16B16Sfloat);
+ Add(Format.R16G16B16X16Unorm, VkFormat.R16G16B16Unorm);
+ Add(Format.R16G16B16X16Snorm, VkFormat.R16G16B16SNorm);
+ Add(Format.R16G16B16X16Uint, VkFormat.R16G16B16Uint);
+ Add(Format.R16G16B16X16Sint, VkFormat.R16G16B16Sint);
+ Add(Format.R32G32B32X32Float, VkFormat.R32G32B32Sfloat);
+ Add(Format.R32G32B32X32Uint, VkFormat.R32G32B32Uint);
+ Add(Format.R32G32B32X32Sint, VkFormat.R32G32B32Sint);
+ Add(Format.Astc4x4Unorm, VkFormat.Astc4x4UnormBlock);
+ Add(Format.Astc5x4Unorm, VkFormat.Astc5x4UnormBlock);
+ Add(Format.Astc5x5Unorm, VkFormat.Astc5x5UnormBlock);
+ Add(Format.Astc6x5Unorm, VkFormat.Astc6x5UnormBlock);
+ Add(Format.Astc6x6Unorm, VkFormat.Astc6x6UnormBlock);
+ Add(Format.Astc8x5Unorm, VkFormat.Astc8x5UnormBlock);
+ Add(Format.Astc8x6Unorm, VkFormat.Astc8x6UnormBlock);
+ Add(Format.Astc8x8Unorm, VkFormat.Astc8x8UnormBlock);
+ Add(Format.Astc10x5Unorm, VkFormat.Astc10x5UnormBlock);
+ Add(Format.Astc10x6Unorm, VkFormat.Astc10x6UnormBlock);
+ Add(Format.Astc10x8Unorm, VkFormat.Astc10x8UnormBlock);
+ Add(Format.Astc10x10Unorm, VkFormat.Astc10x10UnormBlock);
+ Add(Format.Astc12x10Unorm, VkFormat.Astc12x10UnormBlock);
+ Add(Format.Astc12x12Unorm, VkFormat.Astc12x12UnormBlock);
+ Add(Format.Astc4x4Srgb, VkFormat.Astc4x4SrgbBlock);
+ Add(Format.Astc5x4Srgb, VkFormat.Astc5x4SrgbBlock);
+ Add(Format.Astc5x5Srgb, VkFormat.Astc5x5SrgbBlock);
+ Add(Format.Astc6x5Srgb, VkFormat.Astc6x5SrgbBlock);
+ Add(Format.Astc6x6Srgb, VkFormat.Astc6x6SrgbBlock);
+ Add(Format.Astc8x5Srgb, VkFormat.Astc8x5SrgbBlock);
+ Add(Format.Astc8x6Srgb, VkFormat.Astc8x6SrgbBlock);
+ Add(Format.Astc8x8Srgb, VkFormat.Astc8x8SrgbBlock);
+ Add(Format.Astc10x5Srgb, VkFormat.Astc10x5SrgbBlock);
+ Add(Format.Astc10x6Srgb, VkFormat.Astc10x6SrgbBlock);
+ Add(Format.Astc10x8Srgb, VkFormat.Astc10x8SrgbBlock);
+ Add(Format.Astc10x10Srgb, VkFormat.Astc10x10SrgbBlock);
+ Add(Format.Astc12x10Srgb, VkFormat.Astc12x10SrgbBlock);
+ Add(Format.Astc12x12Srgb, VkFormat.Astc12x12SrgbBlock);
+ Add(Format.B5G6R5Unorm, VkFormat.R5G6B5UnormPack16);
+ Add(Format.B5G5R5X1Unorm, VkFormat.A1R5G5B5UnormPack16);
+ Add(Format.B5G5R5A1Unorm, VkFormat.A1R5G5B5UnormPack16);
+ Add(Format.A1B5G5R5Unorm, VkFormat.R5G5B5A1UnormPack16);
+ Add(Format.B8G8R8X8Unorm, VkFormat.B8G8R8Unorm);
+ Add(Format.B8G8R8A8Unorm, VkFormat.B8G8R8A8Unorm);
+ Add(Format.B8G8R8X8Srgb, VkFormat.B8G8R8Srgb);
+ Add(Format.B8G8R8A8Srgb, VkFormat.B8G8R8A8Srgb);
+ }
+
+ private static void Add(Format format, VkFormat vkFormat)
+ {
+ Table[(int)format] = vkFormat;
+ }
+
+ public static VkFormat GetFormat(Format format)
+ {
+ return Table[(int)format];
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
new file mode 100644
index 00000000..e5318e93
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -0,0 +1,203 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Linq;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class FramebufferParams
+ {
+ private readonly Device _device;
+ private readonly Auto<DisposableImageView>[] _attachments;
+ private readonly TextureView[] _colors;
+ private readonly TextureView _depthStencil;
+ private uint _validColorAttachments;
+
+ public uint Width { get; }
+ public uint Height { get; }
+ public uint Layers { get; }
+
+ public uint[] AttachmentSamples { get; }
+ public VkFormat[] AttachmentFormats { get; }
+ public int[] AttachmentIndices { get; }
+
+ public int AttachmentsCount { get; }
+ public int MaxColorAttachmentIndex { get; }
+ public bool HasDepthStencil { get; }
+ public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
+
+ public FramebufferParams(
+ Device device,
+ Auto<DisposableImageView> view,
+ uint width,
+ uint height,
+ bool isDepthStencil,
+ VkFormat format)
+ {
+ _device = device;
+ _attachments = new[] { view };
+ _validColorAttachments = 1u;
+
+ Width = width;
+ Height = height;
+ Layers = 1;
+
+ AttachmentSamples = new[] { 1u };
+ AttachmentFormats = new[] { format };
+ AttachmentIndices = new[] { 0 };
+
+ AttachmentsCount = 1;
+
+ HasDepthStencil = isDepthStencil;
+ }
+
+ public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil)
+ {
+ _device = device;
+
+ int colorsCount = colors.Count(IsValidTextureView);
+
+ int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0);
+
+ _attachments = new Auto<DisposableImageView>[count];
+ _colors = new TextureView[colorsCount];
+
+ AttachmentSamples = new uint[count];
+ AttachmentFormats = new VkFormat[count];
+ AttachmentIndices = new int[count];
+ MaxColorAttachmentIndex = colors.Length - 1;
+
+ uint width = uint.MaxValue;
+ uint height = uint.MaxValue;
+ uint layers = uint.MaxValue;
+
+ int index = 0;
+ int bindIndex = 0;
+
+ foreach (ITexture color in colors)
+ {
+ if (IsValidTextureView(color))
+ {
+ var texture = (TextureView)color;
+
+ _attachments[index] = texture.GetImageViewForAttachment();
+ _colors[index] = texture;
+ _validColorAttachments |= 1u << bindIndex;
+
+ AttachmentSamples[index] = (uint)texture.Info.Samples;
+ AttachmentFormats[index] = texture.VkFormat;
+ AttachmentIndices[index] = bindIndex;
+
+ width = Math.Min(width, (uint)texture.Width);
+ height = Math.Min(height, (uint)texture.Height);
+ layers = Math.Min(layers, (uint)texture.Layers);
+
+ if (++index >= colorsCount)
+ {
+ break;
+ }
+ }
+
+ bindIndex++;
+ }
+
+ if (depthStencil is TextureView dsTexture && dsTexture.Valid)
+ {
+ _attachments[count - 1] = dsTexture.GetImageViewForAttachment();
+ _depthStencil = dsTexture;
+
+ AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples;
+ AttachmentFormats[count - 1] = dsTexture.VkFormat;
+
+ width = Math.Min(width, (uint)dsTexture.Width);
+ height = Math.Min(height, (uint)dsTexture.Height);
+ layers = Math.Min(layers, (uint)dsTexture.Layers);
+
+ HasDepthStencil = true;
+ }
+
+ if (count == 0)
+ {
+ width = height = layers = 1;
+ }
+
+ Width = width;
+ Height = height;
+ Layers = layers;
+
+ AttachmentsCount = count;
+ }
+
+ public Auto<DisposableImageView> GetAttachment(int index)
+ {
+ if ((uint)index >= _attachments.Length)
+ {
+ return null;
+ }
+
+ return _attachments[index];
+ }
+
+ public bool IsValidColorAttachment(int bindIndex)
+ {
+ return (uint)bindIndex < Constants.MaxRenderTargets && (_validColorAttachments & (1u << bindIndex)) != 0;
+ }
+
+ private static bool IsValidTextureView(ITexture texture)
+ {
+ return texture is TextureView view && view.Valid;
+ }
+
+ public ClearRect GetClearRect(Rectangle<int> scissor, int layer)
+ {
+ int x = scissor.X;
+ int y = scissor.Y;
+ int width = Math.Min((int)Width - scissor.X, scissor.Width);
+ int height = Math.Min((int)Height - scissor.Y, scissor.Height);
+
+ return new ClearRect(new Rect2D(new Offset2D(x, y), new Extent2D((uint)width, (uint)height)), (uint)layer, 1);
+ }
+
+ public unsafe Auto<DisposableFramebuffer> Create(Vk api, CommandBufferScoped cbs, Auto<DisposableRenderPass> renderPass)
+ {
+ ImageView* attachments = stackalloc ImageView[_attachments.Length];
+
+ for (int i = 0; i < _attachments.Length; i++)
+ {
+ attachments[i] = _attachments[i].Get(cbs).Value;
+ }
+
+ var framebufferCreateInfo = new FramebufferCreateInfo()
+ {
+ SType = StructureType.FramebufferCreateInfo,
+ RenderPass = renderPass.Get(cbs).Value,
+ AttachmentCount = (uint)_attachments.Length,
+ PAttachments = attachments,
+ Width = Width,
+ Height = Height,
+ Layers = Layers
+ };
+
+ api.CreateFramebuffer(_device, framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
+ return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
+ }
+
+ public void UpdateModifications()
+ {
+ if (_colors != null)
+ {
+ for (int index = 0; index < _colors.Length; index++)
+ {
+ _colors[index].Storage.SetModification(
+ AccessFlags.AccessColorAttachmentWriteBit,
+ PipelineStageFlags.PipelineStageColorAttachmentOutputBit);
+ }
+ }
+
+ _depthStencil?.Storage.SetModification(
+ AccessFlags.AccessDepthStencilAttachmentWriteBit,
+ PipelineStageFlags.PipelineStageColorAttachmentOutputBit);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
new file mode 100644
index 00000000..5721962d
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
@@ -0,0 +1,63 @@
+using Silk.NET.Vulkan;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct HardwareCapabilities
+ {
+ public readonly bool SupportsIndexTypeUint8;
+ public readonly bool SupportsCustomBorderColor;
+ public readonly bool SupportsIndirectParameters;
+ public readonly bool SupportsFragmentShaderInterlock;
+ public readonly bool SupportsGeometryShaderPassthrough;
+ public readonly bool SupportsSubgroupSizeControl;
+ public readonly bool SupportsConditionalRendering;
+ public readonly bool SupportsExtendedDynamicState;
+ public readonly bool SupportsMultiView;
+ public readonly bool SupportsNullDescriptors;
+ public readonly bool SupportsPushDescriptors;
+ public readonly bool SupportsTransformFeedback;
+ public readonly bool SupportsTransformFeedbackQueries;
+ public readonly bool SupportsGeometryShader;
+ public readonly uint MinSubgroupSize;
+ public readonly uint MaxSubgroupSize;
+ public readonly ShaderStageFlags RequiredSubgroupSizeStages;
+
+ public HardwareCapabilities(
+ bool supportsIndexTypeUint8,
+ bool supportsCustomBorderColor,
+ bool supportsIndirectParameters,
+ bool supportsFragmentShaderInterlock,
+ bool supportsGeometryShaderPassthrough,
+ bool supportsSubgroupSizeControl,
+ bool supportsConditionalRendering,
+ bool supportsExtendedDynamicState,
+ bool supportsMultiView,
+ bool supportsNullDescriptors,
+ bool supportsPushDescriptors,
+ bool supportsTransformFeedback,
+ bool supportsTransformFeedbackQueries,
+ bool supportsGeometryShader,
+ uint minSubgroupSize,
+ uint maxSubgroupSize,
+ ShaderStageFlags requiredSubgroupSizeStages)
+ {
+ SupportsIndexTypeUint8 = supportsIndexTypeUint8;
+ SupportsCustomBorderColor = supportsCustomBorderColor;
+ SupportsIndirectParameters = supportsIndirectParameters;
+ SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
+ SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
+ SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
+ SupportsConditionalRendering = supportsConditionalRendering;
+ SupportsExtendedDynamicState = supportsExtendedDynamicState;
+ SupportsMultiView = supportsMultiView;
+ SupportsNullDescriptors = supportsNullDescriptors;
+ SupportsPushDescriptors = supportsPushDescriptors;
+ SupportsTransformFeedback = supportsTransformFeedback;
+ SupportsTransformFeedbackQueries = supportsTransformFeedbackQueries;
+ SupportsGeometryShader = supportsGeometryShader;
+ MinSubgroupSize = minSubgroupSize;
+ MaxSubgroupSize = maxSubgroupSize;
+ RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/HashTableSlim.cs b/Ryujinx.Graphics.Vulkan/HashTableSlim.cs
new file mode 100644
index 00000000..2dde2aeb
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/HashTableSlim.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ interface IRefEquatable<T>
+ {
+ bool Equals(ref T other);
+ }
+
+ class HashTableSlim<K, V> where K : IRefEquatable<K>
+ {
+ private const int TotalBuckets = 16; // Must be power of 2
+ private const int TotalBucketsMask = TotalBuckets - 1;
+
+ private struct Entry
+ {
+ public K Key;
+ public V Value;
+ }
+
+ private readonly Entry[][] _hashTable = new Entry[TotalBuckets][];
+
+ public IEnumerable<K> Keys
+ {
+ get
+ {
+ foreach (Entry[] bucket in _hashTable)
+ {
+ if (bucket != null)
+ {
+ foreach (Entry entry in bucket)
+ {
+ yield return entry.Key;
+ }
+ }
+ }
+ }
+ }
+
+ public IEnumerable<V> Values
+ {
+ get
+ {
+ foreach (Entry[] bucket in _hashTable)
+ {
+ if (bucket != null)
+ {
+ foreach (Entry entry in bucket)
+ {
+ yield return entry.Value;
+ }
+ }
+ }
+ }
+ }
+
+ public void Add(ref K key, V value)
+ {
+ var entry = new Entry()
+ {
+ Key = key,
+ Value = value
+ };
+
+ int hashCode = key.GetHashCode();
+ int bucketIndex = hashCode & TotalBucketsMask;
+
+ var bucket = _hashTable[bucketIndex];
+ if (bucket != null)
+ {
+ int index = bucket.Length;
+
+ Array.Resize(ref _hashTable[bucketIndex], index + 1);
+
+ _hashTable[bucketIndex][index] = entry;
+ }
+ else
+ {
+ _hashTable[bucketIndex] = new Entry[]
+ {
+ entry
+ };
+ }
+ }
+
+ public bool TryGetValue(ref K key, out V value)
+ {
+ int hashCode = key.GetHashCode();
+
+ var bucket = _hashTable[hashCode & TotalBucketsMask];
+ if (bucket != null)
+ {
+
+ for (int i = 0; i < bucket.Length; i++)
+ {
+ ref var entry = ref bucket[i];
+
+ if (entry.Key.Equals(ref key))
+ {
+ value = entry.Value;
+ return true;
+ }
+ }
+ }
+
+ value = default;
+ return false;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/HelperShader.cs b/Ryujinx.Graphics.Vulkan/HelperShader.cs
new file mode 100644
index 00000000..53a03cfb
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/HelperShader.cs
@@ -0,0 +1,352 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+using Ryujinx.Graphics.Vulkan.Shaders;
+using Silk.NET.Vulkan;
+using System;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class HelperShader : IDisposable
+ {
+ private readonly PipelineHelperShader _pipeline;
+ private readonly ISampler _samplerLinear;
+ private readonly ISampler _samplerNearest;
+ private readonly IProgram _programColorBlit;
+ private readonly IProgram _programColorBlitClearAlpha;
+ private readonly IProgram _programColorClear;
+
+ public HelperShader(VulkanRenderer gd, Device device)
+ {
+ _pipeline = new PipelineHelperShader(gd, device);
+ _pipeline.Initialize();
+
+ _samplerLinear = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
+ _samplerNearest = gd.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Nearest, MagFilter.Nearest));
+
+ var vertexBindings = new ShaderBindings(
+ new[] { 1 },
+ Array.Empty<int>(),
+ Array.Empty<int>(),
+ Array.Empty<int>());
+
+ var fragmentBindings = new ShaderBindings(
+ Array.Empty<int>(),
+ Array.Empty<int>(),
+ new[] { 0 },
+ Array.Empty<int>());
+
+ _programColorBlit = gd.CreateProgramWithMinimalLayout(new[]
+ {
+ new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Glsl),
+ new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, fragmentBindings, ShaderStage.Fragment, TargetLanguage.Glsl),
+ });
+
+ _programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[]
+ {
+ new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Glsl),
+ new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, fragmentBindings, ShaderStage.Fragment, TargetLanguage.Glsl),
+ });
+
+ var fragmentBindings2 = new ShaderBindings(
+ Array.Empty<int>(),
+ Array.Empty<int>(),
+ Array.Empty<int>(),
+ Array.Empty<int>());
+
+ _programColorClear = gd.CreateProgramWithMinimalLayout(new[]
+ {
+ new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Glsl),
+ new ShaderSource(ShaderBinaries.ColorClearFragmentShaderSource, fragmentBindings2, ShaderStage.Fragment, TargetLanguage.Glsl),
+ });
+ }
+
+ public void Blit(
+ VulkanRenderer gd,
+ TextureView src,
+ Auto<DisposableImageView> dst,
+ int dstWidth,
+ int dstHeight,
+ VkFormat dstFormat,
+ Extents2D srcRegion,
+ Extents2D dstRegion,
+ bool linearFilter,
+ bool clearAlpha = false)
+ {
+ gd.FlushAllCommands();
+
+ using var cbs = gd.CommandBufferPool.Rent();
+
+ Blit(gd, cbs, src, dst, dstWidth, dstHeight, dstFormat, srcRegion, dstRegion, linearFilter, clearAlpha);
+ }
+
+ public void Blit(
+ VulkanRenderer gd,
+ CommandBufferScoped cbs,
+ TextureView src,
+ Auto<DisposableImageView> dst,
+ int dstWidth,
+ int dstHeight,
+ VkFormat dstFormat,
+ Extents2D srcRegion,
+ Extents2D dstRegion,
+ bool linearFilter,
+ bool clearAlpha = false)
+ {
+ _pipeline.SetCommandBuffer(cbs);
+
+ const int RegionBufferSize = 16;
+
+ var sampler = linearFilter ? _samplerLinear : _samplerNearest;
+
+ _pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, sampler);
+
+ Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
+
+ region[0] = (float)srcRegion.X1 / src.Width;
+ region[1] = (float)srcRegion.X2 / src.Width;
+ region[2] = (float)srcRegion.Y1 / src.Height;
+ region[3] = (float)srcRegion.Y2 / src.Height;
+
+ if (dstRegion.X1 > dstRegion.X2)
+ {
+ (region[0], region[1]) = (region[1], region[0]);
+ }
+
+ if (dstRegion.Y1 > dstRegion.Y2)
+ {
+ (region[2], region[3]) = (region[3], region[2]);
+ }
+
+ var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
+
+ gd.BufferManager.SetData<float>(bufferHandle, 0, region);
+
+ Span<BufferRange> bufferRanges = stackalloc BufferRange[1];
+
+ bufferRanges[0] = new BufferRange(bufferHandle, 0, RegionBufferSize);
+
+ _pipeline.SetUniformBuffers(1, bufferRanges);
+
+ Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
+
+ var rect = new Rectangle<float>(
+ MathF.Min(dstRegion.X1, dstRegion.X2),
+ MathF.Min(dstRegion.Y1, dstRegion.Y2),
+ MathF.Abs(dstRegion.X2 - dstRegion.X1),
+ MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
+
+ viewports[0] = new GAL.Viewport(
+ rect,
+ ViewportSwizzle.PositiveX,
+ ViewportSwizzle.PositiveY,
+ ViewportSwizzle.PositiveZ,
+ ViewportSwizzle.PositiveW,
+ 0f,
+ 1f);
+
+ Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
+
+ scissors[0] = new Rectangle<int>(0, 0, dstWidth, dstHeight);
+
+ _pipeline.SetProgram(clearAlpha ? _programColorBlitClearAlpha : _programColorBlit);
+ _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
+ _pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
+ _pipeline.SetScissors(scissors);
+
+ if (clearAlpha)
+ {
+ _pipeline.ClearRenderTargetColor(0, 0, new ColorF(0f, 0f, 0f, 1f));
+ }
+
+ _pipeline.SetViewports(viewports, false);
+ _pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
+ _pipeline.Draw(4, 1, 0, 0);
+ _pipeline.Finish();
+
+ gd.BufferManager.Delete(bufferHandle);
+ }
+
+ public void Clear(
+ VulkanRenderer gd,
+ Auto<DisposableImageView> dst,
+ ReadOnlySpan<float> clearColor,
+ uint componentMask,
+ int dstWidth,
+ int dstHeight,
+ VkFormat dstFormat,
+ Rectangle<int> scissor)
+ {
+ const int ClearColorBufferSize = 16;
+
+ gd.FlushAllCommands();
+
+ using var cbs = gd.CommandBufferPool.Rent();
+
+ _pipeline.SetCommandBuffer(cbs);
+
+ var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize, false);
+
+ gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
+
+ Span<BufferRange> bufferRanges = stackalloc BufferRange[1];
+
+ bufferRanges[0] = new BufferRange(bufferHandle, 0, ClearColorBufferSize);
+
+ _pipeline.SetUniformBuffers(1, bufferRanges);
+
+ Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
+
+ viewports[0] = new GAL.Viewport(
+ new Rectangle<float>(0, 0, dstWidth, dstHeight),
+ ViewportSwizzle.PositiveX,
+ ViewportSwizzle.PositiveY,
+ ViewportSwizzle.PositiveZ,
+ ViewportSwizzle.PositiveW,
+ 0f,
+ 1f);
+
+ Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
+
+ scissors[0] = scissor;
+
+ _pipeline.SetProgram(_programColorClear);
+ _pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
+ _pipeline.SetRenderTargetColorMasks(new uint[] { componentMask });
+ _pipeline.SetViewports(viewports, false);
+ _pipeline.SetScissors(scissors);
+ _pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
+ _pipeline.Draw(4, 1, 0, 0);
+ _pipeline.Finish();
+
+ gd.BufferManager.Delete(bufferHandle);
+ }
+
+ public void DrawTexture(
+ VulkanRenderer gd,
+ PipelineBase pipeline,
+ TextureView src,
+ ISampler srcSampler,
+ Extents2DF srcRegion,
+ Extents2DF dstRegion)
+ {
+ const int RegionBufferSize = 16;
+
+ pipeline.SetTextureAndSampler(ShaderStage.Fragment, 0, src, srcSampler);
+
+ Span<float> region = stackalloc float[RegionBufferSize / sizeof(float)];
+
+ region[0] = srcRegion.X1 / src.Width;
+ region[1] = srcRegion.X2 / src.Width;
+ region[2] = srcRegion.Y1 / src.Height;
+ region[3] = srcRegion.Y2 / src.Height;
+
+ if (dstRegion.X1 > dstRegion.X2)
+ {
+ (region[0], region[1]) = (region[1], region[0]);
+ }
+
+ if (dstRegion.Y1 > dstRegion.Y2)
+ {
+ (region[2], region[3]) = (region[3], region[2]);
+ }
+
+ var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
+
+ gd.BufferManager.SetData<float>(bufferHandle, 0, region);
+
+ Span<BufferRange> bufferRanges = stackalloc BufferRange[1];
+
+ bufferRanges[0] = new BufferRange(bufferHandle, 0, RegionBufferSize);
+
+ pipeline.SetUniformBuffers(1, bufferRanges);
+
+ Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
+
+ var rect = new Rectangle<float>(
+ MathF.Min(dstRegion.X1, dstRegion.X2),
+ MathF.Min(dstRegion.Y1, dstRegion.Y2),
+ MathF.Abs(dstRegion.X2 - dstRegion.X1),
+ MathF.Abs(dstRegion.Y2 - dstRegion.Y1));
+
+ viewports[0] = new GAL.Viewport(
+ rect,
+ ViewportSwizzle.PositiveX,
+ ViewportSwizzle.PositiveY,
+ ViewportSwizzle.PositiveZ,
+ ViewportSwizzle.PositiveW,
+ 0f,
+ 1f);
+
+ Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
+
+ pipeline.SetProgram(_programColorBlit);
+ pipeline.SetViewports(viewports, false);
+ pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip);
+ pipeline.Draw(4, 1, 0, 0);
+
+ gd.BufferManager.Delete(bufferHandle);
+ }
+
+ public unsafe void ConvertI8ToI16(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size)
+ {
+ // TODO: Do this with a compute shader?
+ var srcBuffer = src.GetBuffer().Get(cbs, srcOffset, size).Value;
+ var dstBuffer = dst.GetBuffer().Get(cbs, 0, size * 2).Value;
+
+ gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
+
+ var bufferCopy = new BufferCopy[size];
+
+ for (ulong i = 0; i < (ulong)size; i++)
+ {
+ bufferCopy[i] = new BufferCopy((ulong)srcOffset + i, i * 2, 1);
+ }
+
+ BufferHolder.InsertBufferBarrier(
+ gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ BufferHolder.DefaultAccessFlags,
+ AccessFlags.AccessTransferWriteBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ 0,
+ size * 2);
+
+ fixed (BufferCopy* pBufferCopy = bufferCopy)
+ {
+ gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)size, pBufferCopy);
+ }
+
+ BufferHolder.InsertBufferBarrier(
+ gd,
+ cbs.CommandBuffer,
+ dstBuffer,
+ AccessFlags.AccessTransferWriteBit,
+ BufferHolder.DefaultAccessFlags,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ 0,
+ size * 2);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _programColorBlitClearAlpha.Dispose();
+ _programColorBlit.Dispose();
+ _samplerNearest.Dispose();
+ _samplerLinear.Dispose();
+ _pipeline.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/IdList.cs b/Ryujinx.Graphics.Vulkan/IdList.cs
new file mode 100644
index 00000000..d5a87a05
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/IdList.cs
@@ -0,0 +1,115 @@
+using System.Collections.Generic;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class IdList<T> where T : class
+ {
+ private readonly List<T> _list;
+ private int _freeMin;
+
+ public IdList()
+ {
+ _list = new List<T>();
+ _freeMin = 0;
+ }
+
+ public int Add(T value)
+ {
+ int id;
+ int count = _list.Count;
+ id = _list.IndexOf(null, _freeMin);
+
+ if ((uint)id < (uint)count)
+ {
+ _list[id] = value;
+ }
+ else
+ {
+ id = count;
+ _freeMin = id + 1;
+
+ _list.Add(value);
+ }
+
+ return id + 1;
+ }
+
+ public void Remove(int id)
+ {
+ id--;
+
+ int count = _list.Count;
+
+ if ((uint)id >= (uint)count)
+ {
+ return;
+ }
+
+ if (id + 1 == count)
+ {
+ // Trim unused items.
+ int removeIndex = id;
+
+ while (removeIndex > 0 && _list[removeIndex - 1] == null)
+ {
+ removeIndex--;
+ }
+
+ _list.RemoveRange(removeIndex, count - removeIndex);
+
+ if (_freeMin > removeIndex)
+ {
+ _freeMin = removeIndex;
+ }
+ }
+ else
+ {
+ _list[id] = null;
+
+ if (_freeMin > id)
+ {
+ _freeMin = id;
+ }
+ }
+ }
+
+ public bool TryGetValue(int id, out T value)
+ {
+ id--;
+
+ try
+ {
+ value = _list[id];
+ return value != null;
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ value = null;
+ return false;
+ }
+ catch (IndexOutOfRangeException)
+ {
+ value = null;
+ return false;
+ }
+ }
+
+ public void Clear()
+ {
+ _list.Clear();
+ _freeMin = 0;
+ }
+
+ public IEnumerator<T> GetEnumerator()
+ {
+ for (int i = 0; i < _list.Count; i++)
+ {
+ if (_list[i] != null)
+ {
+ yield return _list[i];
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/ImageWindow.cs b/Ryujinx.Graphics.Vulkan/ImageWindow.cs
new file mode 100644
index 00000000..5dd23155
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/ImageWindow.cs
@@ -0,0 +1,361 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class ImageWindow : WindowBase, IWindow, IDisposable
+ {
+ private const int ImageCount = 5;
+ private const int SurfaceWidth = 1280;
+ private const int SurfaceHeight = 720;
+
+ private readonly VulkanRenderer _gd;
+ private readonly PhysicalDevice _physicalDevice;
+ private readonly Device _device;
+
+ private Auto<DisposableImage>[] _images;
+ private Auto<DisposableImageView>[] _imageViews;
+ private Auto<MemoryAllocation>[] _imageAllocationAuto;
+ private ulong[] _imageSizes;
+ private ulong[] _imageOffsets;
+
+ private Semaphore _imageAvailableSemaphore;
+ private Semaphore _renderFinishedSemaphore;
+
+ private int _width = SurfaceWidth;
+ private int _height = SurfaceHeight;
+ private VkFormat _format;
+ private bool _recreateImages;
+ private int _nextImage;
+
+ internal new bool ScreenCaptureRequested { get; set; }
+
+ public unsafe ImageWindow(VulkanRenderer gd, PhysicalDevice physicalDevice, Device device)
+ {
+ _gd = gd;
+ _physicalDevice = physicalDevice;
+ _device = device;
+
+ _format = VkFormat.R8G8B8A8Unorm;
+
+ _images = new Auto<DisposableImage>[ImageCount];
+ _imageAllocationAuto = new Auto<MemoryAllocation>[ImageCount];
+ _imageSizes = new ulong[ImageCount];
+ _imageOffsets = new ulong[ImageCount];
+
+ CreateImages();
+
+ var semaphoreCreateInfo = new SemaphoreCreateInfo()
+ {
+ SType = StructureType.SemaphoreCreateInfo
+ };
+
+ gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _imageAvailableSemaphore).ThrowOnError();
+ gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _renderFinishedSemaphore).ThrowOnError();
+ }
+
+ private void RecreateImages()
+ {
+ for (int i = 0; i < ImageCount; i++)
+ {
+ _imageViews[i]?.Dispose();
+ _imageAllocationAuto[i]?.Dispose();
+ _images[i]?.Dispose();
+ }
+
+ CreateImages();
+ }
+
+ private unsafe void CreateImages()
+ {
+ _imageViews = new Auto<DisposableImageView>[ImageCount];
+
+ var cbs = _gd.CommandBufferPool.Rent();
+ for (int i = 0; i < _images.Length; i++)
+ {
+ var imageCreateInfo = new ImageCreateInfo
+ {
+ SType = StructureType.ImageCreateInfo,
+ ImageType = ImageType.ImageType2D,
+ Format = _format,
+ Extent =
+ new Extent3D((uint?)_width,
+ (uint?)_height, 1),
+ MipLevels = 1,
+ ArrayLayers = 1,
+ Samples = SampleCountFlags.SampleCount1Bit,
+ Tiling = ImageTiling.Optimal,
+ Usage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageTransferDstBit,
+ SharingMode = SharingMode.Exclusive,
+ InitialLayout = ImageLayout.Undefined,
+ Flags = ImageCreateFlags.ImageCreateMutableFormatBit
+ };
+
+ _gd.Api.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
+ _images[i] = new Auto<DisposableImage>(new DisposableImage(_gd.Api, _device, image));
+
+ _gd.Api.GetImageMemoryRequirements(_device, image,
+ out var memoryRequirements);
+
+ var allocation = _gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, memoryRequirements, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit);
+
+ _imageSizes[i] = allocation.Size;
+ _imageOffsets[i] = allocation.Offset;
+
+ _imageAllocationAuto[i] = new Auto<MemoryAllocation>(allocation);
+
+ _gd.Api.BindImageMemory(_device, image, allocation.Memory, allocation.Offset);
+
+ _imageViews[i] = CreateImageView(image, _format);
+
+ Transition(
+ cbs.CommandBuffer,
+ image,
+ 0,
+ 0,
+ ImageLayout.Undefined,
+ ImageLayout.ColorAttachmentOptimal);
+ }
+
+ _gd.CommandBufferPool.Return(cbs);
+ }
+
+ private unsafe Auto<DisposableImageView> CreateImageView(Image image, VkFormat format)
+ {
+ var componentMapping = new ComponentMapping(
+ ComponentSwizzle.R,
+ ComponentSwizzle.G,
+ ComponentSwizzle.B,
+ ComponentSwizzle.A);
+
+ var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1);
+
+ var imageCreateInfo = new ImageViewCreateInfo()
+ {
+ SType = StructureType.ImageViewCreateInfo,
+ Image = image,
+ ViewType = ImageViewType.ImageViewType2D,
+ Format = format,
+ Components = componentMapping,
+ SubresourceRange = subresourceRange
+ };
+
+ _gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
+ return new Auto<DisposableImageView>(new DisposableImageView(_gd.Api, _device, imageView));
+ }
+
+ public override unsafe void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
+ {
+ if (_recreateImages)
+ {
+ RecreateImages();
+ _recreateImages = false;
+ }
+
+ var image = _images[_nextImage];
+
+ _gd.FlushAllCommands();
+
+ var cbs = _gd.CommandBufferPool.Rent();
+
+ Transition(
+ cbs.CommandBuffer,
+ image.GetUnsafe().Value,
+ 0,
+ AccessFlags.AccessTransferWriteBit,
+ ImageLayout.ColorAttachmentOptimal,
+ ImageLayout.General);
+
+ var view = (TextureView)texture;
+
+ int srcX0, srcX1, srcY0, srcY1;
+ float scale = view.ScaleFactor;
+
+ if (crop.Left == 0 && crop.Right == 0)
+ {
+ srcX0 = 0;
+ srcX1 = (int)(view.Width / scale);
+ }
+ else
+ {
+ srcX0 = crop.Left;
+ srcX1 = crop.Right;
+ }
+
+ if (crop.Top == 0 && crop.Bottom == 0)
+ {
+ srcY0 = 0;
+ srcY1 = (int)(view.Height / scale);
+ }
+ else
+ {
+ srcY0 = crop.Top;
+ srcY1 = crop.Bottom;
+ }
+
+ if (scale != 1f)
+ {
+ srcX0 = (int)(srcX0 * scale);
+ srcY0 = (int)(srcY0 * scale);
+ srcX1 = (int)Math.Ceiling(srcX1 * scale);
+ srcY1 = (int)Math.Ceiling(srcY1 * scale);
+ }
+
+ if (ScreenCaptureRequested)
+ {
+ CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY);
+
+ ScreenCaptureRequested = false;
+ }
+
+ float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY));
+ float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX));
+
+ int dstWidth = (int)(_width * ratioX);
+ int dstHeight = (int)(_height * ratioY);
+
+ int dstPaddingX = (_width - dstWidth) / 2;
+ int dstPaddingY = (_height - dstHeight) / 2;
+
+ int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX;
+ int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX;
+
+ int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
+ int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
+
+ _gd.HelperShader.Blit(
+ _gd,
+ cbs,
+ view,
+ _imageViews[_nextImage],
+ _width,
+ _height,
+ _format,
+ new Extents2D(srcX0, srcY0, srcX1, srcY1),
+ new Extents2D(dstX0, dstY1, dstX1, dstY0),
+ true,
+ true);
+
+ Transition(
+ cbs.CommandBuffer,
+ image.GetUnsafe().Value,
+ 0,
+ 0,
+ ImageLayout.General,
+ ImageLayout.ColorAttachmentOptimal);
+
+ _gd.CommandBufferPool.Return(
+ cbs,
+ null,
+ stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
+ null);
+
+ var memory = _imageAllocationAuto[_nextImage].GetUnsafe().Memory;
+ var presentInfo = new PresentImageInfo(image.GetUnsafe().Value, memory, _imageSizes[_nextImage], _imageOffsets[_nextImage], _renderFinishedSemaphore, _imageAvailableSemaphore);
+
+ swapBuffersCallback(presentInfo);
+
+ _nextImage %= ImageCount;
+ }
+
+ private unsafe void Transition(
+ CommandBuffer commandBuffer,
+ Image image,
+ AccessFlags srcAccess,
+ AccessFlags dstAccess,
+ ImageLayout srcLayout,
+ ImageLayout dstLayout)
+ {
+ var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1);
+
+ var barrier = new ImageMemoryBarrier()
+ {
+ SType = StructureType.ImageMemoryBarrier,
+ SrcAccessMask = srcAccess,
+ DstAccessMask = dstAccess,
+ OldLayout = srcLayout,
+ NewLayout = dstLayout,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Image = image,
+ SubresourceRange = subresourceRange
+ };
+
+ _gd.Api.CmdPipelineBarrier(
+ commandBuffer,
+ PipelineStageFlags.PipelineStageTopOfPipeBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ 0,
+ 0,
+ null,
+ 0,
+ null,
+ 1,
+ barrier);
+ }
+
+ private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
+ {
+ byte[] bitmap = texture.GetData(x, y, width, height);
+
+ _gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
+ }
+
+ public override void SetSize(int width, int height)
+ {
+ if (_width != width || _height != height)
+ {
+ _recreateImages = true;
+ }
+
+ _width = width;
+ _height = height;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ unsafe
+ {
+ _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null);
+ _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null);
+
+ for (int i = 0; i < ImageCount; i++)
+ {
+ _imageViews[i]?.Dispose();
+ _imageAllocationAuto[i]?.Dispose();
+ _images[i]?.Dispose();
+ }
+ }
+ }
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+
+ public class PresentImageInfo
+ {
+ public Image Image { get; }
+ public DeviceMemory Memory { get; }
+ public ulong MemorySize { get; set; }
+ public ulong MemoryOffset { get; set; }
+ public Semaphore ReadySemaphore { get; }
+ public Semaphore AvailableSemaphore { get; }
+
+ public PresentImageInfo(Image image, DeviceMemory memory, ulong memorySize, ulong memoryOffset, Semaphore readySemaphore, Semaphore availableSemaphore)
+ {
+ this.Image = image;
+ this.Memory = memory;
+ this.MemorySize = memorySize;
+ this.MemoryOffset = memoryOffset;
+ this.ReadySemaphore = readySemaphore;
+ this.AvailableSemaphore = availableSemaphore;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs b/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs
new file mode 100644
index 00000000..04956e36
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs
@@ -0,0 +1,37 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct MemoryAllocation : IDisposable
+ {
+ private readonly MemoryAllocatorBlockList _owner;
+ private readonly MemoryAllocatorBlockList.Block _block;
+
+ public DeviceMemory Memory { get; }
+ public IntPtr HostPointer { get;}
+ public ulong Offset { get; }
+ public ulong Size { get; }
+
+ public MemoryAllocation(
+ MemoryAllocatorBlockList owner,
+ MemoryAllocatorBlockList.Block block,
+ DeviceMemory memory,
+ IntPtr hostPointer,
+ ulong offset,
+ ulong size)
+ {
+ _owner = owner;
+ _block = block;
+ Memory = memory;
+ HostPointer = hostPointer;
+ Offset = offset;
+ Size = size;
+ }
+
+ public void Dispose()
+ {
+ _owner.Free(_block, Offset, Size);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs b/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs
new file mode 100644
index 00000000..0b05ef92
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs
@@ -0,0 +1,84 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class MemoryAllocator : IDisposable
+ {
+ private ulong MaxDeviceMemoryUsageEstimate = 16UL * 1024 * 1024 * 1024;
+
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly List<MemoryAllocatorBlockList> _blockLists;
+
+ private int _blockAlignment;
+
+ public MemoryAllocator(Vk api, Device device, uint maxMemoryAllocationCount)
+ {
+ _api = api;
+ _device = device;
+ _blockLists = new List<MemoryAllocatorBlockList>();
+ _blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / (ulong)maxMemoryAllocationCount);
+ }
+
+ public MemoryAllocation AllocateDeviceMemory(
+ PhysicalDevice physicalDevice,
+ MemoryRequirements requirements,
+ MemoryPropertyFlags flags = 0)
+ {
+ int memoryTypeIndex = FindSuitableMemoryTypeIndex(_api, physicalDevice, requirements.MemoryTypeBits, flags);
+ if (memoryTypeIndex < 0)
+ {
+ return default;
+ }
+
+ bool map = flags.HasFlag(MemoryPropertyFlags.MemoryPropertyHostVisibleBit);
+ return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map);
+ }
+
+ private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map)
+ {
+ for (int i = 0; i < _blockLists.Count; i++)
+ {
+ var bl = _blockLists[i];
+ if (bl.MemoryTypeIndex == memoryTypeIndex)
+ {
+ lock (bl)
+ {
+ return bl.Allocate(size, alignment, map);
+ }
+ }
+ }
+
+ var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment);
+ _blockLists.Add(newBl);
+ return newBl.Allocate(size, alignment, map);
+ }
+
+ private static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits, MemoryPropertyFlags flags)
+ {
+ api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
+
+ for (int i = 0; i < properties.MemoryTypeCount; i++)
+ {
+ var type = properties.MemoryTypes[i];
+
+ if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public void Dispose()
+ {
+ for (int i = 0; i < _blockLists.Count; i++)
+ {
+ _blockLists[i].Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs b/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs
new file mode 100644
index 00000000..1c008d49
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs
@@ -0,0 +1,280 @@
+using Ryujinx.Common;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class MemoryAllocatorBlockList : IDisposable
+ {
+ private const ulong InvalidOffset = ulong.MaxValue;
+
+ public class Block : IComparable<Block>
+ {
+ public DeviceMemory Memory { get; private set; }
+ public IntPtr HostPointer { get; private set; }
+ public ulong Size { get; }
+ public bool Mapped => HostPointer != IntPtr.Zero;
+
+ private struct Range : IComparable<Range>
+ {
+ public ulong Offset { get; }
+ public ulong Size { get; }
+
+ public Range(ulong offset, ulong size)
+ {
+ Offset = offset;
+ Size = size;
+ }
+
+ public int CompareTo(Range other)
+ {
+ return Offset.CompareTo(other.Offset);
+ }
+ }
+
+ private readonly List<Range> _freeRanges;
+
+ public Block(DeviceMemory memory, IntPtr hostPointer, ulong size)
+ {
+ Memory = memory;
+ HostPointer = hostPointer;
+ Size = size;
+ _freeRanges = new List<Range>
+ {
+ new Range(0, size)
+ };
+ }
+
+ public ulong Allocate(ulong size, ulong alignment)
+ {
+ for (int i = 0; i < _freeRanges.Count; i++)
+ {
+ var range = _freeRanges[i];
+
+ ulong alignedOffset = BitUtils.AlignUp(range.Offset, (int)alignment);
+ ulong sizeDelta = alignedOffset - range.Offset;
+ ulong usableSize = range.Size - sizeDelta;
+
+ if (sizeDelta < range.Size && usableSize >= size)
+ {
+ _freeRanges.RemoveAt(i);
+
+ if (sizeDelta != 0)
+ {
+ InsertFreeRange(range.Offset, sizeDelta);
+ }
+
+ ulong endOffset = range.Offset + range.Size;
+ ulong remainingSize = endOffset - (alignedOffset + size);
+ if (remainingSize != 0)
+ {
+ InsertFreeRange(endOffset - remainingSize, remainingSize);
+ }
+
+ return alignedOffset;
+ }
+ }
+
+ return InvalidOffset;
+ }
+
+ public void Free(ulong offset, ulong size)
+ {
+ InsertFreeRangeComingled(offset, size);
+ }
+
+ private void InsertFreeRange(ulong offset, ulong size)
+ {
+ var range = new Range(offset, size);
+ int index = _freeRanges.BinarySearch(range);
+ if (index < 0)
+ {
+ index = ~index;
+ }
+
+ _freeRanges.Insert(index, range);
+ }
+
+ private void InsertFreeRangeComingled(ulong offset, ulong size)
+ {
+ ulong endOffset = offset + size;
+ var range = new Range(offset, size);
+ int index = _freeRanges.BinarySearch(range);
+ if (index < 0)
+ {
+ index = ~index;
+ }
+
+ if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset)
+ {
+ endOffset = _freeRanges[index].Offset + _freeRanges[index].Size;
+ _freeRanges.RemoveAt(index);
+ }
+
+ if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset)
+ {
+ offset = _freeRanges[index - 1].Offset;
+ _freeRanges.RemoveAt(--index);
+ }
+
+ range = new Range(offset, endOffset - offset);
+
+ _freeRanges.Insert(index, range);
+ }
+
+ public bool IsTotallyFree()
+ {
+ if (_freeRanges.Count == 1 && _freeRanges[0].Size == Size)
+ {
+ Debug.Assert(_freeRanges[0].Offset == 0);
+ return true;
+ }
+
+ return false;
+ }
+
+ public int CompareTo(Block other)
+ {
+ return Size.CompareTo(other.Size);
+ }
+
+ public unsafe void Destroy(Vk api, Device device)
+ {
+ if (Mapped)
+ {
+ api.UnmapMemory(device, Memory);
+ HostPointer = IntPtr.Zero;
+ }
+
+ if (Memory.Handle != 0)
+ {
+ api.FreeMemory(device, Memory, null);
+ Memory = default;
+ }
+ }
+ }
+
+ private readonly List<Block> _blocks;
+
+ private readonly Vk _api;
+ private readonly Device _device;
+
+ public int MemoryTypeIndex { get; }
+
+ private readonly int _blockAlignment;
+
+ public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment)
+ {
+ _blocks = new List<Block>();
+ _api = api;
+ _device = device;
+ MemoryTypeIndex = memoryTypeIndex;
+ _blockAlignment = blockAlignment;
+ }
+
+ public unsafe MemoryAllocation Allocate(ulong size, ulong alignment, bool map)
+ {
+ // Ensure we have a sane alignment value.
+ if ((ulong)(int)alignment != alignment || (int)alignment <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}.");
+ }
+
+ for (int i = 0; i < _blocks.Count; i++)
+ {
+ var block = _blocks[i];
+
+ if (block.Mapped == map && block.Size >= size)
+ {
+ ulong offset = block.Allocate(size, alignment);
+ if (offset != InvalidOffset)
+ {
+ return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size);
+ }
+ }
+ }
+
+ ulong blockAlignedSize = BitUtils.AlignUp(size, _blockAlignment);
+
+ var memoryAllocateInfo = new MemoryAllocateInfo()
+ {
+ SType = StructureType.MemoryAllocateInfo,
+ AllocationSize = blockAlignedSize,
+ MemoryTypeIndex = (uint)MemoryTypeIndex
+ };
+
+ _api.AllocateMemory(_device, memoryAllocateInfo, null, out var deviceMemory).ThrowOnError();
+
+ IntPtr hostPointer = IntPtr.Zero;
+
+ if (map)
+ {
+ unsafe
+ {
+ void* pointer = null;
+ _api.MapMemory(_device, deviceMemory, 0, blockAlignedSize, 0, ref pointer).ThrowOnError();
+ hostPointer = (IntPtr)pointer;
+ }
+ }
+
+ var newBlock = new Block(deviceMemory, hostPointer, blockAlignedSize);
+
+ InsertBlock(newBlock);
+
+ ulong newBlockOffset = newBlock.Allocate(size, alignment);
+ Debug.Assert(newBlockOffset != InvalidOffset);
+
+ return new MemoryAllocation(this, newBlock, deviceMemory, GetHostPointer(newBlock, newBlockOffset), newBlockOffset, size);
+ }
+
+ private static IntPtr GetHostPointer(Block block, ulong offset)
+ {
+ if (block.HostPointer == IntPtr.Zero)
+ {
+ return IntPtr.Zero;
+ }
+
+ return (IntPtr)((nuint)(nint)block.HostPointer + offset);
+ }
+
+ public unsafe void Free(Block block, ulong offset, ulong size)
+ {
+ block.Free(offset, size);
+
+ if (block.IsTotallyFree())
+ {
+ for (int i = 0; i < _blocks.Count; i++)
+ {
+ if (_blocks[i] == block)
+ {
+ _blocks.RemoveAt(i);
+ break;
+ }
+ }
+
+ block.Destroy(_api, _device);
+ }
+ }
+
+ private void InsertBlock(Block block)
+ {
+ int index = _blocks.BinarySearch(block);
+ if (index < 0)
+ {
+ index = ~index;
+ }
+
+ _blocks.Insert(index, block);
+ }
+
+ public unsafe void Dispose()
+ {
+ for (int i = 0; i < _blocks.Count; i++)
+ {
+ _blocks[i].Destroy(_api, _device);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
new file mode 100644
index 00000000..9a9a3626
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
@@ -0,0 +1,212 @@
+using Silk.NET.Vulkan;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ /// <summary>
+ /// Holder for multiple host GPU fences.
+ /// </summary>
+ class MultiFenceHolder
+ {
+ private static int BufferUsageTrackingGranularity = 4096;
+
+ private readonly Dictionary<FenceHolder, int> _fences;
+ private BufferUsageBitmap _bufferUsageBitmap;
+
+ /// <summary>
+ /// Creates a new instance of the multiple fence holder.
+ /// </summary>
+ public MultiFenceHolder()
+ {
+ _fences = new Dictionary<FenceHolder, int>();
+ }
+
+ /// <summary>
+ /// Creates a new instance of the multiple fence holder, with a given buffer size in mind.
+ /// </summary>
+ /// <param name="size">Size of the buffer</param>
+ public MultiFenceHolder(int size)
+ {
+ _fences = new Dictionary<FenceHolder, int>();
+ _bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
+ }
+
+ /// <summary>
+ /// Adds buffer usage information to the uses list.
+ /// </summary>
+ /// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
+ /// <param name="offset">Offset of the buffer being used</param>
+ /// <param name="size">Size of the buffer region being used, in bytes</param>
+ public void AddBufferUse(int cbIndex, int offset, int size)
+ {
+ _bufferUsageBitmap.Add(cbIndex, offset, size);
+ }
+
+ /// <summary>
+ /// Removes all buffer usage information for a given command buffer.
+ /// </summary>
+ /// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
+ public void RemoveBufferUses(int cbIndex)
+ {
+ _bufferUsageBitmap?.Clear(cbIndex);
+ }
+
+ /// <summary>
+ /// Checks if a given range of a buffer is being used by a command buffer still being processed by the GPU.
+ /// </summary>
+ /// <param name="cbIndex">Index of the command buffer where the buffer is used</param>
+ /// <param name="offset">Offset of the buffer being used</param>
+ /// <param name="size">Size of the buffer region being used, in bytes</param>
+ /// <returns>True if in use, false otherwise</returns>
+ public bool IsBufferRangeInUse(int cbIndex, int offset, int size)
+ {
+ return _bufferUsageBitmap.OverlapsWith(cbIndex, offset, size);
+ }
+
+ /// <summary>
+ /// Checks if a given range of a buffer is being used by any command buffer still being processed by the GPU.
+ /// </summary>
+ /// <param name="offset">Offset of the buffer being used</param>
+ /// <param name="size">Size of the buffer region being used, in bytes</param>
+ /// <returns>True if in use, false otherwise</returns>
+ public bool IsBufferRangeInUse(int offset, int size)
+ {
+ return _bufferUsageBitmap.OverlapsWith(offset, size);
+ }
+
+ /// <summary>
+ /// Adds a fence to the holder.
+ /// </summary>
+ /// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
+ /// <param name="fence">Fence to be added</param>
+ public void AddFence(int cbIndex, FenceHolder fence)
+ {
+ lock (_fences)
+ {
+ _fences.TryAdd(fence, cbIndex);
+ }
+ }
+
+ /// <summary>
+ /// Removes a fence from the holder.
+ /// </summary>
+ /// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
+ /// <param name="fence">Fence to be removed</param>
+ public void RemoveFence(int cbIndex, FenceHolder fence)
+ {
+ lock (_fences)
+ {
+ _fences.Remove(fence);
+ }
+ }
+
+ /// <summary>
+ /// Wait until all the fences on the holder are signaled.
+ /// </summary>
+ /// <param name="api">Vulkan API instance</param>
+ /// <param name="device">GPU device that the fences belongs to</param>
+ public void WaitForFences(Vk api, Device device)
+ {
+ WaitForFencesImpl(api, device, 0, 0, false, 0UL);
+ }
+
+ /// <summary>
+ /// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
+ /// </summary>
+ /// <param name="api">Vulkan API instance</param>
+ /// <param name="device">GPU device that the fences belongs to</param>
+ /// <param name="offset">Start offset of the buffer range</param>
+ /// <param name="size">Size of the buffer range in bytes</param>
+ public void WaitForFences(Vk api, Device device, int offset, int size)
+ {
+ WaitForFencesImpl(api, device, offset, size, false, 0UL);
+ }
+
+ /// <summary>
+ /// Wait until all the fences on the holder are signaled, or the timeout expires.
+ /// </summary>
+ /// <param name="api">Vulkan API instance</param>
+ /// <param name="device">GPU device that the fences belongs to</param>
+ /// <param name="timeout">Timeout in nanoseconds</param>
+ /// <returns>True if all fences were signaled, false otherwise</returns>
+ public bool WaitForFences(Vk api, Device device, ulong timeout)
+ {
+ return WaitForFencesImpl(api, device, 0, 0, true, timeout);
+ }
+
+ /// <summary>
+ /// Wait until all the fences on the holder with buffer uses overlapping the specified range are signaled.
+ /// </summary>
+ /// <param name="api">Vulkan API instance</param>
+ /// <param name="device">GPU device that the fences belongs to</param>
+ /// <param name="offset">Start offset of the buffer range</param>
+ /// <param name="size">Size of the buffer range in bytes</param>
+ /// <param name="hasTimeout">Indicates if <paramref name="timeout"/> should be used</param>
+ /// <param name="timeout">Timeout in nanoseconds</param>
+ /// <returns>True if all fences were signaled before the timeout expired, false otherwise</returns>
+ private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
+ {
+ FenceHolder[] fenceHolders;
+ Fence[] fences;
+
+ lock (_fences)
+ {
+ fenceHolders = size != 0 ? GetOverlappingFences(offset, size) : _fences.Keys.ToArray();
+ fences = new Fence[fenceHolders.Length];
+
+ for (int i = 0; i < fenceHolders.Length; i++)
+ {
+ fences[i] = fenceHolders[i].Get();
+ }
+ }
+
+ if (fences.Length == 0)
+ {
+ return true;
+ }
+
+ bool signaled = true;
+
+ if (hasTimeout)
+ {
+ signaled = FenceHelper.AllSignaled(api, device, fences, timeout);
+ }
+ else
+ {
+ FenceHelper.WaitAllIndefinitely(api, device, fences);
+ }
+
+ for (int i = 0; i < fenceHolders.Length; i++)
+ {
+ fenceHolders[i].Put();
+ }
+
+ return signaled;
+ }
+
+ /// <summary>
+ /// Gets fences to wait for use of a given buffer region.
+ /// </summary>
+ /// <param name="offset">Offset of the range</param>
+ /// <param name="size">Size of the range in bytes</param>
+ /// <returns>Fences for the specified region</returns>
+ private FenceHolder[] GetOverlappingFences(int offset, int size)
+ {
+ List<FenceHolder> overlapping = new List<FenceHolder>();
+
+ foreach (var kv in _fences)
+ {
+ var fence = kv.Key;
+ var ownerCbIndex = kv.Value;
+
+ if (_bufferUsageBitmap.OverlapsWith(ownerCbIndex, offset, size))
+ {
+ overlapping.Add(fence);
+ }
+ }
+
+ return overlapping.ToArray();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/NativeArray.cs b/Ryujinx.Graphics.Vulkan/NativeArray.cs
new file mode 100644
index 00000000..9d66ce8d
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/NativeArray.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ unsafe class NativeArray<T> : IDisposable where T : unmanaged
+ {
+ public T* Pointer { get; private set; }
+ public int Length { get; }
+
+ public ref T this[int index]
+ {
+ get => ref Pointer[Checked(index)];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int Checked(int index)
+ {
+ if ((uint)index >= (uint)Length)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ return index;
+ }
+
+ public NativeArray(int length)
+ {
+ Pointer = (T*)Marshal.AllocHGlobal(checked(length * Unsafe.SizeOf<T>()));
+ Length = length;
+ }
+
+ public Span<T> ToSpan()
+ {
+ return new Span<T>(Pointer, Length);
+ }
+
+ public void Dispose()
+ {
+ Marshal.FreeHGlobal((IntPtr)Pointer);
+ Pointer = null;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs b/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs
new file mode 100644
index 00000000..fca13c31
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs
@@ -0,0 +1,89 @@
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ internal class PersistentFlushBuffer : IDisposable
+ {
+ private VulkanRenderer _gd;
+
+ private BufferHolder _flushStorage;
+
+ public PersistentFlushBuffer(VulkanRenderer 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<byte> 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<byte> 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 Span<byte> GetTextureData(CommandBufferPool cbp, TextureView view, int size, int layer, int level)
+ {
+ 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, layer, level, 1, 1, singleSlice: true);
+ }
+
+ 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
new file mode 100644
index 00000000..d73b2a66
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -0,0 +1,1220 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class PipelineBase : IDisposable
+ {
+ public const int DescriptorSetLayouts = 4;
+
+ public const int UniformSetIndex = 0;
+ public const int StorageSetIndex = 1;
+ public const int TextureSetIndex = 2;
+ public const int ImageSetIndex = 3;
+
+ protected readonly VulkanRenderer Gd;
+ protected readonly Device Device;
+ public readonly PipelineCache PipelineCache;
+
+ private PipelineDynamicState _dynamicState;
+ private PipelineState _newState;
+ private bool _stateDirty;
+ private GAL.PrimitiveTopology _topology;
+
+ private ulong _currentPipelineHandle;
+
+ protected Auto<DisposablePipeline> Pipeline;
+
+ protected PipelineBindPoint Pbp;
+
+ protected CommandBufferScoped Cbs;
+ protected CommandBufferScoped? PreloadCbs;
+ protected CommandBuffer CommandBuffer;
+
+ public CommandBufferScoped CurrentCommandBuffer => Cbs;
+
+ private ShaderCollection _program;
+
+ private Vector4<float>[] _renderScale = new Vector4<float>[73];
+ private int _fragmentScaleCount;
+
+ protected FramebufferParams FramebufferParams;
+ private Auto<DisposableFramebuffer> _framebuffer;
+ private Auto<DisposableRenderPass> _renderPass;
+ private int _writtenAttachmentCount;
+ private bool _renderPassActive;
+
+ private readonly DescriptorSetUpdater _descriptorSetUpdater;
+
+ private BufferState _indexBuffer;
+ private readonly BufferState[] _transformFeedbackBuffers;
+ private readonly BufferState[] _vertexBuffers;
+ protected Rectangle<int> ClearScissor;
+
+ public SupportBufferUpdater SupportBufferUpdater;
+
+ private bool _needsIndexBufferRebind;
+ private bool _needsTransformFeedbackBuffersRebind;
+ private bool _needsVertexBuffersRebind;
+
+ private bool _tfEnabled;
+ private bool _tfActive;
+
+ public ulong DrawCount { get; private set; }
+
+ public unsafe PipelineBase(VulkanRenderer gd, Device device)
+ {
+ Gd = gd;
+ Device = device;
+
+ var pipelineCacheCreateInfo = new PipelineCacheCreateInfo()
+ {
+ SType = StructureType.PipelineCacheCreateInfo
+ };
+
+ gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
+
+ _descriptorSetUpdater = new DescriptorSetUpdater(gd, this);
+
+ _transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
+ _vertexBuffers = new BufferState[Constants.MaxVertexBuffers + 1];
+
+ const int EmptyVbSize = 16;
+
+ using var emptyVb = gd.BufferManager.Create(gd, EmptyVbSize);
+ emptyVb.SetData(0, new byte[EmptyVbSize]);
+ _vertexBuffers[0] = new BufferState(emptyVb.GetBuffer(), 0, EmptyVbSize, 0UL);
+ _needsVertexBuffersRebind = true;
+
+ ClearScissor = new Rectangle<int>(0, 0, 0xffff, 0xffff);
+
+ var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f };
+ new Span<Vector4<float>>(_renderScale).Fill(defaultScale);
+
+ _newState.Initialize();
+ _newState.LineWidth = 1f;
+ _newState.SamplesCount = 1;
+ }
+
+ public void Initialize()
+ {
+ SupportBufferUpdater = new SupportBufferUpdater(Gd);
+ SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, SupportBuffer.RenderScaleMaxCount);
+ }
+
+ public unsafe void Barrier()
+ {
+ MemoryBarrier memoryBarrier = new MemoryBarrier()
+ {
+ SType = StructureType.MemoryBarrier,
+ SrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit,
+ DstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit
+ };
+
+ Gd.Api.CmdPipelineBarrier(
+ CommandBuffer,
+ PipelineStageFlags.PipelineStageFragmentShaderBit,
+ PipelineStageFlags.PipelineStageFragmentShaderBit,
+ 0,
+ 1,
+ memoryBarrier,
+ 0,
+ null,
+ 0,
+ null);
+ }
+
+ public void BeginTransformFeedback(GAL.PrimitiveTopology topology)
+ {
+ _tfEnabled = true;
+ }
+
+ public void ClearBuffer(BufferHandle destination, int offset, int size, uint value)
+ {
+ EndRenderPass();
+
+ var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, true).Get(Cbs, offset, size).Value;
+
+ BufferHolder.InsertBufferBarrier(
+ Gd,
+ Cbs.CommandBuffer,
+ dst,
+ BufferHolder.DefaultAccessFlags,
+ AccessFlags.AccessTransferWriteBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ offset,
+ size);
+
+ Gd.Api.CmdFillBuffer(CommandBuffer, dst, (ulong)offset, (ulong)size, value);
+
+ BufferHolder.InsertBufferBarrier(
+ Gd,
+ Cbs.CommandBuffer,
+ dst,
+ AccessFlags.AccessTransferWriteBit,
+ BufferHolder.DefaultAccessFlags,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ offset,
+ size);
+ }
+
+ public unsafe void ClearRenderTargetColor(int index, int layer, ColorF color)
+ {
+ if (FramebufferParams == null || !FramebufferParams.IsValidColorAttachment(index))
+ {
+ return;
+ }
+
+ if (_renderPass == null)
+ {
+ CreateRenderPass();
+ }
+
+ BeginRenderPass();
+
+ var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha));
+ var attachment = new ClearAttachment(ImageAspectFlags.ImageAspectColorBit, (uint)index, clearValue);
+ var clearRect = FramebufferParams?.GetClearRect(ClearScissor, layer) ?? default;
+
+ Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
+ }
+
+ public unsafe void ClearRenderTargetDepthStencil(int layer, float depthValue, bool depthMask, int stencilValue, int stencilMask)
+ {
+ // TODO: Use stencilMask (fully)
+
+ if (FramebufferParams == null || !FramebufferParams.HasDepthStencil)
+ {
+ return;
+ }
+
+ if (_renderPass == null)
+ {
+ CreateRenderPass();
+ }
+
+ BeginRenderPass();
+
+ var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue));
+ var flags = depthMask ? ImageAspectFlags.ImageAspectDepthBit : 0;
+
+ if (stencilMask != 0)
+ {
+ flags |= ImageAspectFlags.ImageAspectStencilBit;
+ }
+
+ var attachment = new ClearAttachment(flags, 0, clearValue);
+ var clearRect = FramebufferParams?.GetClearRect(ClearScissor, layer) ?? default;
+
+ Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
+ }
+
+ public void CommandBufferBarrier()
+ {
+ // TODO: More specific barrier?
+ Barrier();
+ }
+
+ public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
+ {
+ EndRenderPass();
+
+ var src = Gd.BufferManager.GetBuffer(CommandBuffer, source, false);
+ var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, true);
+
+ BufferHolder.Copy(Gd, Cbs, src, dst, srcOffset, dstOffset, size);
+ }
+
+ public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
+ {
+ if (!_program.IsLinked)
+ {
+ return;
+ }
+
+ EndRenderPass();
+ RecreatePipelineIfNeeded(PipelineBindPoint.Compute);
+
+ Gd.Api.CmdDispatch(CommandBuffer, (uint)groupsX, (uint)groupsY, (uint)groupsZ);
+ }
+
+ public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
+ {
+ if (!_program.IsLinked)
+ {
+ return;
+ }
+
+ RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
+ BeginRenderPass();
+ ResumeTransformFeedbackInternal();
+ DrawCount++;
+
+ if (_topology == GAL.PrimitiveTopology.Quads)
+ {
+ int quadsCount = vertexCount / 4;
+
+ for (int i = 0; i < quadsCount; i++)
+ {
+ Gd.Api.CmdDraw(CommandBuffer, 4, (uint)instanceCount, (uint)(firstVertex + i * 4), (uint)firstInstance);
+ }
+ }
+ else
+ {
+ Gd.Api.CmdDraw(CommandBuffer, (uint)vertexCount, (uint)instanceCount, (uint)firstVertex, (uint)firstInstance);
+ }
+ }
+
+ public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
+ {
+ if (!_program.IsLinked)
+ {
+ return;
+ }
+
+ RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
+ BeginRenderPass();
+ ResumeTransformFeedbackInternal();
+ DrawCount++;
+
+ if (_topology == GAL.PrimitiveTopology.Quads)
+ {
+ int quadsCount = indexCount / 4;
+
+ for (int i = 0; i < quadsCount; i++)
+ {
+ Gd.Api.CmdDrawIndexed(CommandBuffer, 4, (uint)instanceCount, (uint)(firstIndex + i * 4), firstVertex, (uint)firstInstance);
+ }
+ }
+ else
+ {
+ Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, (uint)firstIndex, firstVertex, (uint)firstInstance);
+ }
+ }
+
+ public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
+ {
+ if (texture is TextureView srcTexture)
+ {
+ SupportBufferUpdater.Commit();
+
+ var oldCullMode = _newState.CullMode;
+ var oldStencilTestEnable = _newState.StencilTestEnable;
+ var oldDepthTestEnable = _newState.DepthTestEnable;
+ var oldDepthWriteEnable = _newState.DepthWriteEnable;
+ var oldTopology = _newState.Topology;
+ var oldViewports = _dynamicState.Viewports;
+ var oldViewportsCount = _newState.ViewportsCount;
+
+ _newState.CullMode = CullModeFlags.CullModeNone;
+ _newState.StencilTestEnable = false;
+ _newState.DepthTestEnable = false;
+ _newState.DepthWriteEnable = false;
+ SignalStateChange();
+
+ Gd.HelperShader.DrawTexture(
+ Gd,
+ this,
+ srcTexture,
+ sampler,
+ srcRegion,
+ dstRegion);
+
+ _newState.CullMode = oldCullMode;
+ _newState.StencilTestEnable = oldStencilTestEnable;
+ _newState.DepthTestEnable = oldDepthTestEnable;
+ _newState.DepthWriteEnable = oldDepthWriteEnable;
+ _newState.Topology = oldTopology;
+
+ _dynamicState.Viewports = oldViewports;
+ _dynamicState.ViewportsCount = (int)oldViewportsCount;
+ _dynamicState.SetViewportsDirty();
+
+ _newState.ViewportsCount = oldViewportsCount;
+ SignalStateChange();
+ }
+ }
+
+ public void EndTransformFeedback()
+ {
+ PauseTransformFeedbackInternal();
+ _tfEnabled = false;
+ }
+
+ public void MultiDrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
+ {
+ if (!Gd.Capabilities.SupportsIndirectParameters)
+ {
+ throw new NotSupportedException();
+ }
+
+ if (_program.LinkStatus != ProgramLinkStatus.Success)
+ {
+ return;
+ }
+
+ RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
+ BeginRenderPass();
+ ResumeTransformFeedbackInternal();
+ DrawCount++;
+
+ var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, indirectBuffer.Handle, true).Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
+ var countBuffer = Gd.BufferManager.GetBuffer(CommandBuffer, parameterBuffer.Handle, true).Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
+
+ Gd.DrawIndirectCountApi.CmdDrawIndirectCount(
+ CommandBuffer,
+ buffer,
+ (ulong)indirectBuffer.Offset,
+ countBuffer,
+ (ulong)parameterBuffer.Offset,
+ (uint)maxDrawCount,
+ (uint)stride);
+ }
+
+ public void MultiDrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
+ {
+ if (!Gd.Capabilities.SupportsIndirectParameters)
+ {
+ throw new NotSupportedException();
+ }
+
+ if (_program.LinkStatus != ProgramLinkStatus.Success)
+ {
+ return;
+ }
+
+ RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
+ BeginRenderPass();
+ ResumeTransformFeedbackInternal();
+ DrawCount++;
+
+ var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, indirectBuffer.Handle, true).Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
+ var countBuffer = Gd.BufferManager.GetBuffer(CommandBuffer, parameterBuffer.Handle, true).Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
+
+ Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount(
+ CommandBuffer,
+ buffer,
+ (ulong)indirectBuffer.Offset,
+ countBuffer,
+ (ulong)parameterBuffer.Offset,
+ (uint)maxDrawCount,
+ (uint)stride);
+ }
+
+ public void SetAlphaTest(bool enable, float reference, GAL.CompareOp op)
+ {
+ // This is currently handled using shader specialization, as Vulkan does not support alpha test.
+ // In the future, we may want to use this to write the reference value into the support buffer,
+ // to avoid creating one version of the shader per reference value used.
+ }
+
+ public void SetBlendState(int index, BlendDescriptor blend)
+ {
+ ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[index];
+
+ vkBlend.BlendEnable = blend.Enable;
+ vkBlend.SrcColorBlendFactor = blend.ColorSrcFactor.Convert();
+ vkBlend.DstColorBlendFactor = blend.ColorDstFactor.Convert();
+ vkBlend.ColorBlendOp = blend.ColorOp.Convert();
+ vkBlend.SrcAlphaBlendFactor = blend.AlphaSrcFactor.Convert();
+ vkBlend.DstAlphaBlendFactor = blend.AlphaDstFactor.Convert();
+ vkBlend.AlphaBlendOp = blend.AlphaOp.Convert();
+
+ _newState.BlendConstantR = blend.BlendConstant.Red;
+ _newState.BlendConstantG = blend.BlendConstant.Green;
+ _newState.BlendConstantB = blend.BlendConstant.Blue;
+ _newState.BlendConstantA = blend.BlendConstant.Alpha;
+
+ SignalStateChange();
+ }
+
+ public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
+ {
+ _dynamicState.SetDepthBias(factor, units, clamp);
+
+ _newState.DepthBiasEnable = enables != 0;
+ SignalStateChange();
+ }
+
+ public void SetDepthClamp(bool clamp)
+ {
+ _newState.DepthClampEnable = clamp;
+ SignalStateChange();
+ }
+
+ public void SetDepthMode(DepthMode mode)
+ {
+ // Currently this is emulated on the shader, because Vulkan had no support for changing the depth mode.
+ // In the future, we may want to use the VK_EXT_depth_clip_control extension to change it here.
+ }
+
+ public void SetDepthTest(DepthTestDescriptor depthTest)
+ {
+ _newState.DepthTestEnable = depthTest.TestEnable;
+ _newState.DepthWriteEnable = depthTest.WriteEnable;
+ _newState.DepthCompareOp = depthTest.Func.Convert();
+ SignalStateChange();
+ }
+
+ public void SetFaceCulling(bool enable, Face face)
+ {
+ _newState.CullMode = enable ? face.Convert() : CullModeFlags.CullModeNone;
+ SignalStateChange();
+ }
+
+ public void SetFrontFace(GAL.FrontFace frontFace)
+ {
+ _newState.FrontFace = frontFace.Convert();
+ SignalStateChange();
+ }
+
+ public void SetImage(int binding, ITexture image, GAL.Format imageFormat)
+ {
+ _descriptorSetUpdater.SetImage(binding, image, imageFormat);
+ }
+
+ public void SetIndexBuffer(BufferRange buffer, GAL.IndexType type)
+ {
+ _indexBuffer.Dispose();
+
+ if (buffer.Handle != BufferHandle.Null)
+ {
+ Auto<DisposableBuffer> ib = null;
+ int offset = buffer.Offset;
+ int size = buffer.Size;
+
+ if (type == GAL.IndexType.UByte && !Gd.Capabilities.SupportsIndexTypeUint8)
+ {
+ ib = Gd.BufferManager.GetBufferI8ToI16(Cbs, buffer.Handle, offset, size);
+ offset = 0;
+ size *= 2;
+ type = GAL.IndexType.UShort;
+ }
+ else
+ {
+ ib = Gd.BufferManager.GetBuffer(CommandBuffer, buffer.Handle, false);
+ }
+
+ _indexBuffer = new BufferState(ib, offset, size, type.Convert());
+ }
+ else
+ {
+ _indexBuffer = BufferState.Null;
+ }
+
+ _indexBuffer.BindIndexBuffer(Gd.Api, Cbs);
+ }
+
+ public void SetLineParameters(float width, bool smooth)
+ {
+ _newState.LineWidth = width;
+ SignalStateChange();
+ }
+
+ public void SetLogicOpState(bool enable, LogicalOp op)
+ {
+ _newState.LogicOpEnable = enable;
+ _newState.LogicOp = op.Convert();
+ SignalStateChange();
+ }
+
+ public void SetMultisampleState(MultisampleDescriptor multisample)
+ {
+ _newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable;
+ _newState.AlphaToOneEnable = multisample.AlphaToOneEnable;
+ SignalStateChange();
+ }
+
+ public void SetOrigin(Origin origin)
+ {
+ // TODO.
+ }
+
+ public unsafe void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
+ {
+ _newState.PatchControlPoints = (uint)vertices;
+ SignalStateChange();
+
+ // TODO: Default levels (likely needs emulation on shaders?)
+ }
+
+ public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
+ {
+ // TODO.
+ }
+
+ public void SetPolygonMode(GAL.PolygonMode frontMode, GAL.PolygonMode backMode)
+ {
+ // TODO.
+ }
+
+ public void SetPrimitiveRestart(bool enable, int index)
+ {
+ _newState.PrimitiveRestartEnable = enable;
+ // TODO: What to do about the index?
+ SignalStateChange();
+ }
+
+ public void SetPrimitiveTopology(GAL.PrimitiveTopology topology)
+ {
+ _topology = topology;
+
+ var vkTopology = topology.Convert();
+
+ _newState.Topology = vkTopology;
+
+ SignalStateChange();
+ }
+
+ public void SetProgram(IProgram program)
+ {
+ var internalProgram = (ShaderCollection)program;
+ var stages = internalProgram.GetInfos();
+
+ _program = internalProgram;
+
+ _descriptorSetUpdater.SetProgram(internalProgram);
+
+ _newState.PipelineLayout = internalProgram.PipelineLayout;
+ _newState.StagesCount = (uint)stages.Length;
+
+ stages.CopyTo(_newState.Stages.ToSpan().Slice(0, stages.Length));
+
+ SignalStateChange();
+ }
+
+ protected virtual void SignalAttachmentChange()
+ {
+ }
+
+ public void SetRasterizerDiscard(bool discard)
+ {
+ _newState.RasterizerDiscardEnable = discard;
+ SignalStateChange();
+ }
+
+ public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
+ {
+ int count = Math.Min(Constants.MaxRenderTargets, componentMask.Length);
+ int writtenAttachments = 0;
+
+ for (int i = 0; i < count; i++)
+ {
+ ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i];
+
+ vkBlend.ColorWriteMask = (ColorComponentFlags)componentMask[i];
+
+ if (componentMask[i] != 0)
+ {
+ writtenAttachments++;
+ }
+ }
+
+ SignalStateChange();
+
+ if (writtenAttachments != _writtenAttachmentCount)
+ {
+ SignalAttachmentChange();
+ _writtenAttachmentCount = writtenAttachments;
+ }
+ }
+
+ public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
+ {
+ FramebufferParams?.UpdateModifications();
+ CreateFramebuffer(colors, depthStencil);
+ CreateRenderPass();
+ SignalStateChange();
+ SignalAttachmentChange();
+ }
+
+ public void SetRenderTargetScale(float scale)
+ {
+ _renderScale[0].X = scale;
+ SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, 1); // Just the first element.
+ }
+
+ public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
+ {
+ int maxScissors = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
+ int count = Math.Min(maxScissors, regions.Length);
+ if (count > 0)
+ {
+ ClearScissor = regions[0];
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ var region = regions[i];
+ var offset = new Offset2D(region.X, region.Y);
+ var extent = new Extent2D((uint)region.Width, (uint)region.Height);
+
+ _dynamicState.SetScissor(i, new Rect2D(offset, extent));
+ }
+
+ _dynamicState.ScissorsCount = count;
+
+ _newState.ScissorsCount = (uint)count;
+ SignalStateChange();
+ }
+
+ public void SetStencilTest(StencilTestDescriptor stencilTest)
+ {
+ _dynamicState.SetStencilMasks(
+ (uint)stencilTest.BackFuncMask,
+ (uint)stencilTest.BackMask,
+ (uint)stencilTest.BackFuncRef,
+ (uint)stencilTest.FrontFuncMask,
+ (uint)stencilTest.FrontMask,
+ (uint)stencilTest.FrontFuncRef);
+
+ _newState.StencilTestEnable = stencilTest.TestEnable;
+ _newState.StencilBackFailOp = stencilTest.BackSFail.Convert();
+ _newState.StencilBackPassOp = stencilTest.BackDpPass.Convert();
+ _newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert();
+ _newState.StencilBackCompareOp = stencilTest.BackFunc.Convert();
+ _newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert();
+ _newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
+ _newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
+ _newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
+ SignalStateChange();
+ }
+
+ public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers)
+ {
+ _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers);
+ }
+
+ public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
+ {
+ _descriptorSetUpdater.SetTextureAndSampler(Cbs, stage, binding, texture, sampler);
+ }
+
+ public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
+ {
+ PauseTransformFeedbackInternal();
+
+ int count = Math.Min(Constants.MaxTransformFeedbackBuffers, buffers.Length);
+
+ for (int i = 0; i < count; i++)
+ {
+ var range = buffers[i];
+
+ _transformFeedbackBuffers[i].Dispose();
+
+ if (range.Handle != BufferHandle.Null)
+ {
+ _transformFeedbackBuffers[i] = new BufferState(Gd.BufferManager.GetBuffer(CommandBuffer, range.Handle, true), range.Offset, range.Size);
+ _transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i);
+ }
+ else
+ {
+ _transformFeedbackBuffers[i] = BufferState.Null;
+ }
+ }
+ }
+
+ public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers)
+ {
+ _descriptorSetUpdater.SetUniformBuffers(CommandBuffer, first, buffers);
+ }
+
+ public void SetUserClipDistance(int index, bool enableClip)
+ {
+ // TODO.
+ }
+
+ public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
+ {
+ int count = Math.Min(Constants.MaxVertexAttributes, vertexAttribs.Length);
+
+ for (int i = 0; i < count; i++)
+ {
+ var attribute = vertexAttribs[i];
+ var bufferIndex = attribute.IsZero ? 0 : attribute.BufferIndex + 1;
+
+ _newState.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription(
+ (uint)i,
+ (uint)bufferIndex,
+ FormatTable.GetFormat(attribute.Format),
+ (uint)attribute.Offset);
+ }
+
+ _newState.VertexAttributeDescriptionsCount = (uint)count;
+ SignalStateChange();
+ }
+
+ public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
+ {
+ int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length);
+
+ _newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
+
+ int validCount = 1;
+
+ for (int i = 0; i < count; i++)
+ {
+ var vertexBuffer = vertexBuffers[i];
+
+ // TODO: Support divisor > 1
+ var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex;
+
+ if (vertexBuffer.Buffer.Handle != BufferHandle.Null)
+ {
+ var vb = Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
+ if (vb != null)
+ {
+ int binding = i + 1;
+ int descriptorIndex = validCount++;
+
+ _newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription(
+ (uint)binding,
+ (uint)vertexBuffer.Stride,
+ inputRate);
+
+ int vbSize = vertexBuffer.Buffer.Size;
+
+ if (Gd.Vendor == Vendor.Amd && vertexBuffer.Stride > 0)
+ {
+ // AMD has a bug where if offset + stride * count is greater than
+ // the size, then the last attribute will have the wrong value.
+ // As a workaround, simply use the full buffer size.
+ int remainder = vbSize % vertexBuffer.Stride;
+ if (remainder != 0)
+ {
+ vbSize += vertexBuffer.Stride - remainder;
+ }
+ }
+
+ _vertexBuffers[binding].Dispose();
+ _vertexBuffers[binding] = new BufferState(
+ vb,
+ vertexBuffer.Buffer.Offset,
+ vbSize,
+ (ulong)vertexBuffer.Stride);
+
+ _vertexBuffers[binding].BindVertexBuffer(Gd, Cbs, (uint)binding);
+ }
+ }
+ }
+
+ _newState.VertexBindingDescriptionsCount = (uint)validCount;
+ SignalStateChange();
+ }
+
+ public void SetViewports(ReadOnlySpan<GAL.Viewport> viewports, bool disableTransform)
+ {
+ int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
+ int count = Math.Min(maxViewports, viewports.Length);
+
+ static float Clamp(float value)
+ {
+ return Math.Clamp(value, 0f, 1f);
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ var viewport = viewports[i];
+
+ _dynamicState.SetViewport(i, new Silk.NET.Vulkan.Viewport(
+ viewport.Region.X,
+ viewport.Region.Y,
+ viewport.Region.Width == 0f ? 1f : viewport.Region.Width,
+ viewport.Region.Height == 0f ? 1f : viewport.Region.Height,
+ Clamp(viewport.DepthNear),
+ Clamp(viewport.DepthFar)));
+ }
+
+ _dynamicState.ViewportsCount = count;
+
+ float disableTransformF = disableTransform ? 1.0f : 0.0f;
+ if (SupportBufferUpdater.Data.ViewportInverse.W != disableTransformF || disableTransform)
+ {
+ float scale = _renderScale[0].X;
+ SupportBufferUpdater.UpdateViewportInverse(new Vector4<float>
+ {
+ X = scale * 2f / viewports[0].Region.Width,
+ Y = scale * 2f / viewports[0].Region.Height,
+ Z = 1,
+ W = disableTransformF
+ });
+ }
+
+ _newState.ViewportsCount = (uint)count;
+ SignalStateChange();
+ }
+
+ public unsafe void TextureBarrier()
+ {
+ MemoryBarrier memoryBarrier = new MemoryBarrier()
+ {
+ SType = StructureType.MemoryBarrier,
+ SrcAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit,
+ DstAccessMask = AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit
+ };
+
+ Gd.Api.CmdPipelineBarrier(
+ CommandBuffer,
+ PipelineStageFlags.PipelineStageFragmentShaderBit,
+ PipelineStageFlags.PipelineStageFragmentShaderBit,
+ 0,
+ 1,
+ memoryBarrier,
+ 0,
+ null,
+ 0,
+ null);
+ }
+
+ public void TextureBarrierTiled()
+ {
+ TextureBarrier();
+ }
+
+ public void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount)
+ {
+ bool changed = false;
+
+ for (int index = 0; index < totalCount; index++)
+ {
+ if (_renderScale[1 + index].X != scales[index])
+ {
+ _renderScale[1 + index].X = scales[index];
+ changed = true;
+ }
+ }
+
+ // Only update fragment count if there are scales after it for the vertex stage.
+ if (fragmentCount != totalCount && fragmentCount != _fragmentScaleCount)
+ {
+ _fragmentScaleCount = fragmentCount;
+ SupportBufferUpdater.UpdateFragmentRenderScaleCount(_fragmentScaleCount);
+ }
+
+ if (changed)
+ {
+ SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, 1 + totalCount);
+ }
+ }
+
+ protected void SignalCommandBufferChange()
+ {
+ _needsIndexBufferRebind = true;
+ _needsTransformFeedbackBuffersRebind = true;
+ _needsVertexBuffersRebind = true;
+
+ _descriptorSetUpdater.SignalCommandBufferChange();
+ _dynamicState.ForceAllDirty();
+ _currentPipelineHandle = 0;
+ }
+
+ private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil)
+ {
+ FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
+ UpdatePipelineAttachmentFormats();
+ _newState.SamplesCount = FramebufferParams.AttachmentSamples.Length != 0 ? FramebufferParams.AttachmentSamples[0] : 1;
+ }
+
+ protected void UpdatePipelineAttachmentFormats()
+ {
+ var dstAttachmentFormats = _newState.Internal.AttachmentFormats.ToSpan();
+ FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
+
+ int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex + (FramebufferParams.HasDepthStencil ? 1 : 0);
+ for (int i = FramebufferParams.AttachmentFormats.Length; i <= maxAttachmentIndex; i++)
+ {
+ dstAttachmentFormats[i] = 0;
+ }
+
+ _newState.ColorBlendAttachmentStateCount = (uint)(FramebufferParams.MaxColorAttachmentIndex + 1);
+ _newState.HasDepthStencil = FramebufferParams.HasDepthStencil;
+ }
+
+ protected unsafe void CreateRenderPass()
+ {
+ const int MaxAttachments = Constants.MaxRenderTargets + 1;
+
+ AttachmentDescription[] attachmentDescs = null;
+
+ var subpass = new SubpassDescription()
+ {
+ PipelineBindPoint = PipelineBindPoint.Graphics
+ };
+
+ AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
+
+ var hasFramebuffer = FramebufferParams != null;
+
+ if (hasFramebuffer && FramebufferParams.AttachmentsCount != 0)
+ {
+ attachmentDescs = new AttachmentDescription[FramebufferParams.AttachmentsCount];
+
+ for (int i = 0; i < FramebufferParams.AttachmentsCount; i++)
+ {
+ int bindIndex = FramebufferParams.AttachmentIndices[i];
+
+ attachmentDescs[i] = new AttachmentDescription(
+ 0,
+ FramebufferParams.AttachmentFormats[i],
+ TextureStorage.ConvertToSampleCountFlags(FramebufferParams.AttachmentSamples[i]),
+ AttachmentLoadOp.Load,
+ AttachmentStoreOp.Store,
+ AttachmentLoadOp.Load,
+ AttachmentStoreOp.Store,
+ ImageLayout.General,
+ ImageLayout.General);
+ }
+
+ int colorAttachmentsCount = FramebufferParams.ColorAttachmentsCount;
+
+ if (colorAttachmentsCount > MaxAttachments - 1)
+ {
+ colorAttachmentsCount = MaxAttachments - 1;
+ }
+
+ if (colorAttachmentsCount != 0)
+ {
+ int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex;
+ subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
+ subpass.PColorAttachments = &attachmentReferences[0];
+
+ // Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
+ for (int i = 0; i <= maxAttachmentIndex; i++)
+ {
+ subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
+ }
+
+ for (int i = 0; i < colorAttachmentsCount; i++)
+ {
+ int bindIndex = FramebufferParams.AttachmentIndices[i];
+
+ subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
+ }
+ }
+
+ if (FramebufferParams.HasDepthStencil)
+ {
+ uint dsIndex = (uint)FramebufferParams.AttachmentsCount - 1;
+
+ subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
+ *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
+ }
+ }
+
+ var subpassDependency = new SubpassDependency(
+ 0,
+ 0,
+ PipelineStageFlags.PipelineStageAllGraphicsBit,
+ PipelineStageFlags.PipelineStageAllGraphicsBit,
+ AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit | AccessFlags.AccessColorAttachmentWriteBit,
+ AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit | AccessFlags.AccessShaderReadBit,
+ 0);
+
+ fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
+ {
+ var renderPassCreateInfo = new RenderPassCreateInfo()
+ {
+ SType = StructureType.RenderPassCreateInfo,
+ PAttachments = pAttachmentDescs,
+ AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0,
+ PSubpasses = &subpass,
+ SubpassCount = 1,
+ PDependencies = &subpassDependency,
+ DependencyCount = 1
+ };
+
+ Gd.Api.CreateRenderPass(Device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
+
+ _renderPass?.Dispose();
+ _renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(Gd.Api, Device, renderPass));
+ }
+
+ EndRenderPass();
+
+ _framebuffer?.Dispose();
+ _framebuffer = hasFramebuffer ? FramebufferParams.Create(Gd.Api, Cbs, _renderPass) : null;
+ }
+
+ protected void SignalStateChange()
+ {
+ _stateDirty = true;
+ }
+
+ private void RecreatePipelineIfNeeded(PipelineBindPoint pbp)
+ {
+ _dynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
+
+ // Commit changes to the support buffer before drawing.
+ SupportBufferUpdater.Commit();
+
+ if (_stateDirty || Pbp != pbp)
+ {
+ CreatePipeline(pbp);
+ _stateDirty = false;
+ Pbp = pbp;
+ }
+
+ if (_needsIndexBufferRebind)
+ {
+ _indexBuffer.BindIndexBuffer(Gd.Api, Cbs);
+ _needsIndexBufferRebind = false;
+ }
+
+ if (_needsTransformFeedbackBuffersRebind)
+ {
+ PauseTransformFeedbackInternal();
+
+ for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++)
+ {
+ _transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i);
+ }
+
+ _needsTransformFeedbackBuffersRebind = false;
+ }
+
+ if (_needsVertexBuffersRebind)
+ {
+ for (int i = 0; i < Constants.MaxVertexBuffers + 1; i++)
+ {
+ _vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i);
+ }
+
+ _needsVertexBuffersRebind = false;
+ }
+
+ _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, pbp);
+ }
+
+ private void CreatePipeline(PipelineBindPoint pbp)
+ {
+ // We can only create a pipeline if the have the shader stages set.
+ if (_newState.Stages != null)
+ {
+ if (pbp == PipelineBindPoint.Graphics && _renderPass == null)
+ {
+ CreateRenderPass();
+ }
+
+ var pipeline = pbp == PipelineBindPoint.Compute
+ ? _newState.CreateComputePipeline(Gd, Device, _program, PipelineCache)
+ : _newState.CreateGraphicsPipeline(Gd, Device, _program, PipelineCache, _renderPass.Get(Cbs).Value);
+
+ ulong pipelineHandle = pipeline.GetUnsafe().Value.Handle;
+
+ if (_currentPipelineHandle != pipelineHandle)
+ {
+ _currentPipelineHandle = pipelineHandle;
+ Pipeline = pipeline;
+
+ PauseTransformFeedbackInternal();
+ Gd.Api.CmdBindPipeline(CommandBuffer, pbp, Pipeline.Get(Cbs).Value);
+ }
+ }
+ }
+
+ private unsafe void BeginRenderPass()
+ {
+ if (!_renderPassActive)
+ {
+ var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height));
+ var clearValue = new ClearValue();
+
+ var renderPassBeginInfo = new RenderPassBeginInfo()
+ {
+ SType = StructureType.RenderPassBeginInfo,
+ RenderPass = _renderPass.Get(Cbs).Value,
+ Framebuffer = _framebuffer.Get(Cbs).Value,
+ RenderArea = renderArea,
+ PClearValues = &clearValue,
+ ClearValueCount = 1
+ };
+
+ Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline);
+ _renderPassActive = true;
+ }
+ }
+
+ public void EndRenderPass()
+ {
+ if (_renderPassActive)
+ {
+ PauseTransformFeedbackInternal();
+ Gd.Api.CmdEndRenderPass(CommandBuffer);
+ SignalRenderPassEnd();
+ _renderPassActive = false;
+ }
+ }
+
+ protected virtual void SignalRenderPassEnd()
+ {
+ }
+
+ private void PauseTransformFeedbackInternal()
+ {
+ if (_tfEnabled && _tfActive)
+ {
+ EndTransformFeedbackInternal();
+ _tfActive = false;
+ }
+ }
+
+ private void ResumeTransformFeedbackInternal()
+ {
+ if (_tfEnabled && !_tfActive)
+ {
+ BeginTransformFeedbackInternal();
+ _tfActive = true;
+ }
+ }
+
+ private unsafe void BeginTransformFeedbackInternal()
+ {
+ Gd.TransformFeedbackApi.CmdBeginTransformFeedback(CommandBuffer, 0, 0, null, null);
+ }
+
+ private unsafe void EndTransformFeedbackInternal()
+ {
+ Gd.TransformFeedbackApi.CmdEndTransformFeedback(CommandBuffer, 0, 0, null, null);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _renderPass?.Dispose();
+ _framebuffer?.Dispose();
+ _indexBuffer.Dispose();
+ _newState.Dispose();
+ _descriptorSetUpdater.Dispose();
+
+ for (int i = 0; i < _vertexBuffers.Length; i++)
+ {
+ _vertexBuffers[i].Dispose();
+ }
+
+ for (int i = 0; i < _transformFeedbackBuffers.Length; i++)
+ {
+ _transformFeedbackBuffers[i].Dispose();
+ }
+
+ Pipeline?.Dispose();
+
+ unsafe
+ {
+ Gd.Api.DestroyPipelineCache(Device, PipelineCache, null);
+ }
+
+ SupportBufferUpdater.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
new file mode 100644
index 00000000..ff194d71
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
@@ -0,0 +1,278 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class PipelineConverter
+ {
+ public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
+ {
+ const int MaxAttachments = Constants.MaxRenderTargets + 1;
+
+ AttachmentDescription[] attachmentDescs = null;
+
+ var subpass = new SubpassDescription()
+ {
+ PipelineBindPoint = PipelineBindPoint.Graphics
+ };
+
+ AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
+
+ Span<int> attachmentIndices = stackalloc int[MaxAttachments];
+ Span<Silk.NET.Vulkan.Format> attachmentFormats = stackalloc Silk.NET.Vulkan.Format[MaxAttachments];
+
+ int attachmentCount = 0;
+ int colorCount = 0;
+ int maxColorAttachmentIndex = 0;
+
+ for (int i = 0; i < state.AttachmentEnable.Length; i++)
+ {
+ if (state.AttachmentEnable[i])
+ {
+ maxColorAttachmentIndex = i;
+
+ attachmentFormats[attachmentCount] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]);
+
+ attachmentIndices[attachmentCount++] = i;
+ colorCount++;
+ }
+ }
+
+ if (state.DepthStencilEnable)
+ {
+ attachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat);
+ }
+
+ if (attachmentCount != 0)
+ {
+ attachmentDescs = new AttachmentDescription[attachmentCount];
+
+ for (int i = 0; i < attachmentCount; i++)
+ {
+ int bindIndex = attachmentIndices[i];
+
+ attachmentDescs[i] = new AttachmentDescription(
+ 0,
+ attachmentFormats[i],
+ TextureStorage.ConvertToSampleCountFlags((uint)state.SamplesCount),
+ AttachmentLoadOp.Load,
+ AttachmentStoreOp.Store,
+ AttachmentLoadOp.Load,
+ AttachmentStoreOp.Store,
+ ImageLayout.General,
+ ImageLayout.General);
+ }
+
+ int colorAttachmentsCount = colorCount;
+
+ if (colorAttachmentsCount > MaxAttachments - 1)
+ {
+ colorAttachmentsCount = MaxAttachments - 1;
+ }
+
+ if (colorAttachmentsCount != 0)
+ {
+ int maxAttachmentIndex = Constants.MaxRenderTargets - 1;
+ subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
+ subpass.PColorAttachments = &attachmentReferences[0];
+
+ // Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
+ for (int i = 0; i <= maxAttachmentIndex; i++)
+ {
+ subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
+ }
+
+ for (int i = 0; i < colorAttachmentsCount; i++)
+ {
+ int bindIndex = attachmentIndices[i];
+
+ subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
+ }
+ }
+
+ if (state.DepthStencilEnable)
+ {
+ uint dsIndex = (uint)attachmentCount - 1;
+
+ subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
+ *subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
+ }
+ }
+
+ var subpassDependency = new SubpassDependency(
+ 0,
+ 0,
+ PipelineStageFlags.PipelineStageAllGraphicsBit,
+ PipelineStageFlags.PipelineStageAllGraphicsBit,
+ AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit,
+ AccessFlags.AccessMemoryReadBit | AccessFlags.AccessMemoryWriteBit,
+ 0);
+
+ fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
+ {
+ var renderPassCreateInfo = new RenderPassCreateInfo()
+ {
+ SType = StructureType.RenderPassCreateInfo,
+ PAttachments = pAttachmentDescs,
+ AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0,
+ PSubpasses = &subpass,
+ SubpassCount = 1,
+ PDependencies = &subpassDependency,
+ DependencyCount = 1
+ };
+
+ gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
+
+ return new DisposableRenderPass(gd.Api, device, renderPass);
+ }
+ }
+
+ public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd)
+ {
+ PipelineState pipeline = new PipelineState();
+ pipeline.Initialize();
+
+ // It is assumed that Dynamic State is enabled when this conversion is used.
+
+ pipeline.BlendConstantA = state.BlendDescriptors[0].BlendConstant.Alpha;
+ pipeline.BlendConstantB = state.BlendDescriptors[0].BlendConstant.Blue;
+ pipeline.BlendConstantG = state.BlendDescriptors[0].BlendConstant.Green;
+ pipeline.BlendConstantR = state.BlendDescriptors[0].BlendConstant.Red;
+
+ pipeline.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.CullModeNone;
+
+ pipeline.DepthBoundsTestEnable = false; // Not implemented.
+
+ pipeline.DepthClampEnable = state.DepthClampEnable;
+
+ pipeline.DepthTestEnable = state.DepthTest.TestEnable;
+ pipeline.DepthWriteEnable = state.DepthTest.WriteEnable;
+ pipeline.DepthCompareOp = state.DepthTest.Func.Convert();
+
+ pipeline.FrontFace = state.FrontFace.Convert();
+
+ pipeline.HasDepthStencil = state.DepthStencilEnable;
+ pipeline.LineWidth = state.LineWidth;
+ pipeline.LogicOpEnable = state.LogicOpEnable;
+ pipeline.LogicOp = state.LogicOp.Convert();
+
+ pipeline.MinDepthBounds = 0f; // Not implemented.
+ pipeline.MaxDepthBounds = 0f; // Not implemented.
+
+ pipeline.PatchControlPoints = state.PatchControlPoints;
+ pipeline.PolygonMode = Silk.NET.Vulkan.PolygonMode.Fill; // Not implemented.
+ pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
+ pipeline.RasterizerDiscardEnable = state.RasterizerDiscard;
+ pipeline.SamplesCount = (uint)state.SamplesCount;
+
+ if (gd.Capabilities.SupportsMultiView)
+ {
+ pipeline.ScissorsCount = Constants.MaxViewports;
+ pipeline.ViewportsCount = Constants.MaxViewports;
+ }
+ else
+ {
+ pipeline.ScissorsCount = 1;
+ pipeline.ViewportsCount = 1;
+ }
+
+ pipeline.DepthBiasEnable = state.BiasEnable != 0;
+
+ // Stencil masks and ref are dynamic, so are 0 in the Vulkan pipeline.
+
+ pipeline.StencilFrontFailOp = state.StencilTest.FrontSFail.Convert();
+ pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
+ pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert();
+ pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert();
+ pipeline.StencilFrontCompareMask = 0;
+ pipeline.StencilFrontWriteMask = 0;
+ pipeline.StencilFrontReference = 0;
+
+ pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
+ pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
+ pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
+ pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
+ pipeline.StencilBackCompareMask = 0;
+ pipeline.StencilBackWriteMask = 0;
+ pipeline.StencilBackReference = 0;
+
+ pipeline.StencilTestEnable = state.StencilTest.TestEnable;
+
+ pipeline.Topology = state.Topology.Convert();
+
+ int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
+
+ for (int i = 0; i < vaCount; i++)
+ {
+ var attribute = state.VertexAttribs[i];
+ var bufferIndex = attribute.IsZero ? 0 : attribute.BufferIndex + 1;
+
+ pipeline.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription(
+ (uint)i,
+ (uint)bufferIndex,
+ FormatTable.GetFormat(attribute.Format),
+ (uint)attribute.Offset);
+ }
+
+ int descriptorIndex = 1;
+ pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
+
+ int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
+
+ for (int i = 0; i < vbCount; i++)
+ {
+ var vertexBuffer = state.VertexBuffers[i];
+
+ if (vertexBuffer.Enable)
+ {
+ var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex;
+
+ // TODO: Support divisor > 1
+ pipeline.Internal.VertexBindingDescriptions[descriptorIndex++] = new VertexInputBindingDescription(
+ (uint)i + 1,
+ (uint)vertexBuffer.Stride,
+ inputRate);
+ }
+ }
+
+ pipeline.VertexBindingDescriptionsCount = (uint)descriptorIndex;
+
+ // NOTE: Viewports, Scissors are dynamic.
+
+ for (int i = 0; i < 8; i++)
+ {
+ var blend = state.BlendDescriptors[i];
+
+ pipeline.Internal.ColorBlendAttachmentState[i] = new PipelineColorBlendAttachmentState(
+ blend.Enable,
+ blend.ColorSrcFactor.Convert(),
+ blend.ColorDstFactor.Convert(),
+ blend.ColorOp.Convert(),
+ blend.AlphaSrcFactor.Convert(),
+ blend.AlphaDstFactor.Convert(),
+ blend.AlphaOp.Convert(),
+ (ColorComponentFlags)state.ColorWriteMask[i]);
+ }
+
+ int maxAttachmentIndex = 0;
+ for (int i = 0; i < 8; i++)
+ {
+ if (state.AttachmentEnable[i])
+ {
+ pipeline.Internal.AttachmentFormats[maxAttachmentIndex++] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]);
+ }
+ }
+
+ if (state.DepthStencilEnable)
+ {
+ pipeline.Internal.AttachmentFormats[maxAttachmentIndex++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat);
+ }
+
+ pipeline.ColorBlendAttachmentStateCount = 8;
+ pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
+
+ return pipeline;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs b/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs
new file mode 100644
index 00000000..2553101d
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs
@@ -0,0 +1,138 @@
+using Ryujinx.Common.Memory;
+using Silk.NET.Vulkan;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct PipelineDynamicState
+ {
+ private float _depthBiasSlopeFactor;
+ private float _depthBiasConstantFactor;
+ private float _depthBiasClamp;
+
+ public int ScissorsCount;
+ private Array16<Rect2D> _scissors;
+
+ private uint _backCompareMask;
+ private uint _backWriteMask;
+ private uint _backReference;
+ private uint _frontCompareMask;
+ private uint _frontWriteMask;
+ private uint _frontReference;
+
+ public int ViewportsCount;
+ public Array16<Viewport> Viewports;
+
+ private enum DirtyFlags
+ {
+ None = 0,
+ DepthBias = 1 << 0,
+ Scissor = 1 << 1,
+ Stencil = 1 << 2,
+ Viewport = 1 << 3,
+ All = DepthBias | Scissor | Stencil | Viewport
+ }
+
+ private DirtyFlags _dirty;
+
+ public void SetDepthBias(float slopeFactor, float constantFactor, float clamp)
+ {
+ _depthBiasSlopeFactor = slopeFactor;
+ _depthBiasConstantFactor = constantFactor;
+ _depthBiasClamp = clamp;
+
+ _dirty |= DirtyFlags.DepthBias;
+ }
+
+ public void SetScissor(int index, Rect2D scissor)
+ {
+ _scissors[index] = scissor;
+
+ _dirty |= DirtyFlags.Scissor;
+ }
+
+ public void SetStencilMasks(
+ uint backCompareMask,
+ uint backWriteMask,
+ uint backReference,
+ uint frontCompareMask,
+ uint frontWriteMask,
+ uint frontReference)
+ {
+ _backCompareMask = backCompareMask;
+ _backWriteMask = backWriteMask;
+ _backReference = backReference;
+ _frontCompareMask = frontCompareMask;
+ _frontWriteMask = frontWriteMask;
+ _frontReference = frontReference;
+
+ _dirty |= DirtyFlags.Stencil;
+ }
+
+ public void SetViewport(int index, Viewport viewport)
+ {
+ Viewports[index] = viewport;
+
+ _dirty |= DirtyFlags.Viewport;
+ }
+
+ public void SetViewportsDirty()
+ {
+ _dirty |= DirtyFlags.Viewport;
+ }
+
+ public void ForceAllDirty()
+ {
+ _dirty = DirtyFlags.All;
+ }
+
+ public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
+ {
+ if (_dirty.HasFlag(DirtyFlags.DepthBias))
+ {
+ RecordDepthBias(api, commandBuffer);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Scissor))
+ {
+ RecordScissor(api, commandBuffer);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Stencil))
+ {
+ RecordStencilMasks(api, commandBuffer);
+ }
+
+ if (_dirty.HasFlag(DirtyFlags.Viewport))
+ {
+ RecordViewport(api, commandBuffer);
+ }
+
+ _dirty = DirtyFlags.None;
+ }
+
+ private void RecordDepthBias(Vk api, CommandBuffer commandBuffer)
+ {
+ api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);
+ }
+
+ private void RecordScissor(Vk api, CommandBuffer commandBuffer)
+ {
+ api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.ToSpan());
+ }
+
+ private void RecordStencilMasks(Vk api, CommandBuffer commandBuffer)
+ {
+ api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backCompareMask);
+ api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backWriteMask);
+ api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.StencilFaceBackBit, _backReference);
+ api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontCompareMask);
+ api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontWriteMask);
+ api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.StencilFaceFrontBit, _frontReference);
+ }
+
+ private void RecordViewport(Vk api, CommandBuffer commandBuffer)
+ {
+ api.CmdSetViewport(commandBuffer, 0, (uint)ViewportsCount, Viewports.ToSpan());
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/Ryujinx.Graphics.Vulkan/PipelineFull.cs
new file mode 100644
index 00000000..8bb04382
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineFull.cs
@@ -0,0 +1,288 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Vulkan.Queries;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class PipelineFull : PipelineBase, IPipeline
+ {
+ private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MB
+
+ private bool _hasPendingQuery;
+
+ private readonly List<QueryPool> _activeQueries;
+ private CounterQueueEvent _activeConditionalRender;
+
+ private readonly List<BufferedQuery> _pendingQueryCopies;
+ private readonly List<BufferedQuery> _pendingQueryResets;
+
+ private ulong _byteWeight;
+
+ public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device)
+ {
+ _activeQueries = new List<QueryPool>();
+ _pendingQueryCopies = new();
+ _pendingQueryResets = new List<BufferedQuery>();
+
+ CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
+ }
+
+ private void CopyPendingQuery()
+ {
+ foreach (var query in _pendingQueryCopies)
+ {
+ query.PoolCopy(Cbs);
+ }
+
+ lock (_pendingQueryResets)
+ {
+ foreach (var query in _pendingQueryResets)
+ {
+ query.PoolReset(CommandBuffer);
+ }
+
+ _pendingQueryResets.Clear();
+ }
+
+ _pendingQueryCopies.Clear();
+ }
+
+ public void ClearRenderTargetColor(int index, int layer, uint componentMask, ColorF color)
+ {
+ if (FramebufferParams == null)
+ {
+ return;
+ }
+
+ if (componentMask != 0xf)
+ {
+ // We can't use CmdClearAttachments if not writing all components,
+ // because on Vulkan, the pipeline state does not affect clears.
+ var dstTexture = FramebufferParams.GetAttachment(index);
+ if (dstTexture == null)
+ {
+ return;
+ }
+
+ Span<float> clearColor = stackalloc float[4];
+ clearColor[0] = color.Red;
+ clearColor[1] = color.Green;
+ clearColor[2] = color.Blue;
+ clearColor[3] = color.Alpha;
+
+ // TODO: Clear only the specified layer.
+ Gd.HelperShader.Clear(
+ Gd,
+ dstTexture,
+ clearColor,
+ componentMask,
+ (int)FramebufferParams.Width,
+ (int)FramebufferParams.Height,
+ FramebufferParams.AttachmentFormats[index],
+ ClearScissor);
+ }
+ else
+ {
+ ClearRenderTargetColor(index, layer, color);
+ }
+ }
+
+ public void EndHostConditionalRendering()
+ {
+ if (Gd.Capabilities.SupportsConditionalRendering)
+ {
+ // Gd.ConditionalRenderingApi.CmdEndConditionalRendering(CommandBuffer);
+ }
+ else
+ {
+ // throw new NotSupportedException();
+ }
+
+ _activeConditionalRender?.ReleaseHostAccess();
+ _activeConditionalRender = null;
+ }
+
+ public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual)
+ {
+ // Compare an event and a constant value.
+ if (value is CounterQueueEvent evt)
+ {
+ // Easy host conditional rendering when the check matches what GL can do:
+ // - Event is of type samples passed.
+ // - Result is not a combination of multiple queries.
+ // - Comparing against 0.
+ // - Event has not already been flushed.
+
+ if (compare == 0 && evt.Type == CounterType.SamplesPassed && evt.ClearCounter)
+ {
+ if (!value.ReserveForHostAccess())
+ {
+ // If the event has been flushed, then just use the values on the CPU.
+ // The query object may already be repurposed for another draw (eg. begin + end).
+ return false;
+ }
+
+ if (Gd.Capabilities.SupportsConditionalRendering)
+ {
+ var buffer = evt.GetBuffer().Get(Cbs, 0, sizeof(long)).Value;
+ var flags = isEqual ? ConditionalRenderingFlagsEXT.ConditionalRenderingInvertedBitExt : 0;
+
+ var conditionalRenderingBeginInfo = new ConditionalRenderingBeginInfoEXT()
+ {
+ SType = StructureType.ConditionalRenderingBeginInfoExt,
+ Buffer = buffer,
+ Flags = flags
+ };
+
+ // Gd.ConditionalRenderingApi.CmdBeginConditionalRendering(CommandBuffer, conditionalRenderingBeginInfo);
+ }
+
+ _activeConditionalRender = evt;
+ return true;
+ }
+ }
+
+ // The GPU will flush the queries to CPU and evaluate the condition there instead.
+
+ FlushPendingQuery(); // The thread will be stalled manually flushing the counter, so flush commands now.
+ return false;
+ }
+
+ public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual)
+ {
+ FlushPendingQuery(); // The thread will be stalled manually flushing the counter, so flush commands now.
+ return false;
+ }
+
+ private void FlushPendingQuery()
+ {
+ if (_hasPendingQuery)
+ {
+ _hasPendingQuery = false;
+ FlushCommandsImpl();
+ }
+ }
+
+ public CommandBufferScoped GetPreloadCommandBuffer()
+ {
+ if (PreloadCbs == null)
+ {
+ PreloadCbs = Gd.CommandBufferPool.Rent();
+ }
+
+ return PreloadCbs.Value;
+ }
+
+ public void FlushCommandsIfWeightExceeding(IAuto disposedResource, ulong byteWeight)
+ {
+ bool usedByCurrentCb = disposedResource.HasCommandBufferDependency(Cbs);
+
+ if (PreloadCbs != null && !usedByCurrentCb)
+ {
+ usedByCurrentCb = disposedResource.HasCommandBufferDependency(PreloadCbs.Value);
+ }
+
+ if (usedByCurrentCb)
+ {
+ // Since we can only free memory after the command buffer that uses a given resource was executed,
+ // keeping the command buffer might cause a high amount of memory to be in use.
+ // To prevent that, we force submit command buffers if the memory usage by resources
+ // in use by the current command buffer is above a given limit, and those resources were disposed.
+ _byteWeight += byteWeight;
+
+ if (_byteWeight >= MinByteWeightForFlush)
+ {
+ FlushCommandsImpl();
+ }
+ }
+ }
+
+ public void FlushCommandsImpl()
+ {
+ EndRenderPass();
+
+ foreach (var queryPool in _activeQueries)
+ {
+ Gd.Api.CmdEndQuery(CommandBuffer, queryPool, 0);
+ }
+
+ _byteWeight = 0;
+
+ if (PreloadCbs != null)
+ {
+ PreloadCbs.Value.Dispose();
+ PreloadCbs = null;
+ }
+
+ CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
+
+ // Restore per-command buffer state.
+
+ if (Pipeline != null)
+ {
+ Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value);
+ }
+
+ foreach (var queryPool in _activeQueries)
+ {
+ Gd.Api.CmdResetQueryPool(CommandBuffer, queryPool, 0, 1);
+ Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, 0);
+ }
+
+ SignalCommandBufferChange();
+ }
+
+ public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset)
+ {
+ if (needsReset)
+ {
+ EndRenderPass();
+
+ Gd.Api.CmdResetQueryPool(CommandBuffer, pool, 0, 1);
+
+ lock (_pendingQueryResets)
+ {
+ _pendingQueryResets.Remove(query); // Might be present on here.
+ }
+ }
+
+ Gd.Api.CmdBeginQuery(CommandBuffer, pool, 0, 0);
+
+ _activeQueries.Add(pool);
+ }
+
+ public void EndQuery(QueryPool pool)
+ {
+ Gd.Api.CmdEndQuery(CommandBuffer, pool, 0);
+
+ _activeQueries.Remove(pool);
+ }
+
+ public void ResetQuery(BufferedQuery query)
+ {
+ lock (_pendingQueryResets)
+ {
+ _pendingQueryResets.Add(query);
+ }
+ }
+
+ public void CopyQueryResults(BufferedQuery query)
+ {
+ _pendingQueryCopies.Add(query);
+
+ _hasPendingQuery = true;
+ }
+
+ protected override void SignalAttachmentChange()
+ {
+ FlushPendingQuery();
+ }
+
+ protected override void SignalRenderPassEnd()
+ {
+ CopyPendingQuery();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs b/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs
new file mode 100644
index 00000000..f874a962
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs
@@ -0,0 +1,44 @@
+using Silk.NET.Vulkan;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class PipelineHelperShader : PipelineBase
+ {
+ public PipelineHelperShader(VulkanRenderer gd, Device device) : base(gd, device)
+ {
+ }
+
+ public void SetRenderTarget(Auto<DisposableImageView> view, uint width, uint height, bool isDepthStencil, VkFormat format)
+ {
+ CreateFramebuffer(view, width, height, isDepthStencil, format);
+ CreateRenderPass();
+ SignalStateChange();
+ }
+
+ private void CreateFramebuffer(Auto<DisposableImageView> view, uint width, uint height, bool isDepthStencil, VkFormat format)
+ {
+ FramebufferParams = new FramebufferParams(Device, view, width, height, isDepthStencil, format);
+ UpdatePipelineAttachmentFormats();
+ }
+
+ public void SetCommandBuffer(CommandBufferScoped cbs)
+ {
+ CommandBuffer = (Cbs = cbs).CommandBuffer;
+
+ // Restore per-command buffer state.
+
+ if (Pipeline != null)
+ {
+ Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(CurrentCommandBuffer).Value);
+ }
+
+ SignalCommandBufferChange();
+ }
+
+ public void Finish()
+ {
+ EndRenderPass();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs b/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs
new file mode 100644
index 00000000..c834fa62
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs
@@ -0,0 +1,58 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class PipelineLayoutCache
+ {
+ private readonly PipelineLayoutCacheEntry[] _plce;
+ private readonly List<PipelineLayoutCacheEntry> _plceMinimal;
+
+ public PipelineLayoutCache()
+ {
+ _plce = new PipelineLayoutCacheEntry[1 << Constants.MaxShaderStages];
+ _plceMinimal = new List<PipelineLayoutCacheEntry>();
+ }
+
+ public PipelineLayoutCacheEntry Create(VulkanRenderer gd, Device device, ShaderSource[] shaders)
+ {
+ var plce = new PipelineLayoutCacheEntry(gd, device, shaders);
+ _plceMinimal.Add(plce);
+ return plce;
+ }
+
+ public PipelineLayoutCacheEntry GetOrCreate(VulkanRenderer gd, Device device, uint stages, bool usePd)
+ {
+ if (_plce[stages] == null)
+ {
+ _plce[stages] = new PipelineLayoutCacheEntry(gd, device, stages, usePd);
+ }
+
+ return _plce[stages];
+ }
+
+ protected virtual unsafe void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ for (int i = 0; i < _plce.Length; i++)
+ {
+ _plce[i]?.Dispose();
+ }
+
+ foreach (var plce in _plceMinimal)
+ {
+ plce.Dispose();
+ }
+
+ _plceMinimal.Clear();
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
new file mode 100644
index 00000000..2c966115
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
@@ -0,0 +1,112 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class PipelineLayoutCacheEntry
+ {
+ private readonly VulkanRenderer _gd;
+ private readonly Device _device;
+
+ public DescriptorSetLayout[] DescriptorSetLayouts { get; }
+ public PipelineLayout PipelineLayout { get; }
+
+ private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
+ private readonly int[] _dsCacheCursor;
+ private int _dsLastCbIndex;
+
+ private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device)
+ {
+ _gd = gd;
+ _device = device;
+
+ _dsCache = new List<Auto<DescriptorSetCollection>>[CommandBufferPool.MaxCommandBuffers][];
+
+ for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
+ {
+ _dsCache[i] = new List<Auto<DescriptorSetCollection>>[PipelineBase.DescriptorSetLayouts];
+
+ for (int j = 0; j < PipelineBase.DescriptorSetLayouts; j++)
+ {
+ _dsCache[i][j] = new List<Auto<DescriptorSetCollection>>();
+ }
+ }
+
+ _dsCacheCursor = new int[PipelineBase.DescriptorSetLayouts];
+ }
+
+ public PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, uint stages, bool usePd) : this(gd, device)
+ {
+ DescriptorSetLayouts = PipelineLayoutFactory.Create(gd, device, stages, usePd, out var pipelineLayout);
+ PipelineLayout = pipelineLayout;
+ }
+
+ public PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, ShaderSource[] shaders) : this(gd, device)
+ {
+ DescriptorSetLayouts = PipelineLayoutFactory.CreateMinimal(gd, device, shaders, out var pipelineLayout);
+ PipelineLayout = pipelineLayout;
+ }
+
+ public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
+ VulkanRenderer gd,
+ int commandBufferIndex,
+ int setIndex,
+ out bool isNew)
+ {
+ if (_dsLastCbIndex != commandBufferIndex)
+ {
+ _dsLastCbIndex = commandBufferIndex;
+
+ for (int i = 0; i < PipelineBase.DescriptorSetLayouts; i++)
+ {
+ _dsCacheCursor[i] = 0;
+ }
+ }
+
+ var list = _dsCache[commandBufferIndex][setIndex];
+ int index = _dsCacheCursor[setIndex]++;
+ if (index == list.Count)
+ {
+ var dsc = gd.DescriptorSetManager.AllocateDescriptorSet(gd.Api, DescriptorSetLayouts[setIndex]);
+ list.Add(dsc);
+ isNew = true;
+ return dsc;
+ }
+
+ isNew = false;
+ return list[index];
+ }
+
+ protected virtual unsafe void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ for (int i = 0; i < _dsCache.Length; i++)
+ {
+ for (int j = 0; j < _dsCache[i].Length; j++)
+ {
+ for (int k = 0; k < _dsCache[i][j].Count; k++)
+ {
+ _dsCache[i][j][k].Dispose();
+ }
+
+ _dsCache[i][j].Clear();
+ }
+ }
+
+ _gd.Api.DestroyPipelineLayout(_device, PipelineLayout, null);
+
+ for (int i = 0; i < DescriptorSetLayouts.Length; i++)
+ {
+ _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
new file mode 100644
index 00000000..541f3a25
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
@@ -0,0 +1,253 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class PipelineLayoutFactory
+ {
+ private const ShaderStageFlags SupportBufferStages =
+ ShaderStageFlags.ShaderStageVertexBit |
+ ShaderStageFlags.ShaderStageFragmentBit |
+ ShaderStageFlags.ShaderStageComputeBit;
+
+ public static unsafe DescriptorSetLayout[] Create(VulkanRenderer gd, Device device, uint stages, bool usePd, out PipelineLayout layout)
+ {
+ int stagesCount = BitOperations.PopCount(stages);
+
+ int uCount = Constants.MaxUniformBuffersPerStage * stagesCount + 1;
+ int tCount = Constants.MaxTexturesPerStage * 2 * stagesCount;
+ int iCount = Constants.MaxImagesPerStage * 2 * stagesCount;
+
+ DescriptorSetLayoutBinding* uLayoutBindings = stackalloc DescriptorSetLayoutBinding[uCount];
+ DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[stagesCount];
+ DescriptorSetLayoutBinding* tLayoutBindings = stackalloc DescriptorSetLayoutBinding[tCount];
+ DescriptorSetLayoutBinding* iLayoutBindings = stackalloc DescriptorSetLayoutBinding[iCount];
+
+ uLayoutBindings[0] = new DescriptorSetLayoutBinding
+ {
+ Binding = 0,
+ DescriptorType = DescriptorType.UniformBuffer,
+ DescriptorCount = 1,
+ StageFlags = SupportBufferStages
+ };
+
+ int iter = 0;
+
+ while (stages != 0)
+ {
+ int stage = BitOperations.TrailingZeroCount(stages);
+ stages &= ~(1u << stage);
+
+ var stageFlags = stage switch
+ {
+ 1 => ShaderStageFlags.ShaderStageFragmentBit,
+ 2 => ShaderStageFlags.ShaderStageGeometryBit,
+ 3 => ShaderStageFlags.ShaderStageTessellationControlBit,
+ 4 => ShaderStageFlags.ShaderStageTessellationEvaluationBit,
+ _ => ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.ShaderStageComputeBit
+ };
+
+ void Set(DescriptorSetLayoutBinding* bindings, int maxPerStage, DescriptorType type, int start, int skip)
+ {
+ int totalPerStage = maxPerStage * skip;
+
+ for (int i = 0; i < maxPerStage; i++)
+ {
+ bindings[start + iter * totalPerStage + i] = new DescriptorSetLayoutBinding
+ {
+ Binding = (uint)(start + stage * totalPerStage + i),
+ DescriptorType = type,
+ DescriptorCount = 1,
+ StageFlags = stageFlags
+ };
+ }
+ }
+
+ void SetStorage(DescriptorSetLayoutBinding* bindings, int maxPerStage, int start = 0)
+ {
+ bindings[start + iter] = new DescriptorSetLayoutBinding
+ {
+ Binding = (uint)(start + stage * maxPerStage),
+ DescriptorType = DescriptorType.StorageBuffer,
+ DescriptorCount = (uint)maxPerStage,
+ StageFlags = stageFlags
+ };
+ }
+
+ Set(uLayoutBindings, Constants.MaxUniformBuffersPerStage, DescriptorType.UniformBuffer, 1, 1);
+ SetStorage(sLayoutBindings, Constants.MaxStorageBuffersPerStage);
+ Set(tLayoutBindings, Constants.MaxTexturesPerStage, DescriptorType.CombinedImageSampler, 0, 2);
+ Set(tLayoutBindings, Constants.MaxTexturesPerStage, DescriptorType.UniformTexelBuffer, Constants.MaxTexturesPerStage, 2);
+ Set(iLayoutBindings, Constants.MaxImagesPerStage, DescriptorType.StorageImage, 0, 2);
+ Set(iLayoutBindings, Constants.MaxImagesPerStage, DescriptorType.StorageTexelBuffer, Constants.MaxImagesPerStage, 2);
+
+ iter++;
+ }
+
+ DescriptorSetLayout[] layouts = new DescriptorSetLayout[PipelineFull.DescriptorSetLayouts];
+
+ var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ PBindings = uLayoutBindings,
+ BindingCount = (uint)uCount,
+ Flags = usePd ? DescriptorSetLayoutCreateFlags.DescriptorSetLayoutCreatePushDescriptorBitKhr : 0
+ };
+
+ var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ PBindings = sLayoutBindings,
+ BindingCount = (uint)stagesCount
+ };
+
+ var tDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ PBindings = tLayoutBindings,
+ BindingCount = (uint)tCount
+ };
+
+ var iDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ PBindings = iLayoutBindings,
+ BindingCount = (uint)iCount
+ };
+
+ gd.Api.CreateDescriptorSetLayout(device, uDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.UniformSetIndex]).ThrowOnError();
+ gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.StorageSetIndex]).ThrowOnError();
+ gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.TextureSetIndex]).ThrowOnError();
+ gd.Api.CreateDescriptorSetLayout(device, iDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.ImageSetIndex]).ThrowOnError();
+
+ fixed (DescriptorSetLayout* pLayouts = layouts)
+ {
+ var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo()
+ {
+ SType = StructureType.PipelineLayoutCreateInfo,
+ PSetLayouts = pLayouts,
+ SetLayoutCount = PipelineFull.DescriptorSetLayouts
+ };
+
+ gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
+ }
+
+ return layouts;
+ }
+
+ public static unsafe DescriptorSetLayout[] CreateMinimal(VulkanRenderer gd, Device device, ShaderSource[] shaders, out PipelineLayout layout)
+ {
+ int stagesCount = shaders.Length;
+
+ int uCount = 0;
+ int tCount = 0;
+ int iCount = 0;
+
+ foreach (var shader in shaders)
+ {
+ uCount += shader.Bindings.UniformBufferBindings.Count;
+ tCount += shader.Bindings.TextureBindings.Count;
+ iCount += shader.Bindings.ImageBindings.Count;
+ }
+
+ DescriptorSetLayoutBinding* uLayoutBindings = stackalloc DescriptorSetLayoutBinding[uCount];
+ DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[stagesCount];
+ DescriptorSetLayoutBinding* tLayoutBindings = stackalloc DescriptorSetLayoutBinding[tCount];
+ DescriptorSetLayoutBinding* iLayoutBindings = stackalloc DescriptorSetLayoutBinding[iCount];
+
+ int uIndex = 0;
+ int sIndex = 0;
+ int tIndex = 0;
+ int iIndex = 0;
+
+ foreach (var shader in shaders)
+ {
+ var stageFlags = shader.Stage.Convert();
+
+ void Set(DescriptorSetLayoutBinding* bindings, DescriptorType type, ref int start, IEnumerable<int> bds)
+ {
+ foreach (var b in bds)
+ {
+ bindings[start++] = new DescriptorSetLayoutBinding
+ {
+ Binding = (uint)b,
+ DescriptorType = type,
+ DescriptorCount = 1,
+ StageFlags = stageFlags
+ };
+ }
+ }
+
+ void SetStorage(DescriptorSetLayoutBinding* bindings, ref int start, int count)
+ {
+ bindings[start++] = new DescriptorSetLayoutBinding
+ {
+ Binding = (uint)start,
+ DescriptorType = DescriptorType.StorageBuffer,
+ DescriptorCount = (uint)count,
+ StageFlags = stageFlags
+ };
+ }
+
+ // TODO: Support buffer textures and images here.
+ // This is only used for the helper shaders on the backend, and we don't use buffer textures on them
+ // so far, so it's not really necessary right now.
+ Set(uLayoutBindings, DescriptorType.UniformBuffer, ref uIndex, shader.Bindings.UniformBufferBindings);
+ SetStorage(sLayoutBindings, ref sIndex, shader.Bindings.StorageBufferBindings.Count);
+ Set(tLayoutBindings, DescriptorType.CombinedImageSampler, ref tIndex, shader.Bindings.TextureBindings);
+ Set(iLayoutBindings, DescriptorType.StorageImage, ref iIndex, shader.Bindings.ImageBindings);
+ }
+
+ DescriptorSetLayout[] layouts = new DescriptorSetLayout[PipelineFull.DescriptorSetLayouts];
+
+ var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ PBindings = uLayoutBindings,
+ BindingCount = (uint)uCount
+ };
+
+ var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ PBindings = sLayoutBindings,
+ BindingCount = (uint)stagesCount
+ };
+
+ var tDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ PBindings = tLayoutBindings,
+ BindingCount = (uint)tCount
+ };
+
+ var iDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
+ {
+ SType = StructureType.DescriptorSetLayoutCreateInfo,
+ PBindings = iLayoutBindings,
+ BindingCount = (uint)iCount
+ };
+
+ gd.Api.CreateDescriptorSetLayout(device, uDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.UniformSetIndex]).ThrowOnError();
+ gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.StorageSetIndex]).ThrowOnError();
+ gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.TextureSetIndex]).ThrowOnError();
+ gd.Api.CreateDescriptorSetLayout(device, iDescriptorSetLayoutCreateInfo, null, out layouts[PipelineFull.ImageSetIndex]).ThrowOnError();
+
+ fixed (DescriptorSetLayout* pLayouts = layouts)
+ {
+ var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo()
+ {
+ SType = StructureType.PipelineLayoutCreateInfo,
+ PSetLayouts = pLayouts,
+ SetLayoutCount = PipelineFull.DescriptorSetLayouts
+ };
+
+ gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
+ }
+
+ return layouts;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineState.cs b/Ryujinx.Graphics.Vulkan/PipelineState.cs
new file mode 100644
index 00000000..15c4d79e
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineState.cs
@@ -0,0 +1,579 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct PipelineState : IDisposable
+ {
+ private const int RequiredSubgroupSize = 32;
+
+ public PipelineUid Internal;
+
+ public float LineWidth
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 0) & 0xFFFFFFFF));
+ set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
+ }
+
+ public float DepthBiasClamp
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 32) & 0xFFFFFFFF));
+ set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
+ }
+
+ public float DepthBiasConstantFactor
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 0) & 0xFFFFFFFF));
+ set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
+ }
+
+ public float DepthBiasSlopeFactor
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 32) & 0xFFFFFFFF));
+ set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
+ }
+
+ public uint StencilFrontCompareMask
+ {
+ get => (uint)((Internal.Id2 >> 0) & 0xFFFFFFFF);
+ set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
+ }
+
+ public uint StencilFrontWriteMask
+ {
+ get => (uint)((Internal.Id2 >> 32) & 0xFFFFFFFF);
+ set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF) | ((ulong)value << 32);
+ }
+
+ public uint StencilFrontReference
+ {
+ get => (uint)((Internal.Id3 >> 0) & 0xFFFFFFFF);
+ set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
+ }
+
+ public uint StencilBackCompareMask
+ {
+ get => (uint)((Internal.Id3 >> 32) & 0xFFFFFFFF);
+ set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF) | ((ulong)value << 32);
+ }
+
+ public uint StencilBackWriteMask
+ {
+ get => (uint)((Internal.Id4 >> 0) & 0xFFFFFFFF);
+ set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
+ }
+
+ public uint StencilBackReference
+ {
+ get => (uint)((Internal.Id4 >> 32) & 0xFFFFFFFF);
+ set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32);
+ }
+
+ public float MinDepthBounds
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id5 >> 0) & 0xFFFFFFFF));
+ set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
+ }
+
+ public float MaxDepthBounds
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id5 >> 32) & 0xFFFFFFFF));
+ set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
+ }
+
+ public float BlendConstantR
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id6 >> 0) & 0xFFFFFFFF));
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
+ }
+
+ public float BlendConstantG
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id6 >> 32) & 0xFFFFFFFF));
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
+ }
+
+ public float BlendConstantB
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id7 >> 0) & 0xFFFFFFFF));
+ set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
+ }
+
+ public float BlendConstantA
+ {
+ get => BitConverter.Int32BitsToSingle((int)((Internal.Id7 >> 32) & 0xFFFFFFFF));
+ set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
+ }
+
+ public PolygonMode PolygonMode
+ {
+ get => (PolygonMode)((Internal.Id8 >> 0) & 0x3FFFFFFF);
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
+ }
+
+ public uint StagesCount
+ {
+ get => (byte)((Internal.Id8 >> 30) & 0xFF);
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
+ }
+
+ public uint VertexAttributeDescriptionsCount
+ {
+ get => (byte)((Internal.Id8 >> 38) & 0xFF);
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
+ }
+
+ public uint VertexBindingDescriptionsCount
+ {
+ get => (byte)((Internal.Id8 >> 46) & 0xFF);
+ set => Internal.Id8 = (Internal.Id8 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
+ }
+
+ public uint ViewportsCount
+ {
+ get => (byte)((Internal.Id8 >> 54) & 0xFF);
+ set => Internal.Id8 = (Internal.Id8 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
+ }
+
+ public uint ScissorsCount
+ {
+ get => (byte)((Internal.Id9 >> 0) & 0xFF);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
+ }
+
+ public uint ColorBlendAttachmentStateCount
+ {
+ get => (byte)((Internal.Id9 >> 8) & 0xFF);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
+ }
+
+ public PrimitiveTopology Topology
+ {
+ get => (PrimitiveTopology)((Internal.Id9 >> 16) & 0xF);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
+ }
+
+ public LogicOp LogicOp
+ {
+ get => (LogicOp)((Internal.Id9 >> 20) & 0xF);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
+ }
+
+ public CompareOp DepthCompareOp
+ {
+ get => (CompareOp)((Internal.Id9 >> 24) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
+ }
+
+ public StencilOp StencilFrontFailOp
+ {
+ get => (StencilOp)((Internal.Id9 >> 27) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
+ }
+
+ public StencilOp StencilFrontPassOp
+ {
+ get => (StencilOp)((Internal.Id9 >> 30) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
+ }
+
+ public StencilOp StencilFrontDepthFailOp
+ {
+ get => (StencilOp)((Internal.Id9 >> 33) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
+ }
+
+ public CompareOp StencilFrontCompareOp
+ {
+ get => (CompareOp)((Internal.Id9 >> 36) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
+ }
+
+ public StencilOp StencilBackFailOp
+ {
+ get => (StencilOp)((Internal.Id9 >> 39) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
+ }
+
+ public StencilOp StencilBackPassOp
+ {
+ get => (StencilOp)((Internal.Id9 >> 42) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
+ }
+
+ public StencilOp StencilBackDepthFailOp
+ {
+ get => (StencilOp)((Internal.Id9 >> 45) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
+ }
+
+ public CompareOp StencilBackCompareOp
+ {
+ get => (CompareOp)((Internal.Id9 >> 48) & 0x7);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
+ }
+
+ public CullModeFlags CullMode
+ {
+ get => (CullModeFlags)((Internal.Id9 >> 51) & 0x3);
+ set => Internal.Id9 = (Internal.Id9 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
+ }
+
+ public bool PrimitiveRestartEnable
+ {
+ get => ((Internal.Id9 >> 53) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
+ }
+
+ public bool DepthClampEnable
+ {
+ get => ((Internal.Id9 >> 54) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
+ }
+
+ public bool RasterizerDiscardEnable
+ {
+ get => ((Internal.Id9 >> 55) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
+ }
+
+ public FrontFace FrontFace
+ {
+ get => (FrontFace)((Internal.Id9 >> 56) & 0x1);
+ set => Internal.Id9 = (Internal.Id9 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
+ }
+
+ public bool DepthBiasEnable
+ {
+ get => ((Internal.Id9 >> 57) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
+ }
+
+ public bool DepthTestEnable
+ {
+ get => ((Internal.Id9 >> 58) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
+ }
+
+ public bool DepthWriteEnable
+ {
+ get => ((Internal.Id9 >> 59) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
+ }
+
+ public bool DepthBoundsTestEnable
+ {
+ get => ((Internal.Id9 >> 60) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
+ }
+
+ public bool StencilTestEnable
+ {
+ get => ((Internal.Id9 >> 61) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
+ }
+
+ public bool LogicOpEnable
+ {
+ get => ((Internal.Id9 >> 62) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
+ }
+
+ public bool HasDepthStencil
+ {
+ get => ((Internal.Id9 >> 63) & 0x1) != 0UL;
+ set => Internal.Id9 = (Internal.Id9 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
+ }
+
+ public uint PatchControlPoints
+ {
+ get => (uint)((Internal.Id10 >> 0) & 0xFFFFFFFF);
+ set => Internal.Id10 = (Internal.Id10 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
+ }
+
+ public uint SamplesCount
+ {
+ get => (uint)((Internal.Id10 >> 32) & 0xFFFFFFFF);
+ set => Internal.Id10 = (Internal.Id10 & 0xFFFFFFFF) | ((ulong)value << 32);
+ }
+
+ public bool AlphaToCoverageEnable
+ {
+ get => ((Internal.Id11 >> 0) & 0x1) != 0UL;
+ set => Internal.Id11 = (Internal.Id11 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
+ }
+
+ public bool AlphaToOneEnable
+ {
+ get => ((Internal.Id11 >> 1) & 0x1) != 0UL;
+ set => Internal.Id11 = (Internal.Id11 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
+ }
+
+ public NativeArray<PipelineShaderStageCreateInfo> Stages;
+ public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes;
+ public PipelineLayout PipelineLayout;
+
+ public void Initialize()
+ {
+ Stages = new NativeArray<PipelineShaderStageCreateInfo>(Constants.MaxShaderStages);
+ StageRequiredSubgroupSizes = new NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT>(Constants.MaxShaderStages);
+
+ for (int index = 0; index < Constants.MaxShaderStages; index++)
+ {
+ StageRequiredSubgroupSizes[index] = new PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT()
+ {
+ SType = StructureType.PipelineShaderStageRequiredSubgroupSizeCreateInfoExt,
+ RequiredSubgroupSize = RequiredSubgroupSize
+ };
+ }
+ }
+
+ public unsafe Auto<DisposablePipeline> CreateComputePipeline(
+ VulkanRenderer gd,
+ Device device,
+ ShaderCollection program,
+ PipelineCache cache)
+ {
+ if (program.TryGetComputePipeline(out var pipeline))
+ {
+ return pipeline;
+ }
+
+ if (gd.Capabilities.SupportsSubgroupSizeControl)
+ {
+ UpdateStageRequiredSubgroupSizes(gd, 1);
+ }
+
+ var pipelineCreateInfo = new ComputePipelineCreateInfo()
+ {
+ SType = StructureType.ComputePipelineCreateInfo,
+ Stage = Stages[0],
+ BasePipelineIndex = -1,
+ Layout = PipelineLayout
+ };
+
+ Pipeline pipelineHandle = default;
+
+ gd.Api.CreateComputePipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError();
+
+ pipeline = new Auto<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle));
+
+ program.AddComputePipeline(pipeline);
+
+ return pipeline;
+ }
+
+ public unsafe void DestroyComputePipeline(ShaderCollection program)
+ {
+ program.RemoveComputePipeline();
+ }
+
+ public unsafe Auto<DisposablePipeline> CreateGraphicsPipeline(
+ VulkanRenderer gd,
+ Device device,
+ ShaderCollection program,
+ PipelineCache cache,
+ RenderPass renderPass)
+ {
+ if (program.TryGetGraphicsPipeline(ref Internal, out var pipeline))
+ {
+ return pipeline;
+ }
+
+ Pipeline pipelineHandle = default;
+
+ fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0])
+ fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0])
+ fixed (Viewport* pViewports = &Internal.Viewports[0])
+ fixed (Rect2D* pScissors = &Internal.Scissors[0])
+ fixed (PipelineColorBlendAttachmentState* pColorBlendAttachmentState = &Internal.ColorBlendAttachmentState[0])
+ {
+ var vertexInputState = new PipelineVertexInputStateCreateInfo
+ {
+ SType = StructureType.PipelineVertexInputStateCreateInfo,
+ VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount,
+ PVertexAttributeDescriptions = pVertexAttributeDescriptions,
+ VertexBindingDescriptionCount = VertexBindingDescriptionsCount,
+ PVertexBindingDescriptions = pVertexBindingDescriptions
+ };
+
+ bool primitiveRestartEnable = PrimitiveRestartEnable;
+
+ primitiveRestartEnable &= Topology == PrimitiveTopology.LineStrip ||
+ Topology == PrimitiveTopology.TriangleStrip ||
+ Topology == PrimitiveTopology.TriangleFan ||
+ Topology == PrimitiveTopology.LineStripWithAdjacency ||
+ Topology == PrimitiveTopology.TriangleStripWithAdjacency;
+
+ var inputAssemblyState = new PipelineInputAssemblyStateCreateInfo()
+ {
+ SType = StructureType.PipelineInputAssemblyStateCreateInfo,
+ PrimitiveRestartEnable = primitiveRestartEnable,
+ Topology = Topology
+ };
+
+ var tessellationState = new PipelineTessellationStateCreateInfo()
+ {
+ SType = StructureType.PipelineTessellationStateCreateInfo,
+ PatchControlPoints = PatchControlPoints
+ };
+
+ var rasterizationState = new PipelineRasterizationStateCreateInfo()
+ {
+ SType = StructureType.PipelineRasterizationStateCreateInfo,
+ DepthClampEnable = DepthClampEnable,
+ RasterizerDiscardEnable = RasterizerDiscardEnable,
+ PolygonMode = PolygonMode,
+ LineWidth = LineWidth,
+ CullMode = CullMode,
+ FrontFace = FrontFace,
+ DepthBiasEnable = DepthBiasEnable,
+ DepthBiasClamp = DepthBiasClamp,
+ DepthBiasConstantFactor = DepthBiasConstantFactor,
+ DepthBiasSlopeFactor = DepthBiasSlopeFactor
+ };
+
+ var viewportState = new PipelineViewportStateCreateInfo()
+ {
+ SType = StructureType.PipelineViewportStateCreateInfo,
+ ViewportCount = ViewportsCount,
+ PViewports = pViewports,
+ ScissorCount = ScissorsCount,
+ PScissors = pScissors
+ };
+
+ var multisampleState = new PipelineMultisampleStateCreateInfo
+ {
+ SType = StructureType.PipelineMultisampleStateCreateInfo,
+ SampleShadingEnable = false,
+ RasterizationSamples = TextureStorage.ConvertToSampleCountFlags(SamplesCount),
+ MinSampleShading = 1,
+ AlphaToCoverageEnable = AlphaToCoverageEnable,
+ AlphaToOneEnable = AlphaToOneEnable
+ };
+
+ var stencilFront = new StencilOpState(
+ StencilFrontFailOp,
+ StencilFrontPassOp,
+ StencilFrontDepthFailOp,
+ StencilFrontCompareOp,
+ StencilFrontCompareMask,
+ StencilFrontWriteMask,
+ StencilFrontReference);
+
+ var stencilBack = new StencilOpState(
+ StencilBackFailOp,
+ StencilBackPassOp,
+ StencilBackDepthFailOp,
+ StencilBackCompareOp,
+ StencilBackCompareMask,
+ StencilBackWriteMask,
+ StencilBackReference);
+
+ var depthStencilState = new PipelineDepthStencilStateCreateInfo()
+ {
+ SType = StructureType.PipelineDepthStencilStateCreateInfo,
+ DepthTestEnable = DepthTestEnable,
+ DepthWriteEnable = DepthWriteEnable,
+ DepthCompareOp = DepthCompareOp,
+ DepthBoundsTestEnable = DepthBoundsTestEnable,
+ StencilTestEnable = StencilTestEnable,
+ Front = stencilFront,
+ Back = stencilBack,
+ MinDepthBounds = MinDepthBounds,
+ MaxDepthBounds = MaxDepthBounds
+ };
+
+ var colorBlendState = new PipelineColorBlendStateCreateInfo()
+ {
+ SType = StructureType.PipelineColorBlendStateCreateInfo,
+ LogicOpEnable = LogicOpEnable,
+ LogicOp = LogicOp,
+ AttachmentCount = ColorBlendAttachmentStateCount,
+ PAttachments = pColorBlendAttachmentState
+ };
+
+ colorBlendState.BlendConstants[0] = BlendConstantR;
+ colorBlendState.BlendConstants[1] = BlendConstantG;
+ colorBlendState.BlendConstants[2] = BlendConstantB;
+ colorBlendState.BlendConstants[3] = BlendConstantA;
+
+ bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
+ int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
+
+ DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
+
+ dynamicStates[0] = DynamicState.Viewport;
+ dynamicStates[1] = DynamicState.Scissor;
+ dynamicStates[2] = DynamicState.DepthBias;
+ dynamicStates[3] = DynamicState.DepthBounds;
+ dynamicStates[4] = DynamicState.StencilCompareMask;
+ dynamicStates[5] = DynamicState.StencilWriteMask;
+ dynamicStates[6] = DynamicState.StencilReference;
+
+ if (supportsExtDynamicState)
+ {
+ dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
+ }
+
+ var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo()
+ {
+ SType = StructureType.PipelineDynamicStateCreateInfo,
+ DynamicStateCount = (uint)dynamicStatesCount,
+ PDynamicStates = dynamicStates
+ };
+
+ if (gd.Capabilities.SupportsSubgroupSizeControl)
+ {
+ UpdateStageRequiredSubgroupSizes(gd, (int)StagesCount);
+ }
+
+ var pipelineCreateInfo = new GraphicsPipelineCreateInfo()
+ {
+ SType = StructureType.GraphicsPipelineCreateInfo,
+ StageCount = StagesCount,
+ PStages = Stages.Pointer,
+ PVertexInputState = &vertexInputState,
+ PInputAssemblyState = &inputAssemblyState,
+ PTessellationState = &tessellationState,
+ PViewportState = &viewportState,
+ PRasterizationState = &rasterizationState,
+ PMultisampleState = &multisampleState,
+ PDepthStencilState = &depthStencilState,
+ PColorBlendState = &colorBlendState,
+ PDynamicState = &pipelineDynamicStateCreateInfo,
+ Layout = PipelineLayout,
+ RenderPass = renderPass,
+ BasePipelineIndex = -1
+ };
+
+ gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError();
+ }
+
+ pipeline = new Auto<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle));
+
+ program.AddGraphicsPipeline(ref Internal, pipeline);
+
+ return pipeline;
+ }
+
+ private unsafe void UpdateStageRequiredSubgroupSizes(VulkanRenderer gd, int count)
+ {
+ for (int index = 0; index < count; index++)
+ {
+ bool canUseExplicitSubgroupSize =
+ (gd.Capabilities.RequiredSubgroupSizeStages & Stages[index].Stage) != 0 &&
+ gd.Capabilities.MinSubgroupSize <= RequiredSubgroupSize &&
+ gd.Capabilities.MaxSubgroupSize >= RequiredSubgroupSize;
+
+ Stages[index].PNext = canUseExplicitSubgroupSize ? StageRequiredSubgroupSizes.Pointer + index : null;
+ }
+ }
+
+ public void Dispose()
+ {
+ Stages.Dispose();
+ StageRequiredSubgroupSizes.Dispose();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineUid.cs b/Ryujinx.Graphics.Vulkan/PipelineUid.cs
new file mode 100644
index 00000000..e8137559
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/PipelineUid.cs
@@ -0,0 +1,133 @@
+using Ryujinx.Common.Memory;
+using Silk.NET.Vulkan;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ struct PipelineUid : IRefEquatable<PipelineUid>
+ {
+ public ulong Id0;
+ public ulong Id1;
+ public ulong Id2;
+ public ulong Id3;
+
+ public ulong Id4;
+ public ulong Id5;
+ public ulong Id6;
+ public ulong Id7;
+
+ public ulong Id8;
+ public ulong Id9;
+ public ulong Id10;
+ public ulong Id11;
+
+ private uint VertexAttributeDescriptionsCount => (byte)((Id8 >> 38) & 0xFF);
+ private uint VertexBindingDescriptionsCount => (byte)((Id8 >> 46) & 0xFF);
+ private uint ViewportsCount => (byte)((Id8 >> 54) & 0xFF);
+ private uint ScissorsCount => (byte)((Id9 >> 0) & 0xFF);
+ private uint ColorBlendAttachmentStateCount => (byte)((Id9 >> 8) & 0xFF);
+ private bool HasDepthStencil => ((Id9 >> 63) & 0x1) != 0UL;
+
+ public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions;
+ public Array33<VertexInputBindingDescription> VertexBindingDescriptions;
+ public Array16<Viewport> Viewports;
+ public Array16<Rect2D> Scissors;
+ public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
+ public Array9<Format> AttachmentFormats;
+
+ public override bool Equals(object obj)
+ {
+ return obj is PipelineUid other && Equals(other);
+ }
+
+ public bool Equals(ref PipelineUid other)
+ {
+ if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)) ||
+ !Unsafe.As<ulong, Vector256<byte>>(ref Id4).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id4)) ||
+ !Unsafe.As<ulong, Vector256<byte>>(ref Id8).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id8)))
+ {
+ return false;
+ }
+
+ if (!SequenceEqual<VertexInputAttributeDescription>(VertexAttributeDescriptions.ToSpan(), other.VertexAttributeDescriptions.ToSpan(), VertexAttributeDescriptionsCount))
+ {
+ return false;
+ }
+
+ if (!SequenceEqual<VertexInputBindingDescription>(VertexBindingDescriptions.ToSpan(), other.VertexBindingDescriptions.ToSpan(), VertexBindingDescriptionsCount))
+ {
+ return false;
+ }
+
+ if (!SequenceEqual<PipelineColorBlendAttachmentState>(ColorBlendAttachmentState.ToSpan(), other.ColorBlendAttachmentState.ToSpan(), ColorBlendAttachmentStateCount))
+ {
+ return false;
+ }
+
+ if (!SequenceEqual<Format>(AttachmentFormats.ToSpan(), other.AttachmentFormats.ToSpan(), ColorBlendAttachmentStateCount + (HasDepthStencil ? 1u : 0u)))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool SequenceEqual<T>(ReadOnlySpan<T> x, ReadOnlySpan<T> y, uint count) where T : unmanaged
+ {
+ return MemoryMarshal.Cast<T, byte>(x.Slice(0, (int)count)).SequenceEqual(MemoryMarshal.Cast<T, byte>(y.Slice(0, (int)count)));
+ }
+
+ public override int GetHashCode()
+ {
+ ulong hash64 = Id0 * 23 ^
+ Id1 * 23 ^
+ Id2 * 23 ^
+ Id3 * 23 ^
+ Id4 * 23 ^
+ Id5 * 23 ^
+ Id6 * 23 ^
+ Id7 * 23 ^
+ Id8 * 23 ^
+ Id9 * 23 ^
+ Id10 * 23 ^
+ Id11 * 23;
+
+ for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
+ {
+ hash64 ^= VertexAttributeDescriptions[i].Binding * 23;
+ hash64 ^= (uint)VertexAttributeDescriptions[i].Format * 23;
+ hash64 ^= VertexAttributeDescriptions[i].Location * 23;
+ hash64 ^= VertexAttributeDescriptions[i].Offset * 23;
+ }
+
+ for (int i = 0; i < (int)VertexBindingDescriptionsCount; i++)
+ {
+ hash64 ^= VertexBindingDescriptions[i].Binding * 23;
+ hash64 ^= (uint)VertexBindingDescriptions[i].InputRate * 23;
+ hash64 ^= VertexBindingDescriptions[i].Stride * 23;
+ }
+
+ for (int i = 0; i < (int)ColorBlendAttachmentStateCount; i++)
+ {
+ hash64 ^= ColorBlendAttachmentState[i].BlendEnable * 23;
+ hash64 ^= (uint)ColorBlendAttachmentState[i].SrcColorBlendFactor * 23;
+ hash64 ^= (uint)ColorBlendAttachmentState[i].DstColorBlendFactor * 23;
+ hash64 ^= (uint)ColorBlendAttachmentState[i].ColorBlendOp * 23;
+ hash64 ^= (uint)ColorBlendAttachmentState[i].SrcAlphaBlendFactor * 23;
+ hash64 ^= (uint)ColorBlendAttachmentState[i].DstAlphaBlendFactor * 23;
+ hash64 ^= (uint)ColorBlendAttachmentState[i].AlphaBlendOp * 23;
+ hash64 ^= (uint)ColorBlendAttachmentState[i].ColorWriteMask * 23;
+ }
+
+ for (int i = 0; i < (int)ColorBlendAttachmentStateCount; i++)
+ {
+ hash64 ^= (uint)AttachmentFormats[i] * 23;
+ }
+
+ return (int)hash64 ^ ((int)(hash64 >> 32) * 17);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs b/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs
new file mode 100644
index 00000000..a53e02a1
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs
@@ -0,0 +1,206 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Vulkan.Queries
+{
+ class BufferedQuery : IDisposable
+ {
+ private const int MaxQueryRetries = 5000;
+ private const long DefaultValue = -1;
+ private const long DefaultValueInt = 0xFFFFFFFF;
+
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly PipelineFull _pipeline;
+
+ private QueryPool _queryPool;
+ private bool _isReset;
+
+ private readonly BufferHolder _buffer;
+ private readonly IntPtr _bufferMap;
+ private readonly CounterType _type;
+ private bool _result32Bit;
+ private bool _isSupported;
+
+ private long _defaultValue;
+
+ public unsafe BufferedQuery(VulkanRenderer gd, Device device, PipelineFull pipeline, CounterType type, bool result32Bit)
+ {
+ _api = gd.Api;
+ _device = device;
+ _pipeline = pipeline;
+ _type = type;
+ _result32Bit = result32Bit;
+
+ _isSupported = QueryTypeSupported(gd, type);
+
+ if (_isSupported)
+ {
+ QueryPipelineStatisticFlags flags = type == CounterType.PrimitivesGenerated ?
+ QueryPipelineStatisticFlags.QueryPipelineStatisticGeometryShaderPrimitivesBit : 0;
+
+ var queryPoolCreateInfo = new QueryPoolCreateInfo()
+ {
+ SType = StructureType.QueryPoolCreateInfo,
+ QueryCount = 1,
+ QueryType = GetQueryType(type),
+ PipelineStatistics = flags
+ };
+
+ gd.Api.CreateQueryPool(device, queryPoolCreateInfo, null, out _queryPool).ThrowOnError();
+ }
+
+ var buffer = gd.BufferManager.Create(gd, sizeof(long), forConditionalRendering: true);
+
+ _bufferMap = buffer.Map(0, sizeof(long));
+ _defaultValue = result32Bit ? DefaultValueInt : DefaultValue;
+ Marshal.WriteInt64(_bufferMap, _defaultValue);
+ _buffer = buffer;
+ }
+
+ private bool QueryTypeSupported(VulkanRenderer gd, CounterType type)
+ {
+ return type switch
+ {
+ CounterType.SamplesPassed => true,
+ CounterType.TransformFeedbackPrimitivesWritten => gd.Capabilities.SupportsTransformFeedbackQueries,
+ CounterType.PrimitivesGenerated => gd.Capabilities.SupportsGeometryShader,
+ _ => false
+ };
+ }
+
+ private static QueryType GetQueryType(CounterType type)
+ {
+ return type switch
+ {
+ CounterType.SamplesPassed => QueryType.Occlusion,
+ CounterType.PrimitivesGenerated => QueryType.PipelineStatistics,
+ CounterType.TransformFeedbackPrimitivesWritten => QueryType.TransformFeedbackStreamExt,
+ _ => QueryType.Occlusion
+ };
+ }
+
+ public Auto<DisposableBuffer> GetBuffer()
+ {
+ return _buffer.GetBuffer();
+ }
+
+ public void Reset()
+ {
+ End(false);
+ Begin();
+ }
+
+ public void Begin()
+ {
+ if (_isSupported)
+ {
+ _pipeline.BeginQuery(this, _queryPool, !_isReset);
+ }
+ _isReset = false;
+ }
+
+ public unsafe void End(bool withResult)
+ {
+ if (_isSupported)
+ {
+ _pipeline.EndQuery(_queryPool);
+ }
+
+ if (withResult && _isSupported)
+ {
+ Marshal.WriteInt64(_bufferMap, _defaultValue);
+ _pipeline.CopyQueryResults(this);
+ }
+ else
+ {
+ // Dummy result, just return 0.
+ Marshal.WriteInt64(_bufferMap, 0);
+ }
+ }
+
+ public bool TryGetResult(out long result)
+ {
+ result = Marshal.ReadInt64(_bufferMap);
+
+ return result != _defaultValue;
+ }
+
+ public long AwaitResult(AutoResetEvent wakeSignal = null)
+ {
+ long data = _defaultValue;
+
+ if (wakeSignal == null)
+ {
+ while (data == _defaultValue)
+ {
+ data = Marshal.ReadInt64(_bufferMap);
+ }
+ }
+ else
+ {
+ int iterations = 0;
+ while (data == _defaultValue && iterations++ < MaxQueryRetries)
+ {
+ data = Marshal.ReadInt64(_bufferMap);
+ if (data == _defaultValue)
+ {
+ wakeSignal.WaitOne(1);
+ }
+ }
+
+ if (iterations >= MaxQueryRetries)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Error: Query result {_type} timed out. Took more than {MaxQueryRetries} tries.");
+ }
+ }
+
+ return data;
+ }
+
+ public void PoolReset(CommandBuffer cmd)
+ {
+ if (_isSupported)
+ {
+ _api.CmdResetQueryPool(cmd, _queryPool, 0, 1);
+ }
+ _isReset = true;
+ }
+
+ public void PoolCopy(CommandBufferScoped cbs)
+ {
+ var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long)).Value;
+
+ QueryResultFlags flags = QueryResultFlags.QueryResultWaitBit;
+
+ if (!_result32Bit)
+ {
+ flags |= QueryResultFlags.QueryResult64Bit;
+ }
+
+ _api.CmdCopyQueryPoolResults(
+ cbs.CommandBuffer,
+ _queryPool,
+ 0,
+ 1,
+ buffer,
+ 0,
+ (ulong)(_result32Bit ? sizeof(int) : sizeof(long)),
+ flags);
+ }
+
+ public unsafe void Dispose()
+ {
+ _buffer.Dispose();
+ if (_isSupported)
+ {
+ _api.DestroyQueryPool(_device, _queryPool, null);
+ }
+ _queryPool = default;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs b/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs
new file mode 100644
index 00000000..7ee3c15a
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs
@@ -0,0 +1,224 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Vulkan.Queries
+{
+ class CounterQueue : IDisposable
+ {
+ private const int QueryPoolInitialSize = 100;
+
+ private readonly VulkanRenderer _gd;
+ private readonly Device _device;
+ private readonly PipelineFull _pipeline;
+
+ public CounterType Type { get; }
+ public bool Disposed { get; private set; }
+
+ private Queue<CounterQueueEvent> _events = new Queue<CounterQueueEvent>();
+ private CounterQueueEvent _current;
+
+ private ulong _accumulatedCounter;
+ private int _waiterCount;
+
+ private object _lock = new object();
+
+ private Queue<BufferedQuery> _queryPool;
+ private AutoResetEvent _queuedEvent = new AutoResetEvent(false);
+ private AutoResetEvent _wakeSignal = new AutoResetEvent(false);
+ private AutoResetEvent _eventConsumed = new AutoResetEvent(false);
+
+ private Thread _consumerThread;
+
+ internal CounterQueue(VulkanRenderer gd, Device device, PipelineFull pipeline, CounterType type)
+ {
+ _gd = gd;
+ _device = device;
+ _pipeline = pipeline;
+
+ Type = type;
+
+ _queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize);
+ for (int i = 0; i < QueryPoolInitialSize; i++)
+ {
+ // AMD Polaris GPUs on Windows seem to have issues reporting 64-bit query results.
+ _queryPool.Enqueue(new BufferedQuery(_gd, _device, _pipeline, type, gd.IsAmdWindows));
+ }
+
+ _current = new CounterQueueEvent(this, type, 0);
+
+ _consumerThread = new Thread(EventConsumer);
+ _consumerThread.Start();
+ }
+
+ private void EventConsumer()
+ {
+ while (!Disposed)
+ {
+ CounterQueueEvent evt = null;
+ lock (_lock)
+ {
+ if (_events.Count > 0)
+ {
+ evt = _events.Dequeue();
+ }
+ }
+
+ if (evt == null)
+ {
+ _queuedEvent.WaitOne(); // No more events to go through, wait for more.
+ }
+ else
+ {
+ // Spin-wait rather than sleeping if there are any waiters, by passing null instead of the wake signal.
+ evt.TryConsume(ref _accumulatedCounter, true, _waiterCount == 0 ? _wakeSignal : null);
+ }
+
+ if (_waiterCount > 0)
+ {
+ _eventConsumed.Set();
+ }
+ }
+ }
+
+ internal BufferedQuery GetQueryObject()
+ {
+ // Creating/disposing query objects on a context we're sharing with will cause issues.
+ // So instead, make a lot of query objects on the main thread and reuse them.
+
+ lock (_lock)
+ {
+ if (_queryPool.Count > 0)
+ {
+ BufferedQuery result = _queryPool.Dequeue();
+ return result;
+ }
+ else
+ {
+ return new BufferedQuery(_gd, _device, _pipeline, Type, _gd.IsAmdWindows);
+ }
+ }
+ }
+
+ internal void ReturnQueryObject(BufferedQuery query)
+ {
+ lock (_lock)
+ {
+ _pipeline.ResetQuery(query);
+ _queryPool.Enqueue(query);
+ }
+ }
+
+ public CounterQueueEvent QueueReport(EventHandler<ulong> resultHandler, ulong lastDrawIndex, bool hostReserved)
+ {
+ CounterQueueEvent result;
+ ulong draws = lastDrawIndex - _current.DrawIndex;
+
+ lock (_lock)
+ {
+ // A query's result only matters if more than one draw was performed during it.
+ // Otherwise, dummy it out and return 0 immediately.
+
+ if (hostReserved)
+ {
+ // This counter event is guaranteed to be available for host conditional rendering.
+ _current.ReserveForHostAccess();
+ }
+
+ _current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten);
+ _events.Enqueue(_current);
+
+ _current.OnResult += resultHandler;
+
+ result = _current;
+
+ _current = new CounterQueueEvent(this, Type, lastDrawIndex);
+ }
+
+ _queuedEvent.Set();
+
+ return result;
+ }
+
+ public void QueueReset(ulong lastDrawIndex)
+ {
+ ulong draws = lastDrawIndex - _current.DrawIndex;
+
+ lock (_lock)
+ {
+ _current.Clear(draws != 0);
+ }
+ }
+
+ public void Flush(bool blocking)
+ {
+ if (!blocking)
+ {
+ // Just wake the consumer thread - it will update the queries.
+ _wakeSignal.Set();
+ return;
+ }
+
+ lock (_lock)
+ {
+ // Tell the queue to process all events.
+ while (_events.Count > 0)
+ {
+ CounterQueueEvent flush = _events.Peek();
+ if (!flush.TryConsume(ref _accumulatedCounter, true))
+ {
+ return; // If not blocking, then return when we encounter an event that is not ready yet.
+ }
+ _events.Dequeue();
+ }
+ }
+ }
+
+ public void FlushTo(CounterQueueEvent evt)
+ {
+ // Flush the counter queue on the main thread.
+ Interlocked.Increment(ref _waiterCount);
+
+ _wakeSignal.Set();
+
+ while (!evt.Disposed)
+ {
+ _eventConsumed.WaitOne(1);
+ }
+
+ Interlocked.Decrement(ref _waiterCount);
+ }
+
+ public void Dispose()
+ {
+ lock (_lock)
+ {
+ while (_events.Count > 0)
+ {
+ CounterQueueEvent evt = _events.Dequeue();
+
+ evt.Dispose();
+ }
+
+ Disposed = true;
+ }
+
+ _queuedEvent.Set();
+
+ _consumerThread.Join();
+
+ _current?.Dispose();
+
+ foreach (BufferedQuery query in _queryPool)
+ {
+ query.Dispose();
+ }
+
+ _queuedEvent.Dispose();
+ _wakeSignal.Dispose();
+ _eventConsumed.Dispose();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs b/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs
new file mode 100644
index 00000000..241fe1ee
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs
@@ -0,0 +1,167 @@
+using Ryujinx.Graphics.GAL;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Vulkan.Queries
+{
+ class CounterQueueEvent : ICounterEvent
+ {
+ public event EventHandler<ulong> OnResult;
+
+ public CounterType Type { get; }
+ public bool ClearCounter { get; private set; }
+
+ public bool Disposed { get; private set; }
+ public bool Invalid { get; set; }
+
+ public ulong DrawIndex { get; }
+
+ private CounterQueue _queue;
+ private BufferedQuery _counter;
+
+ private bool _hostAccessReserved = false;
+ private int _refCount = 1; // Starts with a reference from the counter queue.
+
+ private object _lock = new object();
+ private ulong _result = ulong.MaxValue;
+
+ public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex)
+ {
+ _queue = queue;
+
+ _counter = queue.GetQueryObject();
+ Type = type;
+
+ DrawIndex = drawIndex;
+
+ _counter.Begin();
+ }
+
+ public Auto<DisposableBuffer> GetBuffer()
+ {
+ return _counter.GetBuffer();
+ }
+
+ internal void Clear(bool counterReset)
+ {
+ if (counterReset)
+ {
+ _counter.Reset();
+ }
+
+ ClearCounter = true;
+ }
+
+ internal void Complete(bool withResult)
+ {
+ _counter.End(withResult);
+ }
+
+ internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
+ {
+ lock (_lock)
+ {
+ if (Disposed)
+ {
+ return true;
+ }
+
+ if (ClearCounter)
+ {
+ result = 0;
+ }
+
+ long queryResult;
+
+ if (block)
+ {
+ queryResult = _counter.AwaitResult(wakeSignal);
+ }
+ else
+ {
+ if (!_counter.TryGetResult(out queryResult))
+ {
+ return false;
+ }
+ }
+
+ result += (ulong)queryResult;
+
+ _result = result;
+
+ OnResult?.Invoke(this, result);
+
+ Dispose(); // Return the our resources to the pool.
+
+ return true;
+ }
+ }
+
+ public void Flush()
+ {
+ if (Disposed)
+ {
+ return;
+ }
+
+ // Tell the queue to process all events up to this one.
+ _queue.FlushTo(this);
+ }
+
+ public void DecrementRefCount()
+ {
+ if (Interlocked.Decrement(ref _refCount) == 0)
+ {
+ DisposeInternal();
+ }
+ }
+
+ public bool ReserveForHostAccess()
+ {
+ if (_hostAccessReserved)
+ {
+ return true;
+ }
+
+ if (IsValueAvailable())
+ {
+ return false;
+ }
+
+ if (Interlocked.Increment(ref _refCount) == 1)
+ {
+ Interlocked.Decrement(ref _refCount);
+
+ return false;
+ }
+
+ _hostAccessReserved = true;
+
+ return true;
+ }
+
+ public void ReleaseHostAccess()
+ {
+ _hostAccessReserved = false;
+
+ DecrementRefCount();
+ }
+
+ private void DisposeInternal()
+ {
+ _queue.ReturnQueryObject(_counter);
+ }
+
+ private bool IsValueAvailable()
+ {
+ return _result != ulong.MaxValue || _counter.TryGetResult(out _);
+ }
+
+ public void Dispose()
+ {
+ Disposed = true;
+
+ DecrementRefCount();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Queries/Counters.cs b/Ryujinx.Graphics.Vulkan/Queries/Counters.cs
new file mode 100644
index 00000000..63581e42
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Queries/Counters.cs
@@ -0,0 +1,58 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan.Queries
+{
+ class Counters : IDisposable
+ {
+ private readonly CounterQueue[] _counterQueues;
+ private readonly PipelineFull _pipeline;
+
+ public Counters(VulkanRenderer gd, Device device, PipelineFull pipeline)
+ {
+ _pipeline = pipeline;
+
+ int count = Enum.GetNames(typeof(CounterType)).Length;
+
+ _counterQueues = new CounterQueue[count];
+
+ for (int index = 0; index < count; index++)
+ {
+ CounterType type = (CounterType)index;
+ _counterQueues[index] = new CounterQueue(gd, device, pipeline, type);
+ }
+ }
+
+ public CounterQueueEvent QueueReport(CounterType type, EventHandler<ulong> resultHandler, bool hostReserved)
+ {
+ return _counterQueues[(int)type].QueueReport(resultHandler, _pipeline.DrawCount, hostReserved);
+ }
+
+ public void QueueReset(CounterType type)
+ {
+ _counterQueues[(int)type].QueueReset(_pipeline.DrawCount);
+ }
+
+ public void Update()
+ {
+ foreach (var queue in _counterQueues)
+ {
+ queue.Flush(false);
+ }
+ }
+
+ public void Flush(CounterType type)
+ {
+ _counterQueues[(int)type].Flush(true);
+ }
+
+ public void Dispose()
+ {
+ foreach (var queue in _counterQueues)
+ {
+ queue.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
new file mode 100644
index 00000000..fe22f3f5
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.2" />
+ <PackageReference Include="shaderc.net" Version="0.1.0" />
+ <PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
+ <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
+ <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
+ <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
+ <PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
+ <PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
+ <ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Ryujinx.Graphics.Vulkan/SamplerHolder.cs b/Ryujinx.Graphics.Vulkan/SamplerHolder.cs
new file mode 100644
index 00000000..a8f7c944
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/SamplerHolder.cs
@@ -0,0 +1,117 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class SamplerHolder : ISampler
+ {
+ private readonly VulkanRenderer _gd;
+ private readonly Auto<DisposableSampler> _sampler;
+
+ public unsafe SamplerHolder(VulkanRenderer gd, Device device, GAL.SamplerCreateInfo info)
+ {
+ _gd = gd;
+
+ gd.Samplers.Add(this);
+
+ (Filter minFilter, SamplerMipmapMode mipFilter) = EnumConversion.Convert(info.MinFilter);
+
+ float minLod = info.MinLod;
+ float maxLod = info.MaxLod;
+
+ if (info.MinFilter == MinFilter.Nearest || info.MinFilter == MinFilter.Linear)
+ {
+ minLod = 0;
+ maxLod = 0.25f;
+ }
+
+ var borderColor = GetConstrainedBorderColor(info.BorderColor, out var cantConstrain);
+
+ var samplerCreateInfo = new Silk.NET.Vulkan.SamplerCreateInfo()
+ {
+ SType = StructureType.SamplerCreateInfo,
+ MagFilter = info.MagFilter.Convert(),
+ MinFilter = minFilter,
+ MipmapMode = mipFilter,
+ AddressModeU = info.AddressU.Convert(),
+ AddressModeV = info.AddressV.Convert(),
+ AddressModeW = info.AddressP.Convert(),
+ MipLodBias = info.MipLodBias,
+ AnisotropyEnable = info.MaxAnisotropy != 1f,
+ MaxAnisotropy = info.MaxAnisotropy,
+ CompareEnable = info.CompareMode == CompareMode.CompareRToTexture,
+ CompareOp = info.CompareOp.Convert(),
+ MinLod = minLod,
+ MaxLod = maxLod,
+ BorderColor = borderColor,
+ UnnormalizedCoordinates = false // TODO: Use unnormalized coordinates.
+ };
+
+ SamplerCustomBorderColorCreateInfoEXT customBorderColor;
+
+ if (cantConstrain && gd.Capabilities.SupportsCustomBorderColor)
+ {
+ var color = new ClearColorValue(
+ info.BorderColor.Red,
+ info.BorderColor.Green,
+ info.BorderColor.Blue,
+ info.BorderColor.Alpha);
+
+ customBorderColor = new SamplerCustomBorderColorCreateInfoEXT()
+ {
+ SType = StructureType.SamplerCustomBorderColorCreateInfoExt,
+ CustomBorderColor = color
+ };
+
+ samplerCreateInfo.PNext = &customBorderColor;
+ }
+
+ gd.Api.CreateSampler(device, samplerCreateInfo, null, out var sampler).ThrowOnError();
+
+ _sampler = new Auto<DisposableSampler>(new DisposableSampler(gd.Api, device, sampler));
+ }
+
+ private static BorderColor GetConstrainedBorderColor(ColorF arbitraryBorderColor, out bool cantConstrain)
+ {
+ float r = arbitraryBorderColor.Red;
+ float g = arbitraryBorderColor.Green;
+ float b = arbitraryBorderColor.Blue;
+ float a = arbitraryBorderColor.Alpha;
+
+ if (r == 0f && g == 0f && b == 0f)
+ {
+ if (a == 1f)
+ {
+ cantConstrain = false;
+ return BorderColor.FloatOpaqueBlack;
+ }
+ else if (a == 0f)
+ {
+ cantConstrain = false;
+ return BorderColor.FloatTransparentBlack;
+ }
+ }
+ else if (r == 1f && g == 1f && b == 1f && a == 1f)
+ {
+ cantConstrain = false;
+ return BorderColor.FloatOpaqueWhite;
+ }
+
+ cantConstrain = true;
+ return BorderColor.FloatOpaqueBlack;
+ }
+
+ public Auto<DisposableSampler> GetSampler()
+ {
+ return _sampler;
+ }
+
+ public void Dispose()
+ {
+ if (_gd.Samplers.Remove(this))
+ {
+ _sampler.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs b/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs
new file mode 100644
index 00000000..aa1b0eaf
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs
@@ -0,0 +1,60 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Threading;
+using VkSemaphore = Silk.NET.Vulkan.Semaphore;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class SemaphoreHolder : IDisposable
+ {
+ private readonly Vk _api;
+ private readonly Device _device;
+ private VkSemaphore _semaphore;
+ private int _referenceCount;
+ public bool _disposed;
+
+ public unsafe SemaphoreHolder(Vk api, Device device)
+ {
+ _api = api;
+ _device = device;
+
+ var semaphoreCreateInfo = new SemaphoreCreateInfo()
+ {
+ SType = StructureType.SemaphoreCreateInfo
+ };
+
+ api.CreateSemaphore(device, in semaphoreCreateInfo, null, out _semaphore).ThrowOnError();
+
+ _referenceCount = 1;
+ }
+
+ public VkSemaphore GetUnsafe()
+ {
+ return _semaphore;
+ }
+
+ public VkSemaphore Get()
+ {
+ Interlocked.Increment(ref _referenceCount);
+ return _semaphore;
+ }
+
+ public unsafe void Put()
+ {
+ if (Interlocked.Decrement(ref _referenceCount) == 0)
+ {
+ _api.DestroySemaphore(_device, _semaphore, null);
+ _semaphore = default;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ Put();
+ _disposed = true;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Shader.cs b/Ryujinx.Graphics.Vulkan/Shader.cs
new file mode 100644
index 00000000..2ced4bea
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Shader.cs
@@ -0,0 +1,167 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using shaderc;
+using Silk.NET.Vulkan;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class Shader
+ {
+ // The shaderc.net dependency's Options constructor and dispose are not thread safe.
+ // Take this lock when using them.
+ private static object _shaderOptionsLock = new object();
+
+ private readonly Vk _api;
+ private readonly Device _device;
+ private readonly ShaderStageFlags _stage;
+
+ private IntPtr _entryPointName;
+ private ShaderModule _module;
+
+ public ShaderStageFlags StageFlags => _stage;
+
+ public ShaderBindings Bindings { get; }
+
+ public ProgramLinkStatus CompileStatus { private set; get; }
+
+ public readonly Task CompileTask;
+
+ public unsafe Shader(Vk api, Device device, ShaderSource shaderSource)
+ {
+ _api = api;
+ _device = device;
+ Bindings = shaderSource.Bindings;
+
+ CompileStatus = ProgramLinkStatus.Incomplete;
+
+ _stage = shaderSource.Stage.Convert();
+ _entryPointName = Marshal.StringToHGlobalAnsi("main");
+
+ CompileTask = Task.Run(() =>
+ {
+ byte[] spirv = shaderSource.BinaryCode;
+
+ if (spirv == null)
+ {
+ spirv = GlslToSpirv(shaderSource.Code, shaderSource.Stage);
+
+ if (spirv == null)
+ {
+ CompileStatus = ProgramLinkStatus.Failure;
+
+ return;
+ }
+ }
+
+ fixed (byte* pCode = spirv)
+ {
+ var shaderModuleCreateInfo = new ShaderModuleCreateInfo()
+ {
+ SType = StructureType.ShaderModuleCreateInfo,
+ CodeSize = (uint)spirv.Length,
+ PCode = (uint*)pCode
+ };
+
+ api.CreateShaderModule(device, shaderModuleCreateInfo, null, out _module).ThrowOnError();
+ }
+
+ CompileStatus = ProgramLinkStatus.Success;
+ });
+ }
+
+ private unsafe static byte[] GlslToSpirv(string glsl, ShaderStage stage)
+ {
+ // TODO: We should generate the correct code on the shader translator instead of doing this compensation.
+ glsl = glsl.Replace("gl_VertexID", "(gl_VertexIndex - gl_BaseVertex)");
+ glsl = glsl.Replace("gl_InstanceID", "(gl_InstanceIndex - gl_BaseInstance)");
+
+ Options options;
+
+ lock (_shaderOptionsLock)
+ {
+ options = new Options(false)
+ {
+ SourceLanguage = SourceLanguage.Glsl,
+ TargetSpirVVersion = new SpirVVersion(1, 5)
+ };
+ }
+
+ options.SetTargetEnvironment(TargetEnvironment.Vulkan, EnvironmentVersion.Vulkan_1_2);
+ Compiler compiler = new Compiler(options);
+ var scr = compiler.Compile(glsl, "Ryu", GetShaderCShaderStage(stage));
+
+ lock (_shaderOptionsLock)
+ {
+ options.Dispose();
+ }
+
+ if (scr.Status != Status.Success)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Shader compilation error: {scr.Status} {scr.ErrorMessage}");
+
+ return null;
+ }
+
+ var spirvBytes = new Span<byte>((void*)scr.CodePointer, (int)scr.CodeLength);
+
+ byte[] code = new byte[(scr.CodeLength + 3) & ~3];
+
+ spirvBytes.CopyTo(code.AsSpan().Slice(0, (int)scr.CodeLength));
+
+ return code;
+ }
+
+ private static ShaderKind GetShaderCShaderStage(ShaderStage stage)
+ {
+ switch (stage)
+ {
+ case ShaderStage.Vertex:
+ return ShaderKind.GlslVertexShader;
+ case ShaderStage.Geometry:
+ return ShaderKind.GlslGeometryShader;
+ case ShaderStage.TessellationControl:
+ return ShaderKind.GlslTessControlShader;
+ case ShaderStage.TessellationEvaluation:
+ return ShaderKind.GlslTessEvaluationShader;
+ case ShaderStage.Fragment:
+ return ShaderKind.GlslFragmentShader;
+ case ShaderStage.Compute:
+ return ShaderKind.GlslComputeShader;
+ };
+
+ Logger.Debug?.Print(LogClass.Gpu, $"Invalid {nameof(ShaderStage)} enum value: {stage}.");
+
+ return ShaderKind.GlslVertexShader;
+ }
+
+ public unsafe PipelineShaderStageCreateInfo GetInfo()
+ {
+ return new PipelineShaderStageCreateInfo()
+ {
+ SType = StructureType.PipelineShaderStageCreateInfo,
+ Stage = _stage,
+ Module = _module,
+ PName = (byte*)_entryPointName
+ };
+ }
+
+ public void WaitForCompile()
+ {
+ CompileTask.Wait();
+ }
+
+ public unsafe void Dispose()
+ {
+ if (_entryPointName != IntPtr.Zero)
+ {
+ _api.DestroyShaderModule(_device, _module, null);
+ Marshal.FreeHGlobal(_entryPointName);
+ _entryPointName = IntPtr.Zero;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
new file mode 100644
index 00000000..5447b1f5
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
@@ -0,0 +1,406 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class ShaderCollection : IProgram
+ {
+ private readonly PipelineShaderStageCreateInfo[] _infos;
+ private readonly Shader[] _shaders;
+
+ private readonly PipelineLayoutCacheEntry _plce;
+
+ public PipelineLayout PipelineLayout => _plce.PipelineLayout;
+
+ public bool HasMinimalLayout { get; }
+ public bool UsePushDescriptors { get; }
+
+ public uint Stages { get; }
+
+ public int[][][] Bindings { get; }
+
+ public ProgramLinkStatus LinkStatus { get; private set; }
+
+ public bool IsLinked
+ {
+ get
+ {
+ if (LinkStatus == ProgramLinkStatus.Incomplete)
+ {
+ CheckProgramLink(true);
+ }
+
+ return LinkStatus == ProgramLinkStatus.Success;
+ }
+ }
+
+ private HashTableSlim<PipelineUid, Auto<DisposablePipeline>> _graphicsPipelineCache;
+ private Auto<DisposablePipeline> _computePipeline;
+
+ private VulkanRenderer _gd;
+ private Device _device;
+ private bool _initialized;
+ private bool _isCompute;
+
+ private ProgramPipelineState _state;
+ private DisposableRenderPass _dummyRenderPass;
+ private Task _compileTask;
+ private bool _firstBackgroundUse;
+
+ public ShaderCollection(VulkanRenderer gd, Device device, ShaderSource[] shaders, bool isMinimal = false)
+ {
+ _gd = gd;
+ _device = device;
+
+ gd.Shaders.Add(this);
+
+ var internalShaders = new Shader[shaders.Length];
+
+ _infos = new PipelineShaderStageCreateInfo[shaders.Length];
+
+ LinkStatus = ProgramLinkStatus.Incomplete;
+
+ uint stages = 0;
+
+ for (int i = 0; i < shaders.Length; i++)
+ {
+ var shader = new Shader(gd.Api, device, shaders[i]);
+
+ stages |= 1u << shader.StageFlags switch
+ {
+ ShaderStageFlags.ShaderStageFragmentBit => 1,
+ ShaderStageFlags.ShaderStageGeometryBit => 2,
+ ShaderStageFlags.ShaderStageTessellationControlBit => 3,
+ ShaderStageFlags.ShaderStageTessellationEvaluationBit => 4,
+ _ => 0
+ };
+
+ if (shader.StageFlags == ShaderStageFlags.ShaderStageComputeBit)
+ {
+ _isCompute = true;
+ }
+
+ internalShaders[i] = shader;
+ }
+
+ _shaders = internalShaders;
+
+ bool usePd = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
+
+ _plce = isMinimal
+ ? gd.PipelineLayoutCache.Create(gd, device, shaders)
+ : gd.PipelineLayoutCache.GetOrCreate(gd, device, stages, usePd);
+
+ HasMinimalLayout = isMinimal;
+ UsePushDescriptors = usePd;
+
+ Stages = stages;
+
+ int[][] GrabAll(Func<ShaderBindings, IReadOnlyCollection<int>> selector)
+ {
+ bool hasAny = false;
+ int[][] bindings = new int[internalShaders.Length][];
+
+ for (int i = 0; i < internalShaders.Length; i++)
+ {
+ var collection = selector(internalShaders[i].Bindings);
+ hasAny |= collection.Count != 0;
+ bindings[i] = collection.ToArray();
+ }
+
+ return hasAny ? bindings : Array.Empty<int[]>();
+ }
+
+ Bindings = new[]
+ {
+ GrabAll(x => x.UniformBufferBindings),
+ GrabAll(x => x.StorageBufferBindings),
+ GrabAll(x => x.TextureBindings),
+ GrabAll(x => x.ImageBindings)
+ };
+
+ _compileTask = Task.CompletedTask;
+ _firstBackgroundUse = false;
+ }
+
+ public ShaderCollection(
+ VulkanRenderer gd,
+ Device device,
+ ShaderSource[] sources,
+ ProgramPipelineState state,
+ bool fromCache) : this(gd, device, sources)
+ {
+ _state = state;
+
+ _compileTask = BackgroundCompilation();
+ _firstBackgroundUse = !fromCache;
+ }
+
+ private async Task BackgroundCompilation()
+ {
+ await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
+
+ if (_shaders.Any(shader => shader.CompileStatus == ProgramLinkStatus.Failure))
+ {
+ LinkStatus = ProgramLinkStatus.Failure;
+
+ return;
+ }
+
+ try
+ {
+ if (_isCompute)
+ {
+ CreateBackgroundComputePipeline();
+ }
+ else
+ {
+ CreateBackgroundGraphicsPipeline();
+ }
+ }
+ catch (VulkanException e)
+ {
+ Logger.Error?.PrintMsg(LogClass.Gpu, $"Background Compilation failed: {e.Message}");
+
+ LinkStatus = ProgramLinkStatus.Failure;
+ }
+ }
+
+ private void EnsureShadersReady()
+ {
+ if (!_initialized)
+ {
+ CheckProgramLink(true);
+
+ ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
+
+ for (int i = 0; i < _shaders.Length; i++)
+ {
+ var shader = _shaders[i];
+
+ if (shader.CompileStatus != ProgramLinkStatus.Success)
+ {
+ resultStatus = ProgramLinkStatus.Failure;
+ }
+
+ _infos[i] = shader.GetInfo();
+ }
+
+ // If the link status was already set as failure by background compilation, prefer that decision.
+ if (LinkStatus != ProgramLinkStatus.Failure)
+ {
+ LinkStatus = resultStatus;
+ }
+
+ _initialized = true;
+ }
+ }
+
+ public PipelineShaderStageCreateInfo[] GetInfos()
+ {
+ EnsureShadersReady();
+
+ return _infos;
+ }
+
+ protected unsafe DisposableRenderPass CreateDummyRenderPass()
+ {
+ if (_dummyRenderPass.Value.Handle != 0)
+ {
+ return _dummyRenderPass;
+ }
+
+ return _dummyRenderPass = _state.ToRenderPass(_gd, _device);
+ }
+
+ public void CreateBackgroundComputePipeline()
+ {
+ PipelineState pipeline = new PipelineState();
+ pipeline.Initialize();
+
+ pipeline.Stages[0] = _shaders[0].GetInfo();
+ pipeline.StagesCount = 1;
+ pipeline.PipelineLayout = PipelineLayout;
+
+ pipeline.CreateComputePipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache);
+ pipeline.Dispose();
+ }
+
+ public void CreateBackgroundGraphicsPipeline()
+ {
+ // To compile shaders in the background in Vulkan, we need to create valid pipelines using the shader modules.
+ // The GPU provides pipeline state via the GAL that can be converted into our internal Vulkan pipeline state.
+ // This should match the pipeline state at the time of the first draw. If it doesn't, then it'll likely be
+ // close enough that the GPU driver will reuse the compiled shader for the different state.
+
+ // First, we need to create a render pass object compatible with the one that will be used at runtime.
+ // The active attachment formats have been provided by the abstraction layer.
+ var renderPass = CreateDummyRenderPass();
+
+ PipelineState pipeline = _state.ToVulkanPipelineState(_gd);
+
+ // Copy the shader stage info to the pipeline.
+ var stages = pipeline.Stages.ToSpan();
+
+ for (int i = 0; i < _shaders.Length; i++)
+ {
+ stages[i] = _shaders[i].GetInfo();
+ }
+
+ pipeline.StagesCount = (uint)_shaders.Length;
+ pipeline.PipelineLayout = PipelineLayout;
+
+ pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value);
+ pipeline.Dispose();
+ }
+
+ public ProgramLinkStatus CheckProgramLink(bool blocking)
+ {
+ if (LinkStatus == ProgramLinkStatus.Incomplete)
+ {
+ ProgramLinkStatus resultStatus = ProgramLinkStatus.Success;
+
+ foreach (Shader shader in _shaders)
+ {
+ if (shader.CompileStatus == ProgramLinkStatus.Incomplete)
+ {
+ if (blocking)
+ {
+ // Wait for this shader to finish compiling.
+ shader.WaitForCompile();
+
+ if (shader.CompileStatus != ProgramLinkStatus.Success)
+ {
+ resultStatus = ProgramLinkStatus.Failure;
+ }
+ }
+ else
+ {
+ return ProgramLinkStatus.Incomplete;
+ }
+ }
+ }
+
+ if (!_compileTask.IsCompleted)
+ {
+ if (blocking)
+ {
+ _compileTask.Wait();
+
+ if (LinkStatus == ProgramLinkStatus.Failure)
+ {
+ return ProgramLinkStatus.Failure;
+ }
+ }
+ else
+ {
+ return ProgramLinkStatus.Incomplete;
+ }
+ }
+
+ return resultStatus;
+ }
+
+ return LinkStatus;
+ }
+
+ public byte[] GetBinary()
+ {
+ return null;
+ }
+
+ public void AddComputePipeline(Auto<DisposablePipeline> pipeline)
+ {
+ _computePipeline = pipeline;
+ }
+
+ public void RemoveComputePipeline()
+ {
+ _computePipeline = null;
+ }
+
+ public void AddGraphicsPipeline(ref PipelineUid key, Auto<DisposablePipeline> pipeline)
+ {
+ (_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
+ }
+
+ public bool TryGetComputePipeline(out Auto<DisposablePipeline> pipeline)
+ {
+ pipeline = _computePipeline;
+ return pipeline != null;
+ }
+
+ public bool TryGetGraphicsPipeline(ref PipelineUid key, out Auto<DisposablePipeline> pipeline)
+ {
+ if (_graphicsPipelineCache == null)
+ {
+ pipeline = default;
+ return false;
+ }
+
+ if (!_graphicsPipelineCache.TryGetValue(ref key, out pipeline))
+ {
+ if (_firstBackgroundUse)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, "Background pipeline compile missed on draw - incorrect pipeline state?");
+ _firstBackgroundUse = false;
+ }
+
+ return false;
+ }
+
+ _firstBackgroundUse = false;
+
+ return true;
+ }
+
+ public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(
+ VulkanRenderer gd,
+ int commandBufferIndex,
+ int setIndex,
+ out bool isNew)
+ {
+ return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew);
+ }
+
+ protected virtual unsafe void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (!_gd.Shaders.Remove(this))
+ {
+ return;
+ }
+
+ for (int i = 0; i < _shaders.Length; i++)
+ {
+ _shaders[i].Dispose();
+ }
+
+ if (_graphicsPipelineCache != null)
+ {
+ foreach (Auto<DisposablePipeline> pipeline in _graphicsPipelineCache.Values)
+ {
+ pipeline.Dispose();
+ }
+ }
+
+ _computePipeline?.Dispose();
+ if (_dummyRenderPass.Value.Handle != 0)
+ {
+ _dummyRenderPass.Dispose();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitClearAlphaFragmentShaderSource.frag b/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitClearAlphaFragmentShaderSource.frag
new file mode 100644
index 00000000..f31316d0
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitClearAlphaFragmentShaderSource.frag
@@ -0,0 +1,11 @@
+#version 450 core
+
+layout (binding = 0, set = 2) uniform sampler2D tex;
+
+layout (location = 0) in vec2 tex_coord;
+layout (location = 0) out vec4 colour;
+
+void main()
+{
+ colour = vec4(texture(tex, tex_coord).rgb, 1.0f);
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitFragmentShaderSource.frag b/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitFragmentShaderSource.frag
new file mode 100644
index 00000000..89dc1ff8
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitFragmentShaderSource.frag
@@ -0,0 +1,11 @@
+#version 450 core
+
+layout (binding = 0, set = 2) uniform sampler2D tex;
+
+layout (location = 0) in vec2 tex_coord;
+layout (location = 0) out vec4 colour;
+
+void main()
+{
+ colour = texture(tex, tex_coord);
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitVertexShaderSource.vert b/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitVertexShaderSource.vert
new file mode 100644
index 00000000..be93a64d
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitVertexShaderSource.vert
@@ -0,0 +1,20 @@
+#version 450 core
+
+layout (std140, binding = 1) uniform tex_coord_in
+{
+ vec4 tex_coord_in_data;
+};
+
+layout (location = 0) out vec2 tex_coord;
+
+void main()
+{
+ int low = gl_VertexIndex & 1;
+ int high = gl_VertexIndex >> 1;
+ tex_coord.x = tex_coord_in_data[low];
+ tex_coord.y = tex_coord_in_data[2 + high];
+ gl_Position.x = (float(low) - 0.5f) * 2.0f;
+ gl_Position.y = (float(high) - 0.5f) * 2.0f;
+ gl_Position.z = 0.0f;
+ gl_Position.w = 1.0f;
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFragmentShaderSource.frag b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFragmentShaderSource.frag
new file mode 100644
index 00000000..ddd4369c
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFragmentShaderSource.frag
@@ -0,0 +1,9 @@
+#version 450 core
+
+layout (location = 0) in vec4 clear_colour;
+layout (location = 0) out vec4 colour;
+
+void main()
+{
+ colour = clear_colour;
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert
new file mode 100644
index 00000000..2f1b9b2c
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert
@@ -0,0 +1,19 @@
+#version 450 core
+
+layout (std140, binding = 1) uniform clear_colour_in
+{
+ vec4 clear_colour_in_data;
+};
+
+layout (location = 0) out vec4 clear_colour;
+
+void main()
+{
+ int low = gl_VertexIndex & 1;
+ int high = gl_VertexIndex >> 1;
+ clear_colour = clear_colour_in_data;
+ gl_Position.x = (float(low) - 0.5f) * 2.0f;
+ gl_Position.y = (float(high) - 0.5f) * 2.0f;
+ gl_Position.z = 0.0f;
+ gl_Position.w = 1.0f;
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs b/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs
new file mode 100644
index 00000000..b21407c3
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs
@@ -0,0 +1,314 @@
+using System;
+
+namespace Ryujinx.Graphics.Vulkan.Shaders
+{
+ static class ShaderBinaries
+ {
+ public static readonly byte[] ColorBlitClearAlphaFragmentShaderSource = new byte[]
+ {
+ 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x1B, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+ 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F,
+ 0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x00,
+ 0x05, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x5F, 0x63, 0x6F, 0x6F, 0x72,
+ 0x64, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x03, 0x00, 0x0B, 0x00, 0x00, 0x00,
+ 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x3F, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+ 0x3D, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+ };
+
+ public static readonly byte[] ColorBlitFragmentShaderSource = new byte[]
+ {
+ 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+ 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F,
+ 0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x00,
+ 0x05, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x5F, 0x63, 0x6F, 0x6F, 0x72,
+ 0x64, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x03, 0x00, 0x0B, 0x00, 0x00, 0x00,
+ 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+ 0x3D, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+ };
+
+ public static readonly byte[] ColorBlitVertexShaderSource = new byte[]
+ {
+ 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x3F, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+ 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00,
+ 0x0A, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6E, 0x64,
+ 0x65, 0x78, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x5F,
+ 0x63, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x74, 0x65, 0x78, 0x5F, 0x63, 0x6F, 0x6F, 0x72, 0x64, 0x5F, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x08, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x5F,
+ 0x63, 0x6F, 0x6F, 0x72, 0x64, 0x5F, 0x69, 0x6E, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00,
+ 0x2A, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78,
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x06, 0x00, 0x07, 0x00,
+ 0x2A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x69, 0x6E, 0x74,
+ 0x53, 0x69, 0x7A, 0x65, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x07, 0x00, 0x2A, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x43, 0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61,
+ 0x6E, 0x63, 0x65, 0x00, 0x06, 0x00, 0x07, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x67, 0x6C, 0x5F, 0x43, 0x75, 0x6C, 0x6C, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x00,
+ 0x05, 0x00, 0x03, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+ 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x47, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
+ 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x05, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x0B, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x2A, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00,
+ 0x2A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x00, 0x29, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x06, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x2B, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+ 0x2B, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x2B, 0x00, 0x04, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x2B, 0x00, 0x04, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x2B, 0x00, 0x04, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0xC7, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x0D, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x05, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+ 0x41, 0x00, 0x06, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x1D, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
+ 0x1B, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x20, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x28, 0x00, 0x00, 0x00,
+ 0x26, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
+ 0x0D, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x2E, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x32, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x33, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x6F, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x83, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x2F, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
+ 0x36, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x3E, 0x00, 0x03, 0x00, 0x38, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x3A, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
+ 0x41, 0x00, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x3E, 0x00, 0x00, 0x00,
+ 0x3C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+ };
+
+ public static readonly byte[] ColorClearFragmentShaderSource = new byte[]
+ {
+ 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x0D, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+ 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x6C, 0x6F,
+ 0x75, 0x72, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61,
+ 0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00,
+ 0x0B, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+ 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
+ 0x0C, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
+ 0x0C, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+ };
+
+ public static readonly byte[] ColorClearVertexShaderSource = new byte[]
+ {
+ 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x36, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30,
+ 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x0F, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E,
+ 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00,
+ 0x0A, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x49, 0x6E, 0x64,
+ 0x65, 0x78, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x14, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61,
+ 0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00,
+ 0x15, 0x00, 0x00, 0x00, 0x63, 0x6C, 0x65, 0x61, 0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72,
+ 0x5F, 0x69, 0x6E, 0x00, 0x06, 0x00, 0x09, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x63, 0x6C, 0x65, 0x61, 0x72, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x5F, 0x69, 0x6E, 0x5F,
+ 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x17, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50,
+ 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00,
+ 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x73, 0x69, 0x74,
+ 0x69, 0x6F, 0x6E, 0x00, 0x06, 0x00, 0x07, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x69, 0x6E, 0x74, 0x53, 0x69, 0x7A, 0x65, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x07, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x43,
+ 0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x00, 0x06, 0x00, 0x07, 0x00,
+ 0x1F, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x43, 0x75, 0x6C, 0x6C, 0x44,
+ 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x00, 0x05, 0x00, 0x03, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
+ 0x2A, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x1F, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
+ 0x1F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x05, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x11, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x17, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x3B, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x1E, 0x00, 0x03, 0x00, 0x15, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00,
+ 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+ 0x19, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
+ 0x1C, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00,
+ 0x1C, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x06, 0x00,
+ 0x1F, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00,
+ 0x1E, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x1F, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x3F, 0x2B, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x2B, 0x00, 0x04, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x29, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x3F, 0x2B, 0x00, 0x04, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
+ 0xC7, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
+ 0x0C, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x0B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x19, 0x00, 0x00, 0x00,
+ 0x1A, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+ 0x12, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x23, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x25, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
+ 0x41, 0x00, 0x06, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x2A, 0x00, 0x00, 0x00,
+ 0x27, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00,
+ 0x2C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00,
+ 0x2E, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
+ 0x29, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x1D, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00,
+ 0x41, 0x00, 0x06, 0x00, 0x29, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x29, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+ 0x21, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
+ 0x35, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+ };
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/StagingBuffer.cs b/Ryujinx.Graphics.Vulkan/StagingBuffer.cs
new file mode 100644
index 00000000..fe7a786b
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/StagingBuffer.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class StagingBuffer : IDisposable
+ {
+ private const int BufferSize = 16 * 1024 * 1024;
+
+ private int _freeOffset;
+ private int _freeSize;
+
+ private readonly VulkanRenderer _gd;
+ private readonly BufferHolder _buffer;
+
+ private struct PendingCopy
+ {
+ public FenceHolder Fence { get; }
+ public int Size { get; }
+
+ public PendingCopy(FenceHolder fence, int size)
+ {
+ Fence = fence;
+ Size = size;
+ fence.Get();
+ }
+ }
+
+ private readonly Queue<PendingCopy> _pendingCopies;
+
+ public StagingBuffer(VulkanRenderer gd, BufferManager bufferManager)
+ {
+ _gd = gd;
+ _buffer = bufferManager.Create(gd, BufferSize);
+ _pendingCopies = new Queue<PendingCopy>();
+ _freeSize = BufferSize;
+ }
+
+ public unsafe void PushData(CommandBufferPool cbp, CommandBufferScoped? cbs, Action endRenderPass, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
+ {
+ bool isRender = cbs != null;
+ CommandBufferScoped scoped = cbs ?? cbp.Rent();
+
+ // 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)
+ {
+ 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);
+ }
+
+ if (!isRender)
+ {
+ scoped.Dispose();
+ }
+ }
+
+ private void PushDataImpl(CommandBufferScoped cbs, BufferHolder dst, int dstOffset, ReadOnlySpan<byte> data)
+ {
+ var srcBuffer = _buffer.GetBuffer();
+ var dstBuffer = dst.GetBuffer();
+
+ int offset = _freeOffset;
+ int capacity = BufferSize - offset;
+ if (capacity < data.Length)
+ {
+ _buffer.SetDataUnchecked(offset, data.Slice(0, capacity));
+ _buffer.SetDataUnchecked(0, data.Slice(capacity));
+
+ BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, dstOffset, capacity);
+ BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, 0, dstOffset + capacity, data.Length - capacity);
+ }
+ else
+ {
+ _buffer.SetDataUnchecked(offset, data);
+
+ BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, dstOffset, data.Length);
+ }
+
+ _freeOffset = (offset + data.Length) & (BufferSize - 1);
+ _freeSize -= data.Length;
+ 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<byte> 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()
+ {
+ 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;
+ pc.Fence.Put();
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _buffer.Dispose();
+
+ while (_pendingCopies.TryDequeue(out var pc))
+ {
+ pc.Fence.Put();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/SyncManager.cs b/Ryujinx.Graphics.Vulkan/SyncManager.cs
new file mode 100644
index 00000000..a39862d0
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/SyncManager.cs
@@ -0,0 +1,122 @@
+using Ryujinx.Common.Logging;
+using Silk.NET.Vulkan;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class SyncManager
+ {
+ private class SyncHandle
+ {
+ public ulong ID;
+ public MultiFenceHolder Waitable;
+ }
+
+ private ulong _firstHandle = 0;
+
+ private readonly VulkanRenderer _gd;
+ private readonly Device _device;
+ private List<SyncHandle> _handles;
+
+ public SyncManager(VulkanRenderer gd, Device device)
+ {
+ _gd = gd;
+ _device = device;
+ _handles = new List<SyncHandle>();
+ }
+
+ public void Create(ulong id)
+ {
+ MultiFenceHolder waitable = new MultiFenceHolder();
+
+ _gd.FlushAllCommands();
+ _gd.CommandBufferPool.AddWaitable(waitable);
+
+ SyncHandle handle = new SyncHandle
+ {
+ ID = id,
+ Waitable = waitable
+ };
+
+ lock (_handles)
+ {
+ _handles.Add(handle);
+ }
+ }
+
+ public void Wait(ulong id)
+ {
+ SyncHandle result = null;
+
+ lock (_handles)
+ {
+ if ((long)(_firstHandle - id) > 0)
+ {
+ return; // The handle has already been signalled or deleted.
+ }
+
+ foreach (SyncHandle handle in _handles)
+ {
+ if (handle.ID == id)
+ {
+ result = handle;
+ break;
+ }
+ }
+ }
+
+ if (result != null)
+ {
+ lock (result)
+ {
+ if (result.Waitable == null)
+ {
+ return;
+ }
+
+ bool signaled = result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
+ if (!signaled)
+ {
+ Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
+ }
+ }
+ }
+ }
+
+ public void Cleanup()
+ {
+ // Iterate through handles and remove any that have already been signalled.
+
+ while (true)
+ {
+ SyncHandle first = null;
+ lock (_handles)
+ {
+ first = _handles.FirstOrDefault();
+ }
+
+ if (first == null) break;
+
+ bool signaled = first.Waitable.WaitForFences(_gd.Api, _device, 0);
+ if (signaled)
+ {
+ // Delete the sync object.
+ lock (_handles)
+ {
+ lock (first)
+ {
+ _firstHandle = first.ID + 1;
+ _handles.RemoveAt(0);
+ first.Waitable = null;
+ }
+ }
+ } else
+ {
+ // This sync handle and any following have not been reached yet.
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
new file mode 100644
index 00000000..b2c5ff18
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
@@ -0,0 +1,150 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class TextureBuffer : ITexture
+ {
+ private readonly VulkanRenderer _gd;
+
+ private BufferHandle _bufferHandle;
+ private int _offset;
+ private int _size;
+ private Auto<DisposableBufferView> _bufferView;
+ private Dictionary<GAL.Format, Auto<DisposableBufferView>> _selfManagedViews;
+
+ public int Width { get; }
+ public int Height { get; }
+
+ public VkFormat VkFormat { get; }
+
+ public float ScaleFactor { get; }
+
+ public TextureBuffer(VulkanRenderer gd, TextureCreateInfo info, float scale)
+ {
+ _gd = gd;
+ Width = info.Width;
+ Height = info.Height;
+ VkFormat = FormatTable.GetFormat(info.Format);
+ ScaleFactor = scale;
+
+ gd.Textures.Add(this);
+ }
+
+ public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
+ {
+ throw new NotSupportedException();
+ }
+
+ public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ throw new NotSupportedException();
+ }
+
+ public ReadOnlySpan<byte> GetData()
+ {
+ return _gd.GetBufferData(_bufferHandle, _offset, _size);
+ }
+
+ public ReadOnlySpan<byte> GetData(int layer, int level)
+ {
+ return GetData();
+ }
+
+ public void Release()
+ {
+ if (_gd.Textures.Remove(this))
+ {
+ ReleaseImpl();
+ }
+ }
+
+ private void ReleaseImpl()
+ {
+ if (_selfManagedViews != null)
+ {
+ foreach (var bufferView in _selfManagedViews.Values)
+ {
+ bufferView.Dispose();
+ }
+
+ _selfManagedViews = null;
+ }
+
+ _bufferView?.Dispose();
+ _bufferView = null;
+ }
+
+ public void SetData(ReadOnlySpan<byte> data)
+ {
+ _gd.SetBufferData(_bufferHandle, _offset, data);
+ }
+
+ public void SetData(ReadOnlySpan<byte> data, int layer, int level)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void SetStorage(BufferRange buffer)
+ {
+ if (_bufferHandle == buffer.Handle &&
+ _offset == buffer.Offset &&
+ _size == buffer.Size)
+ {
+ return;
+ }
+
+ _bufferHandle = buffer.Handle;
+ _offset = buffer.Offset;
+ _size = buffer.Size;
+
+ ReleaseImpl();;
+ }
+
+ public BufferView GetBufferView(CommandBufferScoped cbs)
+ {
+ if (_bufferView == null)
+ {
+ _bufferView = _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size);
+ }
+
+ return _bufferView?.Get(cbs, _offset, _size).Value ?? default;
+ }
+
+ public BufferView GetBufferView(CommandBufferScoped cbs, GAL.Format format)
+ {
+ var vkFormat = FormatTable.GetFormat(format);
+ if (vkFormat == VkFormat)
+ {
+ return GetBufferView(cbs);
+ }
+
+ if (_selfManagedViews != null && _selfManagedViews.TryGetValue(format, out var bufferView))
+ {
+ return bufferView.Get(cbs, _offset, _size).Value;
+ }
+
+ bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size);
+
+ if (bufferView != null)
+ {
+ (_selfManagedViews ??= new Dictionary<GAL.Format, Auto<DisposableBufferView>>()).Add(format, bufferView);
+ }
+
+ return bufferView?.Get(cbs, _offset, _size).Value ?? default;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/TextureCopy.cs b/Ryujinx.Graphics.Vulkan/TextureCopy.cs
new file mode 100644
index 00000000..05e11093
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/TextureCopy.cs
@@ -0,0 +1,359 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Numerics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class TextureCopy
+ {
+ public static void Blit(
+ Vk api,
+ CommandBuffer commandBuffer,
+ Image srcImage,
+ Image dstImage,
+ TextureCreateInfo srcInfo,
+ TextureCreateInfo dstInfo,
+ Extents2D srcRegion,
+ Extents2D dstRegion,
+ int srcLayer,
+ int dstLayer,
+ int srcLevel,
+ int dstLevel,
+ int layers,
+ int levels,
+ bool linearFilter,
+ ImageAspectFlags srcAspectFlags = 0,
+ ImageAspectFlags dstAspectFlags = 0)
+ {
+ static (Offset3D, Offset3D) ExtentsToOffset3D(Extents2D extents, int width, int height, int level)
+ {
+ static int Clamp(int value, int max)
+ {
+ return Math.Clamp(value, 0, max);
+ }
+
+ var xy1 = new Offset3D(Clamp(extents.X1, width) >> level, Clamp(extents.Y1, height) >> level, 0);
+ var xy2 = new Offset3D(Clamp(extents.X2, width) >> level, Clamp(extents.Y2, height) >> level, 1);
+
+ return (xy1, xy2);
+ }
+
+ if (srcAspectFlags == 0)
+ {
+ srcAspectFlags = srcInfo.Format.ConvertAspectFlags();
+ }
+
+ if (dstAspectFlags == 0)
+ {
+ dstAspectFlags = dstInfo.Format.ConvertAspectFlags();
+ }
+
+ var srcOffsets = new ImageBlit.SrcOffsetsBuffer();
+ var dstOffsets = new ImageBlit.DstOffsetsBuffer();
+
+ var filter = linearFilter && !dstInfo.Format.IsDepthOrStencil() ? Filter.Linear : Filter.Nearest;
+
+ TextureView.InsertImageBarrier(
+ api,
+ commandBuffer,
+ srcImage,
+ TextureStorage.DefaultAccessMask,
+ AccessFlags.AccessTransferReadBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ srcAspectFlags,
+ srcLayer,
+ srcLevel,
+ layers,
+ levels);
+
+ uint copySrcLevel = (uint)srcLevel;
+ uint copyDstLevel = (uint)dstLevel;
+
+ for (int level = 0; level < levels; level++)
+ {
+ var srcSl = new ImageSubresourceLayers(srcAspectFlags, copySrcLevel, (uint)srcLayer, (uint)layers);
+ var dstSl = new ImageSubresourceLayers(dstAspectFlags, copyDstLevel, (uint)dstLayer, (uint)layers);
+
+ (srcOffsets.Element0, srcOffsets.Element1) = ExtentsToOffset3D(srcRegion, srcInfo.Width, srcInfo.Height, level);
+ (dstOffsets.Element0, dstOffsets.Element1) = ExtentsToOffset3D(dstRegion, dstInfo.Width, dstInfo.Height, level);
+
+ var region = new ImageBlit()
+ {
+ SrcSubresource = srcSl,
+ SrcOffsets = srcOffsets,
+ DstSubresource = dstSl,
+ DstOffsets = dstOffsets
+ };
+
+ api.CmdBlitImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region, filter);
+
+ copySrcLevel++;
+ copyDstLevel++;
+
+ if (srcInfo.Target == Target.Texture3D || dstInfo.Target == Target.Texture3D)
+ {
+ layers = Math.Max(1, layers >> 1);
+ }
+ }
+
+ TextureView.InsertImageBarrier(
+ api,
+ commandBuffer,
+ dstImage,
+ AccessFlags.AccessTransferWriteBit,
+ TextureStorage.DefaultAccessMask,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ dstAspectFlags,
+ dstLayer,
+ dstLevel,
+ layers,
+ levels);
+ }
+
+ public static void Copy(
+ Vk api,
+ CommandBuffer commandBuffer,
+ Image srcImage,
+ Image dstImage,
+ TextureCreateInfo srcInfo,
+ TextureCreateInfo dstInfo,
+ int srcViewLayer,
+ int dstViewLayer,
+ int srcViewLevel,
+ int dstViewLevel,
+ int srcLayer,
+ int dstLayer,
+ int srcLevel,
+ int dstLevel)
+ {
+ int srcDepth = srcInfo.GetDepthOrLayers();
+ int srcLevels = srcInfo.Levels;
+
+ int dstDepth = dstInfo.GetDepthOrLayers();
+ int dstLevels = dstInfo.Levels;
+
+ if (dstInfo.Target == Target.Texture3D)
+ {
+ dstDepth = Math.Max(1, dstDepth >> dstLevel);
+ }
+
+ int depth = Math.Min(srcDepth, dstDepth);
+ int levels = Math.Min(srcLevels, dstLevels);
+
+ Copy(
+ api,
+ commandBuffer,
+ srcImage,
+ dstImage,
+ srcInfo,
+ dstInfo,
+ srcViewLayer,
+ dstViewLayer,
+ srcViewLevel,
+ dstViewLevel,
+ srcLayer,
+ dstLayer,
+ srcLevel,
+ dstLevel,
+ depth,
+ levels);
+ }
+
+ private static int ClampLevels(TextureCreateInfo info, int levels)
+ {
+ int width = info.Width;
+ int height = info.Height;
+ int depth = info.Target == Target.Texture3D ? info.Depth : 1;
+
+ int maxLevels = 1 + BitOperations.Log2((uint)Math.Max(Math.Max(width, height), depth));
+
+ if (levels > maxLevels)
+ {
+ levels = maxLevels;
+ }
+
+ return levels;
+ }
+
+ public static void Copy(
+ Vk api,
+ CommandBuffer commandBuffer,
+ Image srcImage,
+ Image dstImage,
+ TextureCreateInfo srcInfo,
+ TextureCreateInfo dstInfo,
+ int srcViewLayer,
+ int dstViewLayer,
+ int srcViewLevel,
+ int dstViewLevel,
+ int srcDepthOrLayer,
+ int dstDepthOrLayer,
+ int srcLevel,
+ int dstLevel,
+ int depthOrLayers,
+ int levels)
+ {
+ int srcZ;
+ int srcLayer;
+ int srcDepth;
+ int srcLayers;
+
+ if (srcInfo.Target == Target.Texture3D)
+ {
+ srcZ = srcDepthOrLayer;
+ srcLayer = 0;
+ srcDepth = depthOrLayers;
+ srcLayers = 1;
+ }
+ else
+ {
+ srcZ = 0;
+ srcLayer = srcDepthOrLayer;
+ srcDepth = 1;
+ srcLayers = depthOrLayers;
+ }
+
+ int dstZ;
+ int dstLayer;
+ int dstDepth;
+ int dstLayers;
+
+ if (dstInfo.Target == Target.Texture3D)
+ {
+ dstZ = dstDepthOrLayer;
+ dstLayer = 0;
+ dstDepth = depthOrLayers;
+ dstLayers = 1;
+ }
+ else
+ {
+ dstZ = 0;
+ dstLayer = dstDepthOrLayer;
+ dstDepth = 1;
+ dstLayers = depthOrLayers;
+ }
+
+ int srcWidth = srcInfo.Width;
+ int srcHeight = srcInfo.Height;
+
+ int dstWidth = dstInfo.Width;
+ int dstHeight = dstInfo.Height;
+
+ srcWidth = Math.Max(1, srcWidth >> srcLevel);
+ srcHeight = Math.Max(1, srcHeight >> srcLevel);
+
+ dstWidth = Math.Max(1, dstWidth >> dstLevel);
+ dstHeight = Math.Max(1, dstHeight >> dstLevel);
+
+ int blockWidth = 1;
+ int blockHeight = 1;
+ bool sizeInBlocks = false;
+
+ // When copying from a compressed to a non-compressed format,
+ // the non-compressed texture will have the size of the texture
+ // in blocks (not in texels), so we must adjust that size to
+ // match the size in texels of the compressed texture.
+ if (!srcInfo.IsCompressed && dstInfo.IsCompressed)
+ {
+ srcWidth *= dstInfo.BlockWidth;
+ srcHeight *= dstInfo.BlockHeight;
+ blockWidth = dstInfo.BlockWidth;
+ blockHeight = dstInfo.BlockHeight;
+
+ sizeInBlocks = true;
+ }
+ else if (srcInfo.IsCompressed && !dstInfo.IsCompressed)
+ {
+ dstWidth *= srcInfo.BlockWidth;
+ dstHeight *= srcInfo.BlockHeight;
+ blockWidth = srcInfo.BlockWidth;
+ blockHeight = srcInfo.BlockHeight;
+ }
+
+ int width = Math.Min(srcWidth, dstWidth);
+ int height = Math.Min(srcHeight, dstHeight);
+
+ ImageAspectFlags srcAspect = srcInfo.Format.ConvertAspectFlags();
+ ImageAspectFlags dstAspect = dstInfo.Format.ConvertAspectFlags();
+
+ TextureView.InsertImageBarrier(
+ api,
+ commandBuffer,
+ srcImage,
+ TextureStorage.DefaultAccessMask,
+ AccessFlags.AccessTransferReadBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ srcAspect,
+ srcViewLayer + srcLayer,
+ srcViewLevel + srcLevel,
+ srcLayers,
+ levels);
+
+ for (int level = 0; level < levels; level++)
+ {
+ // Stop copy if we are already out of the levels range.
+ if (level >= srcInfo.Levels || dstLevel + level >= dstInfo.Levels)
+ {
+ break;
+ }
+
+ var srcSl = new ImageSubresourceLayers(
+ srcAspect,
+ (uint)(srcViewLevel + srcLevel + level),
+ (uint)(srcViewLayer + srcLayer),
+ (uint)srcLayers);
+
+ var dstSl = new ImageSubresourceLayers(
+ dstAspect,
+ (uint)(dstViewLevel + dstLevel + level),
+ (uint)(dstViewLayer + dstLayer),
+ (uint)dstLayers);
+
+ int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width;
+ int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height;
+
+ var extent = new Extent3D((uint)copyWidth, (uint)copyHeight, (uint)srcDepth);
+
+ if (srcInfo.Samples > 1 && srcInfo.Samples != dstInfo.Samples)
+ {
+ var region = new ImageResolve(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent);
+
+ api.CmdResolveImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region);
+ }
+ else
+ {
+ var region = new ImageCopy(srcSl, new Offset3D(0, 0, srcZ), dstSl, new Offset3D(0, 0, dstZ), extent);
+
+ api.CmdCopyImage(commandBuffer, srcImage, ImageLayout.General, dstImage, ImageLayout.General, 1, region);
+ }
+
+ width = Math.Max(1, width >> 1);
+ height = Math.Max(1, height >> 1);
+
+ if (srcInfo.Target == Target.Texture3D)
+ {
+ srcDepth = Math.Max(1, srcDepth >> 1);
+ }
+ }
+
+ TextureView.InsertImageBarrier(
+ api,
+ commandBuffer,
+ dstImage,
+ AccessFlags.AccessTransferWriteBit,
+ TextureStorage.DefaultAccessMask,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ dstAspect,
+ dstViewLayer + dstLayer,
+ dstViewLevel + dstLevel,
+ dstLayers,
+ levels);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/Ryujinx.Graphics.Vulkan/TextureStorage.cs
new file mode 100644
index 00000000..b2cbd602
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -0,0 +1,504 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using VkBuffer = Silk.NET.Vulkan.Buffer;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class TextureStorage : IDisposable
+ {
+ private const MemoryPropertyFlags DefaultImageMemoryFlags =
+ MemoryPropertyFlags.MemoryPropertyDeviceLocalBit;
+
+ private const ImageUsageFlags DefaultUsageFlags =
+ ImageUsageFlags.ImageUsageSampledBit |
+ ImageUsageFlags.ImageUsageTransferSrcBit |
+ ImageUsageFlags.ImageUsageTransferDstBit;
+
+ public const AccessFlags DefaultAccessMask =
+ AccessFlags.AccessShaderReadBit |
+ AccessFlags.AccessShaderWriteBit |
+ AccessFlags.AccessColorAttachmentReadBit |
+ AccessFlags.AccessColorAttachmentWriteBit |
+ AccessFlags.AccessDepthStencilAttachmentReadBit |
+ AccessFlags.AccessDepthStencilAttachmentWriteBit |
+ AccessFlags.AccessTransferReadBit |
+ AccessFlags.AccessTransferWriteBit;
+
+ private readonly VulkanRenderer _gd;
+
+ private readonly Device _device;
+
+ private TextureCreateInfo _info;
+
+ public TextureCreateInfo Info => _info;
+
+ private readonly Image _image;
+ private readonly Auto<DisposableImage> _imageAuto;
+ private readonly Auto<MemoryAllocation> _allocationAuto;
+ private Auto<MemoryAllocation> _foreignAllocationAuto;
+
+ private Dictionary<GAL.Format, TextureStorage> _aliasedStorages;
+
+ private AccessFlags _lastModificationAccess;
+ private PipelineStageFlags _lastModificationStage;
+
+ private int _viewsCount;
+ private ulong _size;
+
+ public VkFormat VkFormat { get; }
+ public float ScaleFactor { get; }
+
+ public unsafe TextureStorage(
+ VulkanRenderer gd,
+ PhysicalDevice physicalDevice,
+ Device device,
+ TextureCreateInfo info,
+ float scaleFactor,
+ Auto<MemoryAllocation> foreignAllocation = null)
+ {
+ _gd = gd;
+ _device = device;
+ _info = info;
+ ScaleFactor = scaleFactor;
+
+ var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
+ var levels = (uint)info.Levels;
+ var layers = (uint)info.GetLayers();
+ var depth = (uint)(info.Target == Target.Texture3D ? info.Depth : 1);
+
+ VkFormat = format;
+
+ var type = info.Target.Convert();
+
+ var extent = new Extent3D((uint)info.Width, (uint)info.Height, depth);
+
+ var sampleCountFlags = ConvertToSampleCountFlags((uint)info.Samples);
+
+ var usage = DefaultUsageFlags;
+
+ if (info.Format.IsDepthOrStencil())
+ {
+ usage |= ImageUsageFlags.ImageUsageDepthStencilAttachmentBit;
+ }
+ else if (info.Format.IsRtColorCompatible())
+ {
+ usage |= ImageUsageFlags.ImageUsageColorAttachmentBit;
+ }
+
+ if (info.Format.IsImageCompatible())
+ {
+ usage |= ImageUsageFlags.ImageUsageStorageBit;
+ }
+
+ var flags = ImageCreateFlags.ImageCreateMutableFormatBit;
+
+ // This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
+ bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
+ bool cubeCompatible = gd.IsAmdGcn ? isCube : (info.Width == info.Height && layers >= 6);
+
+ if (type == ImageType.ImageType2D && cubeCompatible)
+ {
+ flags |= ImageCreateFlags.ImageCreateCubeCompatibleBit;
+ }
+
+ if (type == ImageType.ImageType3D)
+ {
+ flags |= ImageCreateFlags.ImageCreate2DArrayCompatibleBit;
+ }
+
+ var imageCreateInfo = new ImageCreateInfo()
+ {
+ SType = StructureType.ImageCreateInfo,
+ ImageType = type,
+ Format = format,
+ Extent = extent,
+ MipLevels = levels,
+ ArrayLayers = layers,
+ Samples = sampleCountFlags,
+ Tiling = ImageTiling.Optimal,
+ Usage = usage,
+ SharingMode = SharingMode.Exclusive,
+ InitialLayout = ImageLayout.Undefined,
+ Flags = flags
+ };
+
+ gd.Api.CreateImage(device, imageCreateInfo, null, out _image).ThrowOnError();
+
+ if (foreignAllocation == null)
+ {
+ gd.Api.GetImageMemoryRequirements(device, _image, out var requirements);
+ var allocation = gd.MemoryAllocator.AllocateDeviceMemory(physicalDevice, requirements, DefaultImageMemoryFlags);
+
+ if (allocation.Memory.Handle == 0UL)
+ {
+ gd.Api.DestroyImage(device, _image, null);
+ throw new Exception("Image initialization failed.");
+ }
+
+ _size = requirements.Size;
+
+ gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError();
+
+ _allocationAuto = new Auto<MemoryAllocation>(allocation);
+ _imageAuto = new Auto<DisposableImage>(new DisposableImage(_gd.Api, device, _image), null, _allocationAuto);
+
+ InitialTransition(ImageLayout.Undefined, ImageLayout.General);
+ }
+ else
+ {
+ _foreignAllocationAuto = foreignAllocation;
+ foreignAllocation.IncrementReferenceCount();
+ var allocation = foreignAllocation.GetUnsafe();
+
+ gd.Api.BindImageMemory(device, _image, allocation.Memory, allocation.Offset).ThrowOnError();
+
+ _imageAuto = new Auto<DisposableImage>(new DisposableImage(_gd.Api, device, _image));
+
+ InitialTransition(ImageLayout.Preinitialized, ImageLayout.General);
+ }
+ }
+
+ public TextureStorage CreateAliasedColorForDepthStorageUnsafe(GAL.Format format)
+ {
+ var colorFormat = format switch
+ {
+ GAL.Format.S8Uint => GAL.Format.R8Unorm,
+ GAL.Format.D16Unorm => GAL.Format.R16Unorm,
+ GAL.Format.S8UintD24Unorm => GAL.Format.R8G8B8A8Unorm,
+ GAL.Format.D32Float => GAL.Format.R32Float,
+ GAL.Format.D24UnormS8Uint => GAL.Format.R8G8B8A8Unorm,
+ GAL.Format.D32FloatS8Uint => GAL.Format.R32G32Float,
+ _ => throw new ArgumentException($"\"{format}\" is not a supported depth or stencil format.")
+ };
+
+ return CreateAliasedStorageUnsafe(colorFormat);
+ }
+
+ public TextureStorage CreateAliasedStorageUnsafe(GAL.Format format)
+ {
+ if (_aliasedStorages == null || !_aliasedStorages.TryGetValue(format, out var storage))
+ {
+ _aliasedStorages ??= new Dictionary<GAL.Format, TextureStorage>();
+
+ var info = NewCreateInfoWith(ref _info, format, _info.BytesPerPixel);
+
+ storage = new TextureStorage(_gd, default, _device, info, ScaleFactor, _allocationAuto);
+
+ _aliasedStorages.Add(format, storage);
+ }
+
+ return storage;
+ }
+
+ public static TextureCreateInfo NewCreateInfoWith(ref TextureCreateInfo info, GAL.Format format, int bytesPerPixel)
+ {
+ return NewCreateInfoWith(ref info, format, bytesPerPixel, info.Width, info.Height);
+ }
+
+ public static TextureCreateInfo NewCreateInfoWith(
+ ref TextureCreateInfo info,
+ GAL.Format format,
+ int bytesPerPixel,
+ int width,
+ int height)
+ {
+ return new TextureCreateInfo(
+ width,
+ height,
+ info.Depth,
+ info.Levels,
+ info.Samples,
+ info.BlockWidth,
+ info.BlockHeight,
+ bytesPerPixel,
+ format,
+ info.DepthStencilMode,
+ info.Target,
+ info.SwizzleR,
+ info.SwizzleG,
+ info.SwizzleB,
+ info.SwizzleA);
+ }
+
+ public Auto<DisposableImage> GetImage()
+ {
+ return _imageAuto;
+ }
+
+ public Image GetImageForViewCreation()
+ {
+ 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)
+ {
+ 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();
+
+ var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, (uint)_info.Levels, 0, (uint)_info.GetLayers());
+
+ var barrier = new ImageMemoryBarrier()
+ {
+ SType = StructureType.ImageMemoryBarrier,
+ SrcAccessMask = 0,
+ DstAccessMask = DefaultAccessMask,
+ OldLayout = srcLayout,
+ NewLayout = dstLayout,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Image = _imageAuto.Get(cbs).Value,
+ SubresourceRange = subresourceRange
+ };
+
+ _gd.Api.CmdPipelineBarrier(
+ cbs.CommandBuffer,
+ PipelineStageFlags.PipelineStageTopOfPipeBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ 0,
+ 0,
+ null,
+ 0,
+ null,
+ 1,
+ barrier);
+
+ if (useTempCbs)
+ {
+ cbs.Dispose();
+ }
+ }
+
+ public static SampleCountFlags ConvertToSampleCountFlags(uint samples)
+ {
+ if (samples == 0 || samples > (uint)SampleCountFlags.SampleCount64Bit)
+ {
+ return SampleCountFlags.SampleCount1Bit;
+ }
+
+ // Round up to the nearest power of two.
+ return (SampleCountFlags)(1u << (31 - BitOperations.LeadingZeroCount(samples)));
+ }
+
+ public TextureView CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ return new TextureView(_gd, _device, info, this, firstLayer, firstLevel);
+ }
+
+ public void CopyFromOrToBuffer(
+ CommandBuffer commandBuffer,
+ VkBuffer buffer,
+ Image image,
+ int size,
+ bool to,
+ int x,
+ int y,
+ int dstLayer,
+ int dstLevel,
+ int dstLayers,
+ int dstLevels,
+ bool singleSlice,
+ ImageAspectFlags aspectFlags,
+ bool forFlush)
+ {
+ bool is3D = Info.Target == Target.Texture3D;
+ int width = Info.Width;
+ int height = Info.Height;
+ int depth = is3D && !singleSlice ? Info.Depth : 1;
+ int layer = is3D ? 0 : dstLayer;
+ int layers = dstLayers;
+ int levels = dstLevels;
+
+ int offset = 0;
+
+ for (int level = 0; level < levels; level++)
+ {
+ int mipSize = Info.GetMipSize(level);
+
+ if (forFlush)
+ {
+ mipSize = GetBufferDataLength(mipSize);
+ }
+
+ int endOffset = offset + mipSize;
+
+ if ((uint)endOffset > (uint)size)
+ {
+ break;
+ }
+
+ int rowLength = (Info.GetMipStride(level) / Info.BytesPerPixel) * Info.BlockWidth;
+
+ var sl = new ImageSubresourceLayers(
+ aspectFlags,
+ (uint)(dstLevel + level),
+ (uint)layer,
+ (uint)layers);
+
+ var extent = new Extent3D((uint)width, (uint)height, (uint)depth);
+
+ int z = is3D ? dstLayer : 0;
+
+ var region = new BufferImageCopy(
+ (ulong)offset,
+ (uint)BitUtils.AlignUp(rowLength, Info.BlockWidth),
+ (uint)BitUtils.AlignUp(height, Info.BlockHeight),
+ sl,
+ new Offset3D(x, y, z),
+ extent);
+
+ if (to)
+ {
+ _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
+ }
+ else
+ {
+ _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
+ }
+
+ offset += mipSize;
+
+ width = Math.Max(1, width >> 1);
+ height = Math.Max(1, height >> 1);
+
+ if (Info.Target == Target.Texture3D)
+ {
+ depth = Math.Max(1, depth >> 1);
+ }
+ }
+ }
+
+ private int GetBufferDataLength(int length)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ return length * 2;
+ }
+
+ return length;
+ }
+
+ private bool NeedsD24S8Conversion()
+ {
+ return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
+ }
+
+ public void SetModification(AccessFlags accessFlags, PipelineStageFlags stage)
+ {
+ _lastModificationAccess = accessFlags;
+ _lastModificationStage = stage;
+ }
+
+ public void InsertBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
+ {
+ if (_lastModificationAccess != AccessFlags.AccessNoneKhr)
+ {
+ ImageAspectFlags aspectFlags;
+
+ if (_info.Format.IsDepthOrStencil())
+ {
+ if (_info.Format == GAL.Format.S8Uint)
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectStencilBit;
+ }
+ else if (_info.Format == GAL.Format.D16Unorm || _info.Format == GAL.Format.D32Float)
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectDepthBit;
+ }
+ else
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit;
+ }
+ }
+ else
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectColorBit;
+ }
+
+ TextureView.InsertImageBarrier(
+ _gd.Api,
+ cbs.CommandBuffer,
+ _imageAuto.Get(cbs).Value,
+ _lastModificationAccess,
+ dstAccessFlags,
+ _lastModificationStage,
+ dstStageFlags,
+ aspectFlags,
+ 0,
+ 0,
+ _info.GetLayers(),
+ _info.Levels);
+
+ _lastModificationAccess = AccessFlags.AccessNoneKhr;
+ }
+ }
+
+ public void IncrementViewsCount()
+ {
+ _viewsCount++;
+ }
+
+ public void DecrementViewsCount()
+ {
+ if (--_viewsCount == 0)
+ {
+ _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_imageAuto, _size);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_aliasedStorages != null)
+ {
+ foreach (var storage in _aliasedStorages.Values)
+ {
+ storage.Dispose();
+ }
+
+ _aliasedStorages.Clear();
+ }
+
+ _imageAuto.Dispose();
+ _allocationAuto?.Dispose();
+ _foreignAllocationAuto?.DecrementReferenceCount();
+ _foreignAllocationAuto = null;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/TextureView.cs b/Ryujinx.Graphics.Vulkan/TextureView.cs
new file mode 100644
index 00000000..85cc3234
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -0,0 +1,1150 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using VkBuffer = Silk.NET.Vulkan.Buffer;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class TextureView : ITexture, IDisposable
+ {
+ private readonly VulkanRenderer _gd;
+
+ private readonly Device _device;
+
+ private readonly Auto<DisposableImageView> _imageView;
+ private readonly Auto<DisposableImageView> _imageViewIdentity;
+ private readonly Auto<DisposableImageView> _imageView2dArray;
+ private Dictionary<GAL.Format, TextureView> _selfManagedViews;
+
+ private TextureCreateInfo _info;
+
+ public TextureCreateInfo Info => _info;
+
+ public TextureStorage Storage { get; }
+
+ public int Width => Info.Width;
+ public int Height => Info.Height;
+ public int Layers => Info.GetDepthOrLayers();
+ public int FirstLayer { get; }
+ public int FirstLevel { get; }
+ public float ScaleFactor => Storage.ScaleFactor;
+ public VkFormat VkFormat { get; }
+ public bool Valid { get; private set; }
+
+ public TextureView(
+ VulkanRenderer gd,
+ Device device,
+ TextureCreateInfo info,
+ TextureStorage storage,
+ int firstLayer,
+ int firstLevel)
+ {
+ _gd = gd;
+ _device = device;
+ _info = info;
+ Storage = storage;
+ FirstLayer = firstLayer;
+ FirstLevel = firstLevel;
+
+ storage.IncrementViewsCount();
+
+ gd.Textures.Add(this);
+
+ var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
+ var levels = (uint)info.Levels;
+ var layers = (uint)info.GetLayers();
+
+ VkFormat = format;
+
+ var type = info.Target.ConvertView();
+
+ var swizzleR = info.SwizzleR.Convert();
+ var swizzleG = info.SwizzleG.Convert();
+ var swizzleB = info.SwizzleB.Convert();
+ var swizzleA = info.SwizzleA.Convert();
+
+ if (info.Format == GAL.Format.R5G5B5A1Unorm ||
+ info.Format == GAL.Format.R5G5B5X1Unorm ||
+ info.Format == GAL.Format.R5G6B5Unorm)
+ {
+ var temp = swizzleR;
+
+ swizzleR = swizzleB;
+ swizzleB = temp;
+ }
+ else if (info.Format == GAL.Format.R4G4B4A4Unorm)
+ {
+ var tempG = swizzleG;
+ var tempB = swizzleB;
+
+ swizzleB = swizzleA;
+ swizzleG = swizzleR;
+ swizzleR = tempG;
+ swizzleA = tempB;
+ }
+ else if (info.Format == GAL.Format.A1B5G5R5Unorm)
+ {
+ var tempB = swizzleB;
+ var tempA = swizzleA;
+
+ swizzleB = swizzleG;
+ swizzleA = swizzleR;
+ swizzleR = tempA;
+ swizzleG = tempB;
+ }
+
+ var componentMapping = new ComponentMapping(swizzleR, swizzleG, swizzleB, swizzleA);
+
+ var aspectFlags = info.Format.ConvertAspectFlags(info.DepthStencilMode);
+ var aspectFlagsDepth = info.Format.ConvertAspectFlags(DepthStencilMode.Depth);
+
+ var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers);
+ var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers);
+
+ unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType)
+ {
+ var imageCreateInfo = new ImageViewCreateInfo()
+ {
+ SType = StructureType.ImageViewCreateInfo,
+ Image = storage.GetImageForViewCreation(),
+ ViewType = viewType,
+ Format = format,
+ Components = cm,
+ SubresourceRange = sr
+ };
+
+ gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
+ return new Auto<DisposableImageView>(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage());
+ }
+
+ _imageView = CreateImageView(componentMapping, subresourceRange, type);
+
+ // Framebuffer attachments and storage images requires a identity component mapping.
+ var identityComponentMapping = new ComponentMapping(
+ ComponentSwizzle.R,
+ ComponentSwizzle.G,
+ ComponentSwizzle.B,
+ ComponentSwizzle.A);
+
+ _imageViewIdentity = CreateImageView(identityComponentMapping, subresourceRangeDepth, type);
+
+ // Framebuffer attachments also require 3D textures to be bound as 2D array.
+ if (info.Target == Target.Texture3D)
+ {
+ subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
+
+ _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.ImageViewType2DArray);
+ }
+
+ Valid = true;
+ }
+
+ public Auto<DisposableImage> GetImage()
+ {
+ return Storage.GetImage();
+ }
+
+ public Auto<DisposableImageView> GetImageView()
+ {
+ return _imageView;
+ }
+
+ public Auto<DisposableImageView> GetIdentityImageView()
+ {
+ return _imageViewIdentity;
+ }
+
+ public Auto<DisposableImageView> GetImageViewForAttachment()
+ {
+ return _imageView2dArray ?? _imageViewIdentity;
+ }
+
+ public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
+ {
+ var src = this;
+ var dst = (TextureView)destination;
+
+ if (!Valid || !dst.Valid)
+ {
+ return;
+ }
+
+ _gd.PipelineInternal.EndRenderPass();
+
+ var cbs = _gd.PipelineInternal.CurrentCommandBuffer;
+
+ var srcImage = src.GetImage().Get(cbs).Value;
+ var dstImage = dst.GetImage().Get(cbs).Value;
+
+ if (src.Info.Target.IsMultisample())
+ {
+ int depth = Math.Min(src.Info.Depth, dst.Info.Depth - firstLayer);
+ int levels = Math.Min(src.Info.Levels, dst.Info.Levels - firstLevel);
+
+ CopyMSToNonMS(_gd, cbs, src, dst, srcImage, dstImage, 0, firstLayer, 0, firstLevel, depth, levels);
+ }
+ else
+ {
+ TextureCopy.Copy(
+ _gd.Api,
+ cbs.CommandBuffer,
+ srcImage,
+ dstImage,
+ src.Info,
+ dst.Info,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ 0,
+ firstLayer,
+ 0,
+ firstLevel);
+ }
+ }
+
+ public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
+ {
+ var src = this;
+ var dst = (TextureView)destination;
+
+ if (!Valid || !dst.Valid)
+ {
+ return;
+ }
+
+ _gd.PipelineInternal.EndRenderPass();
+
+ var cbs = _gd.PipelineInternal.CurrentCommandBuffer;
+
+ var srcImage = src.GetImage().Get(cbs).Value;
+ var dstImage = dst.GetImage().Get(cbs).Value;
+
+ if (src.Info.Target.IsMultisample())
+ {
+ CopyMSToNonMS(_gd, cbs, src, dst, srcImage, dstImage, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
+ }
+ else
+ {
+ TextureCopy.Copy(
+ _gd.Api,
+ cbs.CommandBuffer,
+ srcImage,
+ dstImage,
+ src.Info,
+ dst.Info,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ srcLayer,
+ dstLayer,
+ srcLevel,
+ dstLevel,
+ 1,
+ 1);
+ }
+ }
+
+ private static void CopyMSToNonMS(
+ VulkanRenderer gd,
+ CommandBufferScoped cbs,
+ TextureView src,
+ TextureView dst,
+ Image srcImage,
+ Image dstImage,
+ int srcLayer,
+ int dstLayer,
+ int srcLevel,
+ int dstLevel,
+ int layers,
+ int levels)
+ {
+ bool differentFormats = src.Info.Format != dst.Info.Format;
+
+ var target = src.Info.Target switch
+ {
+ Target.Texture2D => Target.Texture2DMultisample,
+ Target.Texture2DArray => Target.Texture2DMultisampleArray,
+ Target.Texture2DMultisampleArray => Target.Texture2DArray,
+ _ => Target.Texture2D
+ };
+
+ var intermmediateTarget = differentFormats ? dst.Info.Target : target;
+ using var intermmediate = CreateIntermmediateTexture(gd, src, ref dst._info, intermmediateTarget, layers, levels);
+ var intermmediateImage = intermmediate.GetImage().Get(cbs).Value;
+
+ if (differentFormats)
+ {
+ // If the formats are different, the resolve would perform format conversion.
+ // So we need yet another intermmediate texture and do a copy to reinterpret the
+ // data into the correct (destination) format, without doing any sort of conversion.
+ using var intermmediate2 = CreateIntermmediateTexture(gd, src, ref src._info, target, layers, levels);
+ var intermmediate2Image = intermmediate2.GetImage().Get(cbs).Value;
+
+ TextureCopy.Copy(
+ gd.Api,
+ cbs.CommandBuffer,
+ srcImage,
+ intermmediate2Image,
+ src.Info,
+ intermmediate2.Info,
+ src.FirstLayer,
+ 0,
+ src.FirstLevel,
+ 0,
+ srcLayer,
+ 0,
+ srcLevel,
+ 0,
+ layers,
+ levels);
+
+ TextureCopy.Copy(
+ gd.Api,
+ cbs.CommandBuffer,
+ intermmediate2Image,
+ intermmediateImage,
+ intermmediate2.Info,
+ intermmediate.Info,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ layers,
+ levels);
+ }
+ else
+ {
+ TextureCopy.Copy(
+ gd.Api,
+ cbs.CommandBuffer,
+ srcImage,
+ intermmediateImage,
+ src.Info,
+ intermmediate.Info,
+ src.FirstLayer,
+ 0,
+ src.FirstLevel,
+ 0,
+ srcLayer,
+ 0,
+ srcLevel,
+ 0,
+ layers,
+ levels);
+ }
+
+ var srcRegion = new Extents2D(0, 0, src.Width, src.Height);
+ var dstRegion = new Extents2D(0, 0, dst.Width, dst.Height);
+
+ TextureCopy.Blit(
+ gd.Api,
+ cbs.CommandBuffer,
+ intermmediateImage,
+ dstImage,
+ intermmediate.Info,
+ dst.Info,
+ srcRegion,
+ dstRegion,
+ 0,
+ dst.FirstLevel + dstLevel,
+ 0,
+ dst.FirstLayer + dstLayer,
+ layers,
+ levels,
+ true,
+ ImageAspectFlags.ImageAspectColorBit,
+ ImageAspectFlags.ImageAspectColorBit);
+ }
+
+ private static TextureView CreateIntermmediateTexture(VulkanRenderer gd, TextureView src, ref TextureCreateInfo formatInfo, Target target, int depth, int levels)
+ {
+ return gd.CreateTextureView(new GAL.TextureCreateInfo(
+ src.Width,
+ src.Height,
+ depth,
+ levels,
+ 1,
+ formatInfo.BlockWidth,
+ formatInfo.BlockHeight,
+ formatInfo.BytesPerPixel,
+ formatInfo.Format,
+ DepthStencilMode.Depth,
+ target,
+ SwizzleComponent.Red,
+ SwizzleComponent.Green,
+ SwizzleComponent.Blue,
+ SwizzleComponent.Alpha), 1f);
+ }
+
+ public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
+ {
+ var dst = (TextureView)destination;
+
+ if (_gd.CommandBufferPool.OwnedByCurrentThread)
+ {
+ _gd.PipelineInternal.EndRenderPass();
+
+ var cbs = _gd.PipelineInternal.CurrentCommandBuffer;
+
+ CopyToImpl(cbs, dst, srcRegion, dstRegion, linearFilter);
+ }
+ else
+ {
+ var cbp = _gd.BackgroundResources.Get().GetPool();
+
+ using var cbs = cbp.Rent();
+
+ CopyToImpl(cbs, dst, srcRegion, dstRegion, linearFilter);
+ }
+ }
+
+ private void CopyToImpl(CommandBufferScoped cbs, TextureView dst, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)
+ {
+ var src = this;
+
+ var srcFormat = GetCompatibleGalFormat(src.Info.Format);
+ var dstFormat = GetCompatibleGalFormat(dst.Info.Format);
+
+ bool srcUsesStorageFormat = src.VkFormat == src.Storage.VkFormat;
+ bool dstUsesStorageFormat = dst.VkFormat == dst.Storage.VkFormat;
+
+ int layers = Math.Min(dst.Info.GetDepthOrLayers(), src.Info.GetDepthOrLayers());
+ int levels = Math.Min(dst.Info.Levels, src.Info.Levels);
+
+ if (srcUsesStorageFormat && dstUsesStorageFormat)
+ {
+ if ((srcRegion.X1 | dstRegion.X1) == 0 &&
+ (srcRegion.Y1 | dstRegion.Y1) == 0 &&
+ srcRegion.X2 == src.Width &&
+ srcRegion.Y2 == src.Height &&
+ dstRegion.X2 == dst.Width &&
+ dstRegion.Y2 == dst.Height &&
+ src.Width == dst.Width &&
+ src.Height == dst.Height &&
+ src.VkFormat == dst.VkFormat)
+ {
+ TextureCopy.Copy(
+ _gd.Api,
+ cbs.CommandBuffer,
+ src.GetImage().Get(cbs).Value,
+ dst.GetImage().Get(cbs).Value,
+ src.Info,
+ dst.Info,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ 0,
+ 0,
+ 0,
+ 0,
+ layers,
+ levels);
+
+ return;
+ }
+ else if (_gd.FormatCapabilities.FormatSupports(FormatFeatureFlags.FormatFeatureBlitSrcBit, srcFormat) &&
+ _gd.FormatCapabilities.FormatSupports(FormatFeatureFlags.FormatFeatureBlitDstBit, dstFormat))
+ {
+ TextureCopy.Blit(
+ _gd.Api,
+ cbs.CommandBuffer,
+ src.GetImage().Get(cbs).Value,
+ dst.GetImage().Get(cbs).Value,
+ src.Info,
+ dst.Info,
+ srcRegion,
+ dstRegion,
+ src.FirstLayer,
+ dst.FirstLayer,
+ src.FirstLevel,
+ dst.FirstLevel,
+ layers,
+ levels,
+ linearFilter);
+
+ return;
+ }
+ else if (srcFormat == GAL.Format.D32FloatS8Uint && srcFormat == dstFormat && SupportsBlitFromD32FS8ToD32FAndS8())
+ {
+ BlitDepthStencilWithBuffer(_gd, cbs, src, dst, srcRegion, dstRegion);
+
+ return;
+ }
+ }
+
+ if (VulkanConfiguration.UseSlowSafeBlitOnAmd &&
+ _gd.Vendor == Vendor.Amd &&
+ src.Info.Target == Target.Texture2D &&
+ dst.Info.Target == Target.Texture2D &&
+ !dst.Info.Format.IsDepthOrStencil())
+ {
+ _gd.HelperShader.Blit(
+ _gd,
+ src,
+ dst.GetIdentityImageView(),
+ dst.Width,
+ dst.Height,
+ dst.VkFormat,
+ srcRegion,
+ dstRegion,
+ linearFilter);
+
+ return;
+ }
+
+ Auto<DisposableImage> srcImage;
+ Auto<DisposableImage> dstImage;
+
+ if (dst.Info.Format.IsDepthOrStencil())
+ {
+ srcImage = src.Storage.CreateAliasedColorForDepthStorageUnsafe(srcFormat).GetImage();
+ dstImage = dst.Storage.CreateAliasedColorForDepthStorageUnsafe(dstFormat).GetImage();
+ }
+ else
+ {
+ srcImage = src.Storage.CreateAliasedStorageUnsafe(srcFormat).GetImage();
+ dstImage = dst.Storage.CreateAliasedStorageUnsafe(dstFormat).GetImage();
+ }
+
+ TextureCopy.Blit(
+ _gd.Api,
+ cbs.CommandBuffer,
+ srcImage.Get(cbs).Value,
+ dstImage.Get(cbs).Value,
+ src.Info,
+ dst.Info,
+ srcRegion,
+ dstRegion,
+ src.FirstLevel,
+ dst.FirstLevel,
+ src.FirstLayer,
+ dst.FirstLayer,
+ layers,
+ levels,
+ linearFilter,
+ ImageAspectFlags.ImageAspectColorBit,
+ ImageAspectFlags.ImageAspectColorBit);
+ }
+
+ private static void BlitDepthStencilWithBuffer(
+ VulkanRenderer gd,
+ CommandBufferScoped cbs,
+ TextureView src,
+ TextureView dst,
+ Extents2D srcRegion,
+ Extents2D dstRegion)
+ {
+ int drBaseX = Math.Min(dstRegion.X1, dstRegion.X2);
+ int drBaseY = Math.Min(dstRegion.Y1, dstRegion.Y2);
+ int drWidth = Math.Abs(dstRegion.X2 - dstRegion.X1);
+ int drHeight = Math.Abs(dstRegion.Y2 - dstRegion.Y1);
+
+ var drOriginZero = new Extents2D(
+ dstRegion.X1 - drBaseX,
+ dstRegion.Y1 - drBaseY,
+ dstRegion.X2 - drBaseX,
+ dstRegion.Y2 - drBaseY);
+
+ var d32SrcStorageInfo = TextureStorage.NewCreateInfoWith(ref src._info, GAL.Format.D32Float, 4);
+ var d32DstStorageInfo = TextureStorage.NewCreateInfoWith(ref dst._info, GAL.Format.D32Float, 4, drWidth, drHeight);
+ var s8SrcStorageInfo = TextureStorage.NewCreateInfoWith(ref src._info, GAL.Format.S8Uint, 1);
+ var s8DstStorageInfo = TextureStorage.NewCreateInfoWith(ref dst._info, GAL.Format.S8Uint, 1, drWidth, drHeight);
+
+ using var d32SrcStorage = gd.CreateTextureStorage(d32SrcStorageInfo, src.Storage.ScaleFactor);
+ using var d32DstStorage = gd.CreateTextureStorage(d32DstStorageInfo, dst.Storage.ScaleFactor);
+ using var s8SrcStorage = gd.CreateTextureStorage(s8SrcStorageInfo, src.Storage.ScaleFactor);
+ using var s8DstStorage = gd.CreateTextureStorage(s8DstStorageInfo, dst.Storage.ScaleFactor);
+
+ void SlowBlit(TextureStorage srcTemp, TextureStorage dstTemp, ImageAspectFlags aspectFlags)
+ {
+ int levels = Math.Min(src.Info.Levels, dst.Info.Levels);
+
+ int srcSize = 0;
+ int dstSize = 0;
+
+ for (int l = 0; l < levels; l++)
+ {
+ srcSize += srcTemp.Info.GetMipSize2D(l);
+ dstSize += dstTemp.Info.GetMipSize2D(l);
+ }
+
+ 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,
+ srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value,
+ src.GetImage().Get(cbs).Value,
+ srcSize,
+ to: true,
+ 0,
+ 0,
+ src.FirstLayer,
+ src.FirstLevel,
+ 1,
+ levels,
+ true,
+ aspectFlags,
+ false);
+
+ BufferHolder.InsertBufferBarrier(
+ gd,
+ cbs.CommandBuffer,
+ srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value,
+ AccessFlags.AccessTransferWriteBit,
+ AccessFlags.AccessTransferReadBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ 0,
+ srcSize);
+
+ srcTemp.CopyFromOrToBuffer(
+ cbs.CommandBuffer,
+ srcTempBuffer.GetBuffer().Get(cbs, 0, srcSize).Value,
+ srcTemp.GetImage().Get(cbs).Value,
+ srcSize,
+ to: false,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ levels,
+ true,
+ aspectFlags,
+ false);
+
+ InsertImageBarrier(
+ gd.Api,
+ cbs.CommandBuffer,
+ srcTemp.GetImage().Get(cbs).Value,
+ AccessFlags.AccessTransferWriteBit,
+ AccessFlags.AccessTransferReadBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ aspectFlags,
+ 0,
+ 0,
+ 1,
+ levels);
+
+ TextureCopy.Blit(
+ gd.Api,
+ cbs.CommandBuffer,
+ srcTemp.GetImage().Get(cbs).Value,
+ dstTemp.GetImage().Get(cbs).Value,
+ srcTemp.Info,
+ dstTemp.Info,
+ srcRegion,
+ drOriginZero,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ levels,
+ false,
+ aspectFlags,
+ aspectFlags);
+
+ InsertImageBarrier(
+ gd.Api,
+ cbs.CommandBuffer,
+ dstTemp.GetImage().Get(cbs).Value,
+ AccessFlags.AccessTransferWriteBit,
+ AccessFlags.AccessTransferReadBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ aspectFlags,
+ 0,
+ 0,
+ 1,
+ levels);
+
+ dstTemp.CopyFromOrToBuffer(
+ cbs.CommandBuffer,
+ dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value,
+ dstTemp.GetImage().Get(cbs).Value,
+ dstSize,
+ to: true,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ levels,
+ true,
+ aspectFlags,
+ false);
+
+ BufferHolder.InsertBufferBarrier(
+ gd,
+ cbs.CommandBuffer,
+ dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value,
+ AccessFlags.AccessTransferWriteBit,
+ AccessFlags.AccessTransferReadBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ PipelineStageFlags.PipelineStageTransferBit,
+ 0,
+ dstSize);
+
+ dst.Storage.CopyFromOrToBuffer(
+ cbs.CommandBuffer,
+ dstTempBuffer.GetBuffer().Get(cbs, 0, dstSize).Value,
+ dst.GetImage().Get(cbs).Value,
+ dstSize,
+ to: false,
+ drBaseX,
+ drBaseY,
+ dst.FirstLayer,
+ dst.FirstLevel,
+ 1,
+ levels,
+ true,
+ aspectFlags,
+ false);
+ }
+
+ SlowBlit(d32SrcStorage, d32DstStorage, ImageAspectFlags.ImageAspectDepthBit);
+ SlowBlit(s8SrcStorage, s8DstStorage, ImageAspectFlags.ImageAspectStencilBit);
+ }
+
+ public static unsafe void InsertImageBarrier(
+ Vk api,
+ CommandBuffer commandBuffer,
+ Image image,
+ AccessFlags srcAccessMask,
+ AccessFlags dstAccessMask,
+ PipelineStageFlags srcStageMask,
+ PipelineStageFlags dstStageMask,
+ ImageAspectFlags aspectFlags,
+ int firstLayer,
+ int firstLevel,
+ int layers,
+ int levels)
+ {
+ ImageMemoryBarrier memoryBarrier = new ImageMemoryBarrier()
+ {
+ SType = StructureType.ImageMemoryBarrier,
+ SrcAccessMask = srcAccessMask,
+ DstAccessMask = dstAccessMask,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Image = image,
+ OldLayout = ImageLayout.General,
+ NewLayout = ImageLayout.General,
+ SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers)
+ };
+
+ api.CmdPipelineBarrier(
+ commandBuffer,
+ srcStageMask,
+ dstStageMask,
+ 0,
+ 0,
+ null,
+ 0,
+ null,
+ 1,
+ memoryBarrier);
+ }
+
+ private bool SupportsBlitFromD32FS8ToD32FAndS8()
+ {
+ var formatFeatureFlags = FormatFeatureFlags.FormatFeatureBlitSrcBit | FormatFeatureFlags.FormatFeatureBlitDstBit;
+ return _gd.FormatCapabilities.FormatSupports(formatFeatureFlags, GAL.Format.D32Float) &&
+ _gd.FormatCapabilities.FormatSupports(formatFeatureFlags, GAL.Format.S8Uint);
+ }
+
+ public TextureView GetView(GAL.Format format)
+ {
+ if (format == Info.Format)
+ {
+ return this;
+ }
+
+ if (_selfManagedViews != null && _selfManagedViews.TryGetValue(format, out var view))
+ {
+ return view;
+ }
+
+ view = CreateViewImpl(new TextureCreateInfo(
+ Info.Width,
+ Info.Height,
+ Info.Depth,
+ Info.Levels,
+ Info.Samples,
+ Info.BlockWidth,
+ Info.BlockHeight,
+ Info.BytesPerPixel,
+ format,
+ Info.DepthStencilMode,
+ Info.Target,
+ Info.SwizzleR,
+ Info.SwizzleG,
+ Info.SwizzleB,
+ Info.SwizzleA), 0, 0);
+
+ (_selfManagedViews ??= new Dictionary<GAL.Format, TextureView>()).Add(format, view);
+
+ return view;
+ }
+
+ public ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ return CreateViewImpl(info, firstLayer, firstLevel);
+ }
+
+ private TextureView CreateViewImpl(TextureCreateInfo info, int firstLayer, int firstLevel)
+ {
+ return new TextureView(_gd, _device, info, Storage, FirstLayer + firstLayer, FirstLevel + firstLevel);
+ }
+
+ public byte[] GetData(int x, int y, int width, int height)
+ {
+ int size = width * height * Info.BytesPerPixel;
+ using var bufferHolder = _gd.BufferManager.Create(_gd, size);
+
+ using (var cbs = _gd.CommandBufferPool.Rent())
+ {
+ var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
+ var image = GetImage().Get(cbs).Value;
+
+ CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, x, y, width, height);
+ }
+
+ bufferHolder.WaitForFences();
+ byte[] bitmap = new byte[size];
+ GetDataFromBuffer(bufferHolder.GetDataStorage(0, size), size, Span<byte>.Empty).CopyTo(bitmap);
+ return bitmap;
+ }
+
+ public ReadOnlySpan<byte> GetData()
+ {
+ BackgroundResource resources = _gd.BackgroundResources.Get();
+
+ if (_gd.CommandBufferPool.OwnedByCurrentThread)
+ {
+ _gd.FlushAllCommands();
+
+ return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer());
+ }
+ else
+ {
+ return GetData(resources.GetPool(), resources.GetFlushBuffer());
+ }
+ }
+
+ public ReadOnlySpan<byte> GetData(int layer, int level)
+ {
+ BackgroundResource resources = _gd.BackgroundResources.Get();
+
+ if (_gd.CommandBufferPool.OwnedByCurrentThread)
+ {
+ _gd.FlushAllCommands();
+
+ return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer(), layer, level);
+ }
+ else
+ {
+ return GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level);
+ }
+ }
+
+ private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer)
+ {
+ int size = 0;
+
+ for (int level = 0; level < Info.Levels; level++)
+ {
+ size += Info.GetMipSize(level);
+ }
+
+ size = GetBufferDataLength(size);
+
+ Span<byte> result = flushBuffer.GetTextureData(cbp, this, size);
+ return GetDataFromBuffer(result, size, result);
+ }
+
+ private ReadOnlySpan<byte> GetData(CommandBufferPool cbp, PersistentFlushBuffer flushBuffer, int layer, int level)
+ {
+ int size = GetBufferDataLength(Info.GetMipSize(level));
+
+ Span<byte> result = flushBuffer.GetTextureData(cbp, this, size, layer, level);
+ return GetDataFromBuffer(result, size, result);
+ }
+
+ public void SetData(ReadOnlySpan<byte> data)
+ {
+ SetData(data, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
+ }
+
+ public void SetData(ReadOnlySpan<byte> data, int layer, int level)
+ {
+ SetData(data, layer, level, 1, 1, singleSlice: true);
+ }
+
+ private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice)
+ {
+ int bufferDataLength = GetBufferDataLength(data.Length);
+
+ using var bufferHolder = _gd.BufferManager.Create(_gd, bufferDataLength);
+
+ Auto<DisposableImage> 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();
+ }
+
+ CopyDataToBuffer(bufferHolder.GetDataStorage(0, bufferDataLength), data);
+
+ var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
+ var image = imageAuto.Get(cbs).Value;
+
+ CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice);
+ }
+
+ private int GetBufferDataLength(int length)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ return length * 2;
+ }
+
+ return length;
+ }
+
+ private GAL.Format GetCompatibleGalFormat(GAL.Format format)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ return GAL.Format.D32FloatS8Uint;
+ }
+
+ return format;
+ }
+
+ private void CopyDataToBuffer(Span<byte> storage, ReadOnlySpan<byte> input)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ FormatConverter.ConvertD24S8ToD32FS8(storage, input);
+ return;
+ }
+
+ input.CopyTo(storage);
+ }
+
+ private ReadOnlySpan<byte> GetDataFromBuffer(ReadOnlySpan<byte> storage, int size, Span<byte> output)
+ {
+ if (NeedsD24S8Conversion())
+ {
+ if (output.IsEmpty)
+ {
+ output = new byte[GetBufferDataLength(size)];
+ }
+
+ FormatConverter.ConvertD32FS8ToD24S8(output, storage);
+ return output;
+ }
+
+ return storage;
+ }
+
+ private bool NeedsD24S8Conversion()
+ {
+ return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
+ }
+
+ public void CopyFromOrToBuffer(
+ CommandBuffer commandBuffer,
+ VkBuffer buffer,
+ Image image,
+ int size,
+ bool to,
+ int dstLayer,
+ int dstLevel,
+ int dstLayers,
+ int dstLevels,
+ bool singleSlice)
+ {
+ bool is3D = Info.Target == Target.Texture3D;
+ int width = Math.Max(1, Info.Width >> dstLevel);
+ int height = Math.Max(1, Info.Height >> dstLevel);
+ int depth = is3D && !singleSlice ? Math.Max(1, Info.Depth >> dstLevel) : 1;
+ int layer = is3D ? 0 : dstLayer;
+ int layers = dstLayers;
+ int levels = dstLevels;
+
+ int offset = 0;
+
+ for (int level = 0; level < levels; level++)
+ {
+ int mipSize = GetBufferDataLength(Info.GetMipSize(dstLevel + level));
+
+ int endOffset = offset + mipSize;
+
+ if ((uint)endOffset > (uint)size)
+ {
+ break;
+ }
+
+ int rowLength = (Info.GetMipStride(dstLevel + level) / Info.BytesPerPixel) * Info.BlockWidth;
+
+ var aspectFlags = Info.Format.ConvertAspectFlags();
+
+ if (aspectFlags == (ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit))
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectDepthBit;
+ }
+
+ var sl = new ImageSubresourceLayers(
+ aspectFlags,
+ (uint)(FirstLevel + dstLevel + level),
+ (uint)(FirstLayer + layer),
+ (uint)layers);
+
+ var extent = new Extent3D((uint)width, (uint)height, (uint)depth);
+
+ int z = is3D ? dstLayer : 0;
+
+ var region = new BufferImageCopy(
+ (ulong)offset,
+ (uint)AlignUpNpot(rowLength, Info.BlockWidth),
+ (uint)AlignUpNpot(height, Info.BlockHeight),
+ sl,
+ new Offset3D(0, 0, z),
+ extent);
+
+ if (to)
+ {
+ _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
+ }
+ else
+ {
+ _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
+ }
+
+ offset += mipSize;
+
+ width = Math.Max(1, width >> 1);
+ height = Math.Max(1, height >> 1);
+
+ if (Info.Target == Target.Texture3D)
+ {
+ depth = Math.Max(1, depth >> 1);
+ }
+ }
+ }
+
+ private void CopyFromOrToBuffer(
+ CommandBuffer commandBuffer,
+ VkBuffer buffer,
+ Image image,
+ int size,
+ bool to,
+ int x,
+ int y,
+ int width,
+ int height)
+ {
+ var aspectFlags = Info.Format.ConvertAspectFlags();
+
+ if (aspectFlags == (ImageAspectFlags.ImageAspectDepthBit | ImageAspectFlags.ImageAspectStencilBit))
+ {
+ aspectFlags = ImageAspectFlags.ImageAspectDepthBit;
+ }
+
+ var sl = new ImageSubresourceLayers(aspectFlags, (uint)FirstLevel, (uint)FirstLayer, 1);
+
+ var extent = new Extent3D((uint)width, (uint)height, 1);
+
+ var region = new BufferImageCopy(
+ 0,
+ (uint)AlignUpNpot(width, Info.BlockWidth),
+ (uint)AlignUpNpot(height, Info.BlockHeight),
+ sl,
+ new Offset3D(x, y, 0),
+ extent);
+
+ if (to)
+ {
+ _gd.Api.CmdCopyImageToBuffer(commandBuffer, image, ImageLayout.General, buffer, 1, region);
+ }
+ else
+ {
+ _gd.Api.CmdCopyBufferToImage(commandBuffer, buffer, image, ImageLayout.General, 1, region);
+ }
+ }
+
+ private static int AlignUpNpot(int size, int alignment)
+ {
+ int remainder = size % alignment;
+ if (remainder == 0)
+ {
+ return size;
+ }
+
+ return size + (alignment - remainder);
+ }
+
+ public void SetStorage(BufferRange buffer)
+ {
+ throw new NotImplementedException();
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Valid = false;
+
+ if (_gd.Textures.Remove(this))
+ {
+ _imageView.Dispose();
+ _imageViewIdentity.Dispose();
+ _imageView2dArray?.Dispose();
+
+ Storage.DecrementViewsCount();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_selfManagedViews != null)
+ {
+ foreach (var view in _selfManagedViews.Values)
+ {
+ view.Dispose();
+ }
+
+ _selfManagedViews = null;
+ }
+
+ Dispose(true);
+ }
+
+ public void Release()
+ {
+ Dispose();
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Vendor.cs b/Ryujinx.Graphics.Vulkan/Vendor.cs
new file mode 100644
index 00000000..f06211ca
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Vendor.cs
@@ -0,0 +1,51 @@
+using System.Text.RegularExpressions;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ enum Vendor
+ {
+ Amd,
+ Intel,
+ Nvidia,
+ Qualcomm,
+ Unknown
+ }
+
+ static class VendorUtils
+ {
+ public static Regex AmdGcnRegex = new Regex(@"Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\d{2}(\D|$))|([7-8]\d{3}(\D|$))|Fury|Nano))|(Pro Duo)");
+
+ public static Vendor FromId(uint id)
+ {
+ return id switch
+ {
+ 0x1002 => Vendor.Amd,
+ 0x10DE => Vendor.Nvidia,
+ 0x8086 => Vendor.Intel,
+ 0x5143 => Vendor.Qualcomm,
+ _ => Vendor.Unknown
+ };
+ }
+
+ public static string GetNameFromId(uint id)
+ {
+ return id switch
+ {
+ 0x1002 => "AMD",
+ 0x1010 => "ImgTec",
+ 0x10DE => "NVIDIA",
+ 0x13B5 => "ARM",
+ 0x1AE0 => "Google",
+ 0x5143 => "Qualcomm",
+ 0x8086 => "Intel",
+ 0x10001 => "Vivante",
+ 0x10002 => "VeriSilicon",
+ 0x10003 => "Kazan",
+ 0x10004 => "Codeplay Software Ltd.",
+ 0x10005 => "Mesa",
+ 0x10006 => "PoCL",
+ _ => $"0x{id:X}"
+ };
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs b/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs
new file mode 100644
index 00000000..75b45809
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class VulkanConfiguration
+ {
+ public const bool UseFastBufferUpdates = true;
+ public const bool UseSlowSafeBlitOnAmd = true;
+ public const bool UsePushDescriptors = false;
+
+ public const bool ForceD24S8Unsupported = false;
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/VulkanException.cs b/Ryujinx.Graphics.Vulkan/VulkanException.cs
new file mode 100644
index 00000000..983f03d4
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/VulkanException.cs
@@ -0,0 +1,41 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Runtime.Serialization;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ static class ResultExtensions
+ {
+ public static void ThrowOnError(this Result result)
+ {
+ // Only negative result codes are errors.
+ if ((int)result < (int)Result.Success)
+ {
+ throw new VulkanException(result);
+ }
+ }
+ }
+
+ class VulkanException : Exception
+ {
+ public VulkanException()
+ {
+ }
+
+ public VulkanException(Result result) : base($"Unexpected API error \"{result}\".")
+ {
+ }
+
+ public VulkanException(string message) : base(message)
+ {
+ }
+
+ public VulkanException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected VulkanException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
new file mode 100644
index 00000000..889ce7e2
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
@@ -0,0 +1,596 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.EXT;
+using Silk.NET.Vulkan.Extensions.KHR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ public unsafe static class VulkanInitialization
+ {
+ private const uint InvalidIndex = uint.MaxValue;
+ private const string AppName = "Ryujinx.Graphics.Vulkan";
+ private const int QueuesCount = 2;
+
+ public static string[] DesirableExtensions { get; } = new string[]
+ {
+ ExtConditionalRendering.ExtensionName,
+ ExtExtendedDynamicState.ExtensionName,
+ KhrDrawIndirectCount.ExtensionName,
+ KhrPushDescriptor.ExtensionName,
+ "VK_EXT_custom_border_color",
+ "VK_EXT_descriptor_indexing", // Enabling this works around an issue with disposed buffer bindings on RADV.
+ "VK_EXT_fragment_shader_interlock",
+ "VK_EXT_index_type_uint8",
+ "VK_EXT_robustness2",
+ "VK_EXT_shader_subgroup_ballot",
+ "VK_EXT_subgroup_size_control",
+ "VK_NV_geometry_shader_passthrough"
+ };
+
+ public static string[] RequiredExtensions { get; } = new string[]
+ {
+ KhrSwapchain.ExtensionName,
+ "VK_EXT_shader_subgroup_vote",
+ ExtTransformFeedback.ExtensionName
+ };
+
+ private static string[] _excludedMessages = new string[]
+ {
+ // NOTE: Done on purpose right now.
+ "UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
+ // TODO: Figure out if fixable
+ "VUID-vkCmdDrawIndexed-None-04584",
+ // TODO: Might be worth looking into making this happy to possibly optimize copies.
+ "UNASSIGNED-CoreValidation-DrawState-InvalidImageLayout",
+ // TODO: Fix this, it's causing too much noise right now.
+ "VUID-VkSubpassDependency-srcSubpass-00867"
+ };
+
+ internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback)
+ {
+ var enabledLayers = new List<string>();
+
+ void AddAvailableLayer(string layerName)
+ {
+ uint layerPropertiesCount;
+
+ api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
+
+ LayerProperties[] layerProperties = new LayerProperties[layerPropertiesCount];
+
+ fixed (LayerProperties* pLayerProperties = layerProperties)
+ {
+ api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
+
+ for (int i = 0; i < layerPropertiesCount; i++)
+ {
+ string currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
+
+ if (currentLayerName == layerName)
+ {
+ enabledLayers.Add(layerName);
+ return;
+ }
+ }
+ }
+
+ Logger.Warning?.Print(LogClass.Gpu, $"Missing layer {layerName}");
+ }
+
+ if (logLevel != GraphicsDebugLevel.None)
+ {
+ AddAvailableLayer("VK_LAYER_KHRONOS_validation");
+ }
+
+ var enabledExtensions = requiredExtensions.Append(ExtDebugReport.ExtensionName).ToArray();
+
+ var appName = Marshal.StringToHGlobalAnsi(AppName);
+
+ var applicationInfo = new ApplicationInfo
+ {
+ PApplicationName = (byte*)appName,
+ ApplicationVersion = 1,
+ PEngineName = (byte*)appName,
+ EngineVersion = 1,
+ ApiVersion = Vk.Version12.Value
+ };
+
+ IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
+ IntPtr* ppEnabledLayers = stackalloc IntPtr[enabledLayers.Count];
+
+ for (int i = 0; i < enabledExtensions.Length; i++)
+ {
+ ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
+ }
+
+ for (int i = 0; i < enabledLayers.Count; i++)
+ {
+ ppEnabledLayers[i] = Marshal.StringToHGlobalAnsi(enabledLayers[i]);
+ }
+
+ var instanceCreateInfo = new InstanceCreateInfo
+ {
+ SType = StructureType.InstanceCreateInfo,
+ PApplicationInfo = &applicationInfo,
+ PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
+ PpEnabledLayerNames = (byte**)ppEnabledLayers,
+ EnabledExtensionCount = (uint)enabledExtensions.Length,
+ EnabledLayerCount = (uint)enabledLayers.Count
+ };
+
+ api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
+
+ Marshal.FreeHGlobal(appName);
+
+ for (int i = 0; i < enabledExtensions.Length; i++)
+ {
+ Marshal.FreeHGlobal(ppEnabledExtensions[i]);
+ }
+
+ for (int i = 0; i < enabledLayers.Count; i++)
+ {
+ Marshal.FreeHGlobal(ppEnabledLayers[i]);
+ }
+
+ CreateDebugCallbacks(api, logLevel, instance, out debugReport, out debugReportCallback);
+
+ return instance;
+ }
+
+ private unsafe static uint DebugReport(
+ uint flags,
+ DebugReportObjectTypeEXT objectType,
+ ulong @object,
+ nuint location,
+ int messageCode,
+ byte* layerPrefix,
+ byte* message,
+ void* userData)
+ {
+ var msg = Marshal.PtrToStringAnsi((IntPtr)message);
+
+ foreach (string excludedMessagePart in _excludedMessages)
+ {
+ if (msg.Contains(excludedMessagePart))
+ {
+ return 0;
+ }
+ }
+
+ DebugReportFlagsEXT debugFlags = (DebugReportFlagsEXT)flags;
+
+ if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportErrorBitExt))
+ {
+ Logger.Error?.Print(LogClass.Gpu, msg);
+ //throw new Exception(msg);
+ }
+ else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportWarningBitExt))
+ {
+ Logger.Warning?.Print(LogClass.Gpu, msg);
+ }
+ else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportInformationBitExt))
+ {
+ Logger.Info?.Print(LogClass.Gpu, msg);
+ }
+ else if (debugFlags.HasFlag(DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt))
+ {
+ Logger.Warning?.Print(LogClass.Gpu, msg);
+ }
+ else
+ {
+ Logger.Debug?.Print(LogClass.Gpu, msg);
+ }
+
+ return 0;
+ }
+
+ internal static PhysicalDevice FindSuitablePhysicalDevice(Vk api, Instance instance, SurfaceKHR surface, string preferredGpuId)
+ {
+ uint physicalDeviceCount;
+
+ api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
+
+ PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
+
+ fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
+ {
+ api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
+ }
+
+ // First we try to pick the the user preferred GPU.
+ for (int i = 0; i < physicalDevices.Length; i++)
+ {
+ if (IsPreferredAndSuitableDevice(api, physicalDevices[i], surface, preferredGpuId))
+ {
+ return physicalDevices[i];
+ }
+ }
+
+ // If we fail to do that, just use the first compatible GPU.
+ for (int i = 0; i < physicalDevices.Length; i++)
+ {
+ if (IsSuitableDevice(api, physicalDevices[i], surface))
+ {
+ return physicalDevices[i];
+ }
+ }
+
+ throw new VulkanException("Initialization failed, none of the available GPUs meets the minimum requirements.");
+ }
+
+ internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
+ {
+ var appName = Marshal.StringToHGlobalAnsi(AppName);
+
+ var applicationInfo = new ApplicationInfo
+ {
+ PApplicationName = (byte*)appName,
+ ApplicationVersion = 1,
+ PEngineName = (byte*)appName,
+ EngineVersion = 1,
+ ApiVersion = Vk.Version12.Value
+ };
+
+ var instanceCreateInfo = new InstanceCreateInfo
+ {
+ SType = StructureType.InstanceCreateInfo,
+ PApplicationInfo = &applicationInfo,
+ PpEnabledExtensionNames = null,
+ PpEnabledLayerNames = null,
+ EnabledExtensionCount = 0,
+ EnabledLayerCount = 0
+ };
+
+ api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError();
+
+ Marshal.FreeHGlobal(appName);
+
+ uint physicalDeviceCount;
+
+ api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, null).ThrowOnError();
+
+ PhysicalDevice[] physicalDevices = new PhysicalDevice[physicalDeviceCount];
+
+ fixed (PhysicalDevice* pPhysicalDevices = physicalDevices)
+ {
+ api.EnumeratePhysicalDevices(instance, &physicalDeviceCount, pPhysicalDevices).ThrowOnError();
+ }
+
+ DeviceInfo[] devices = new DeviceInfo[physicalDevices.Length];
+
+ for (int i = 0; i < physicalDevices.Length; i++)
+ {
+ var physicalDevice = physicalDevices[i];
+ api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
+
+ devices[i] = new DeviceInfo(
+ StringFromIdPair(properties.VendorID, properties.DeviceID),
+ VendorUtils.GetNameFromId(properties.VendorID),
+ Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName),
+ properties.DeviceType == PhysicalDeviceType.DiscreteGpu);
+ }
+
+ api.DestroyInstance(instance, null);
+
+ return devices;
+ }
+
+ public static string StringFromIdPair(uint vendorId, uint deviceId)
+ {
+ return $"0x{vendorId:X}_0x{deviceId:X}";
+ }
+
+ private static bool IsPreferredAndSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, string preferredGpuId)
+ {
+ api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
+
+ if (StringFromIdPair(properties.VendorID, properties.DeviceID) != preferredGpuId)
+ {
+ return false;
+ }
+
+ return IsSuitableDevice(api, physicalDevice, surface);
+ }
+
+ private static bool IsSuitableDevice(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface)
+ {
+ int extensionMatches = 0;
+ uint propertiesCount;
+
+ api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
+
+ ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
+
+ fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
+ {
+ api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
+
+ for (int i = 0; i < propertiesCount; i++)
+ {
+ string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
+
+ if (RequiredExtensions.Contains(extensionName))
+ {
+ extensionMatches++;
+ }
+ }
+ }
+
+ return extensionMatches == RequiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
+ }
+
+ internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
+ {
+ const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit;
+
+ var khrSurface = new KhrSurface(api.Context);
+
+ uint propertiesCount;
+
+ api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, null);
+
+ QueueFamilyProperties[] properties = new QueueFamilyProperties[propertiesCount];
+
+ fixed (QueueFamilyProperties* pProperties = properties)
+ {
+ api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, pProperties);
+ }
+
+ for (uint index = 0; index < propertiesCount; index++)
+ {
+ var queueFlags = properties[index].QueueFlags;
+
+ khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice, index, surface, out var surfaceSupported).ThrowOnError();
+
+ if (queueFlags.HasFlag(RequiredFlags) && surfaceSupported)
+ {
+ queueCount = properties[index].QueueCount;
+ return index;
+ }
+ }
+
+ queueCount = 0;
+ return InvalidIndex;
+ }
+
+ public static Device CreateDevice(Vk api, PhysicalDevice physicalDevice, uint queueFamilyIndex, string[] supportedExtensions, uint queueCount)
+ {
+ if (queueCount > QueuesCount)
+ {
+ queueCount = QueuesCount;
+ }
+
+ float* queuePriorities = stackalloc float[(int)queueCount];
+
+ for (int i = 0; i < queueCount; i++)
+ {
+ queuePriorities[i] = 1f;
+ }
+
+ var queueCreateInfo = new DeviceQueueCreateInfo()
+ {
+ SType = StructureType.DeviceQueueCreateInfo,
+ QueueFamilyIndex = queueFamilyIndex,
+ QueueCount = queueCount,
+ PQueuePriorities = queuePriorities
+ };
+
+ api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
+ bool useRobustBufferAccess = VendorUtils.FromId(properties.VendorID) == Vendor.Nvidia;
+
+ var supportedFeatures = api.GetPhysicalDeviceFeature(physicalDevice);
+
+ var features = new PhysicalDeviceFeatures()
+ {
+ DepthBiasClamp = true,
+ DepthClamp = true,
+ DualSrcBlend = true,
+ FragmentStoresAndAtomics = true,
+ GeometryShader = true,
+ ImageCubeArray = true,
+ IndependentBlend = true,
+ LogicOp = true,
+ MultiViewport = true,
+ PipelineStatisticsQuery = true,
+ SamplerAnisotropy = true,
+ ShaderClipDistance = true,
+ ShaderFloat64 = supportedFeatures.ShaderFloat64,
+ ShaderImageGatherExtended = true,
+ // ShaderStorageImageReadWithoutFormat = true,
+ // ShaderStorageImageWriteWithoutFormat = true,
+ TessellationShader = true,
+ VertexPipelineStoresAndAtomics = true,
+ RobustBufferAccess = useRobustBufferAccess
+ };
+
+ void* pExtendedFeatures = null;
+
+ var featuresTransformFeedback = new PhysicalDeviceTransformFeedbackFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceTransformFeedbackFeaturesExt,
+ PNext = pExtendedFeatures,
+ TransformFeedback = true
+ };
+
+ pExtendedFeatures = &featuresTransformFeedback;
+
+ var featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
+ PNext = pExtendedFeatures,
+ NullDescriptor = true
+ };
+
+ pExtendedFeatures = &featuresRobustness2;
+
+ var featuresExtendedDynamicState = new PhysicalDeviceExtendedDynamicStateFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceExtendedDynamicStateFeaturesExt,
+ PNext = pExtendedFeatures,
+ ExtendedDynamicState = supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName)
+ };
+
+ pExtendedFeatures = &featuresExtendedDynamicState;
+
+ var featuresVk11 = new PhysicalDeviceVulkan11Features()
+ {
+ SType = StructureType.PhysicalDeviceVulkan11Features,
+ PNext = pExtendedFeatures,
+ ShaderDrawParameters = true
+ };
+
+ pExtendedFeatures = &featuresVk11;
+
+ var featuresVk12 = new PhysicalDeviceVulkan12Features()
+ {
+ SType = StructureType.PhysicalDeviceVulkan12Features,
+ PNext = pExtendedFeatures,
+ DescriptorIndexing = supportedExtensions.Contains("VK_EXT_descriptor_indexing"),
+ DrawIndirectCount = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName)
+ };
+
+ pExtendedFeatures = &featuresVk12;
+
+ PhysicalDeviceIndexTypeUint8FeaturesEXT featuresIndexU8;
+
+ if (supportedExtensions.Contains("VK_EXT_index_type_uint8"))
+ {
+ featuresIndexU8 = new PhysicalDeviceIndexTypeUint8FeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceIndexTypeUint8FeaturesExt,
+ PNext = pExtendedFeatures,
+ IndexTypeUint8 = true
+ };
+
+ pExtendedFeatures = &featuresIndexU8;
+ }
+
+ PhysicalDeviceFragmentShaderInterlockFeaturesEXT featuresFragmentShaderInterlock;
+
+ if (supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"))
+ {
+ featuresFragmentShaderInterlock = new PhysicalDeviceFragmentShaderInterlockFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceFragmentShaderInterlockFeaturesExt,
+ PNext = pExtendedFeatures,
+ FragmentShaderPixelInterlock = true
+ };
+
+ pExtendedFeatures = &featuresFragmentShaderInterlock;
+ }
+
+ PhysicalDeviceSubgroupSizeControlFeaturesEXT featuresSubgroupSizeControl;
+
+ if (supportedExtensions.Contains("VK_EXT_subgroup_size_control"))
+ {
+ featuresSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlFeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceSubgroupSizeControlFeaturesExt,
+ PNext = pExtendedFeatures,
+ SubgroupSizeControl = true
+ };
+
+ pExtendedFeatures = &featuresSubgroupSizeControl;
+ }
+
+ var enabledExtensions = RequiredExtensions.Union(DesirableExtensions.Intersect(supportedExtensions)).ToArray();
+
+ IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
+
+ for (int i = 0; i < enabledExtensions.Length; i++)
+ {
+ ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]);
+ }
+
+ var deviceCreateInfo = new DeviceCreateInfo()
+ {
+ SType = StructureType.DeviceCreateInfo,
+ PNext = pExtendedFeatures,
+ QueueCreateInfoCount = 1,
+ PQueueCreateInfos = &queueCreateInfo,
+ PpEnabledExtensionNames = (byte**)ppEnabledExtensions,
+ EnabledExtensionCount = (uint)enabledExtensions.Length,
+ PEnabledFeatures = &features
+ };
+
+ api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out var device).ThrowOnError();
+
+ for (int i = 0; i < enabledExtensions.Length; i++)
+ {
+ Marshal.FreeHGlobal(ppEnabledExtensions[i]);
+ }
+
+ return device;
+ }
+
+ public static string[] GetSupportedExtensions(Vk api, PhysicalDevice physicalDevice)
+ {
+ uint propertiesCount;
+
+ api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError();
+
+ ExtensionProperties[] extensionProperties = new ExtensionProperties[propertiesCount];
+
+ fixed (ExtensionProperties* pExtensionProperties = extensionProperties)
+ {
+ api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, pExtensionProperties).ThrowOnError();
+ }
+
+ return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
+ }
+
+ internal static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex)
+ {
+ return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
+ }
+
+ internal unsafe static void CreateDebugCallbacks(
+ Vk api,
+ GraphicsDebugLevel logLevel,
+ Instance instance,
+ out ExtDebugReport debugReport,
+ out DebugReportCallbackEXT debugReportCallback)
+ {
+ debugReport = default;
+
+ if (logLevel != GraphicsDebugLevel.None)
+ {
+ if (!api.TryGetInstanceExtension(instance, out debugReport))
+ {
+ debugReportCallback = default;
+ return;
+ }
+
+ var flags = logLevel switch
+ {
+ GraphicsDebugLevel.Error => DebugReportFlagsEXT.DebugReportErrorBitExt,
+ GraphicsDebugLevel.Slowdowns => DebugReportFlagsEXT.DebugReportErrorBitExt | DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt,
+ GraphicsDebugLevel.All => DebugReportFlagsEXT.DebugReportInformationBitExt |
+ DebugReportFlagsEXT.DebugReportWarningBitExt |
+ DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt |
+ DebugReportFlagsEXT.DebugReportErrorBitExt |
+ DebugReportFlagsEXT.DebugReportDebugBitExt,
+ _ => throw new ArgumentException($"Invalid log level \"{logLevel}\".")
+ };
+ var debugReportCallbackCreateInfo = new DebugReportCallbackCreateInfoEXT()
+ {
+ SType = StructureType.DebugReportCallbackCreateInfoExt,
+ Flags = flags,
+ PfnCallback = new PfnDebugReportCallbackEXT(DebugReport)
+ };
+
+ debugReport.CreateDebugReportCallback(instance, in debugReportCallbackCreateInfo, null, out debugReportCallback).ThrowOnError();
+ }
+ else
+ {
+ debugReportCallback = default;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
new file mode 100644
index 00000000..c7396630
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -0,0 +1,609 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+using Ryujinx.Graphics.Vulkan.Queries;
+using Silk.NET.Vulkan;
+using Silk.NET.Vulkan.Extensions.EXT;
+using Silk.NET.Vulkan.Extensions.KHR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ public sealed class VulkanRenderer : IRenderer
+ {
+ private Instance _instance;
+ private SurfaceKHR _surface;
+ private PhysicalDevice _physicalDevice;
+ private Device _device;
+ private uint _queueFamilyIndex;
+ private WindowBase _window;
+
+ internal FormatCapabilities FormatCapabilities { get; private set; }
+ internal HardwareCapabilities Capabilities;
+
+ internal Vk Api { get; private set; }
+ internal KhrSurface SurfaceApi { get; private set; }
+ internal KhrSwapchain SwapchainApi { get; private set; }
+ internal ExtConditionalRendering ConditionalRenderingApi { get; private set; }
+ internal ExtExtendedDynamicState ExtendedDynamicStateApi { get; private set; }
+ internal KhrPushDescriptor PushDescriptorApi { get; private set; }
+ internal ExtTransformFeedback TransformFeedbackApi { get; private set; }
+ internal KhrDrawIndirectCount DrawIndirectCountApi { get; private set; }
+ internal ExtDebugReport DebugReportApi { get; private set; }
+
+ internal uint QueueFamilyIndex { get; private set; }
+ public bool IsOffScreen { get; }
+ internal Queue Queue { get; private set; }
+ internal Queue BackgroundQueue { get; private set; }
+ internal object BackgroundQueueLock { get; private set; }
+ internal object QueueLock { get; private set; }
+
+ internal MemoryAllocator MemoryAllocator { get; private set; }
+ 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; }
+
+ internal HashSet<ShaderCollection> Shaders { get; }
+ internal HashSet<ITexture> Textures { get; }
+ internal HashSet<SamplerHolder> Samplers { get; }
+
+ private Counters _counters;
+ private SyncManager _syncManager;
+
+ private PipelineFull _pipeline;
+ private DebugReportCallbackEXT _debugReportCallback;
+
+ internal HelperShader HelperShader { get; private set; }
+ internal PipelineFull PipelineInternal => _pipeline;
+
+ public IPipeline Pipeline => _pipeline;
+
+ public IWindow Window => _window;
+
+ private readonly Func<Instance, Vk, SurfaceKHR> _getSurface;
+ private readonly Func<string[]> _getRequiredExtensions;
+ private readonly string _preferredGpuId;
+
+ internal Vendor Vendor { get; private set; }
+ internal bool IsAmdWindows { get; private set; }
+ internal bool IsIntelWindows { get; private set; }
+ internal bool IsAmdGcn { get; private set; }
+ public string GpuVendor { get; private set; }
+ public string GpuRenderer { get; private set; }
+ public string GpuVersion { get; private set; }
+
+ public bool PreferThreading => true;
+
+ public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
+
+ public VulkanRenderer(Func<Instance, Vk, SurfaceKHR> surfaceFunc, Func<string[]> requiredExtensionsFunc, string preferredGpuId)
+ {
+ _getSurface = surfaceFunc;
+ _getRequiredExtensions = requiredExtensionsFunc;
+ _preferredGpuId = preferredGpuId;
+ Shaders = new HashSet<ShaderCollection>();
+ Textures = new HashSet<ITexture>();
+ Samplers = new HashSet<SamplerHolder>();
+ }
+
+ public VulkanRenderer(Instance instance, Device device, PhysicalDevice physicalDevice, Queue queue, uint queueFamilyIndex, object lockObject)
+ {
+ _instance = instance;
+ _physicalDevice = physicalDevice;
+ _device = device;
+ _queueFamilyIndex = queueFamilyIndex;
+
+ Queue = queue;
+ QueueLock = lockObject;
+
+ IsOffScreen = true;
+ Shaders = new HashSet<ShaderCollection>();
+ Textures = new HashSet<ITexture>();
+ Samplers = new HashSet<SamplerHolder>();
+ }
+
+ private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
+ {
+ FormatCapabilities = new FormatCapabilities(Api, _physicalDevice);
+
+ var supportedFeatures = Api.GetPhysicalDeviceFeature(_physicalDevice);
+
+ if (Api.TryGetDeviceExtension(_instance, _device, out ExtConditionalRendering conditionalRenderingApi))
+ {
+ ConditionalRenderingApi = conditionalRenderingApi;
+ }
+
+ if (Api.TryGetDeviceExtension(_instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi))
+ {
+ ExtendedDynamicStateApi = extendedDynamicStateApi;
+ }
+
+ if (Api.TryGetDeviceExtension(_instance, _device, out KhrPushDescriptor pushDescriptorApi))
+ {
+ PushDescriptorApi = pushDescriptorApi;
+ }
+
+ if (Api.TryGetDeviceExtension(_instance, _device, out ExtTransformFeedback transformFeedbackApi))
+ {
+ TransformFeedbackApi = transformFeedbackApi;
+ }
+
+ if (Api.TryGetDeviceExtension(_instance, _device, out KhrDrawIndirectCount drawIndirectCountApi))
+ {
+ DrawIndirectCountApi = drawIndirectCountApi;
+ }
+
+ if (maxQueueCount >= 2)
+ {
+ Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
+ BackgroundQueue = backgroundQueue;
+ BackgroundQueueLock = new object();
+ }
+
+ PhysicalDeviceProperties2 properties2 = new PhysicalDeviceProperties2()
+ {
+ SType = StructureType.PhysicalDeviceProperties2
+ };
+
+ PhysicalDeviceSubgroupSizeControlPropertiesEXT propertiesSubgroupSizeControl = new PhysicalDeviceSubgroupSizeControlPropertiesEXT()
+ {
+ SType = StructureType.PhysicalDeviceSubgroupSizeControlPropertiesExt
+ };
+
+ if (Capabilities.SupportsSubgroupSizeControl)
+ {
+ properties2.PNext = &propertiesSubgroupSizeControl;
+ }
+
+ bool supportsTransformFeedback = supportedExtensions.Contains(ExtTransformFeedback.ExtensionName);
+
+ PhysicalDeviceTransformFeedbackPropertiesEXT propertiesTransformFeedback = new PhysicalDeviceTransformFeedbackPropertiesEXT()
+ {
+ SType = StructureType.PhysicalDeviceTransformFeedbackPropertiesExt
+ };
+
+ if (supportsTransformFeedback)
+ {
+ propertiesTransformFeedback.PNext = properties2.PNext;
+ properties2.PNext = &propertiesTransformFeedback;
+ }
+
+ Api.GetPhysicalDeviceProperties2(_physicalDevice, &properties2);
+
+ PhysicalDeviceFeatures2 features2 = new PhysicalDeviceFeatures2()
+ {
+ SType = StructureType.PhysicalDeviceFeatures2
+ };
+
+ PhysicalDeviceRobustness2FeaturesEXT featuresRobustness2 = new PhysicalDeviceRobustness2FeaturesEXT()
+ {
+ SType = StructureType.PhysicalDeviceRobustness2FeaturesExt
+ };
+
+ if (supportedExtensions.Contains("VK_EXT_robustness2"))
+ {
+ features2.PNext = &featuresRobustness2;
+ }
+
+ Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2);
+
+ Capabilities = new HardwareCapabilities(
+ supportedExtensions.Contains("VK_EXT_index_type_uint8"),
+ supportedExtensions.Contains("VK_EXT_custom_border_color"),
+ supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName),
+ supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"),
+ supportedExtensions.Contains("VK_NV_geometry_shader_passthrough"),
+ supportedExtensions.Contains("VK_EXT_subgroup_size_control"),
+ supportedExtensions.Contains(ExtConditionalRendering.ExtensionName),
+ supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName),
+ features2.Features.MultiViewport,
+ featuresRobustness2.NullDescriptor,
+ supportedExtensions.Contains(KhrPushDescriptor.ExtensionName),
+ supportsTransformFeedback,
+ propertiesTransformFeedback.TransformFeedbackQueries,
+ supportedFeatures.GeometryShader,
+ propertiesSubgroupSizeControl.MinSubgroupSize,
+ propertiesSubgroupSizeControl.MaxSubgroupSize,
+ propertiesSubgroupSizeControl.RequiredSubgroupSizeStages);
+
+ ref var properties = ref properties2.Properties;
+
+ MemoryAllocator = new MemoryAllocator(Api, _device, properties.Limits.MaxMemoryAllocationCount);
+
+ CommandBufferPool = VulkanInitialization.CreateCommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
+
+ DescriptorSetManager = new DescriptorSetManager(_device);
+
+ PipelineLayoutCache = new PipelineLayoutCache();
+
+ BackgroundResources = new BackgroundResources(this, _device);
+
+ BufferManager = new BufferManager(this, _physicalDevice, _device);
+
+ _syncManager = new SyncManager(this, _device);
+ _pipeline = new PipelineFull(this, _device);
+ _pipeline.Initialize();
+
+ HelperShader = new HelperShader(this, _device);
+
+ _counters = new Counters(this, _device, _pipeline);
+ }
+
+ private unsafe void SetupContext(GraphicsDebugLevel logLevel)
+ {
+ var api = Vk.GetApi();
+
+ Api = api;
+
+ _instance = VulkanInitialization.CreateInstance(api, logLevel, _getRequiredExtensions(), out ExtDebugReport debugReport, out _debugReportCallback);
+
+ DebugReportApi = debugReport;
+
+ if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
+ {
+ SurfaceApi = surfaceApi;
+ }
+
+ _surface = _getSurface(_instance, api);
+ _physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(api, _instance, _surface, _preferredGpuId);
+
+ var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(api, _physicalDevice, _surface, out uint maxQueueCount);
+ var supportedExtensions = VulkanInitialization.GetSupportedExtensions(api, _physicalDevice);
+
+ _device = VulkanInitialization.CreateDevice(api, _physicalDevice, queueFamilyIndex, supportedExtensions, maxQueueCount);
+
+ if (api.TryGetDeviceExtension(_instance, _device, out KhrSwapchain swapchainApi))
+ {
+ SwapchainApi = swapchainApi;
+ }
+
+ api.GetDeviceQueue(_device, queueFamilyIndex, 0, out var queue);
+ Queue = queue;
+ QueueLock = new object();
+
+ LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex);
+
+ _window = new Window(this, _surface, _physicalDevice, _device);
+ }
+
+ private unsafe void SetupOffScreenContext(GraphicsDebugLevel logLevel)
+ {
+ var api = Vk.GetApi();
+
+ Api = api;
+
+ VulkanInitialization.CreateDebugCallbacks(api, logLevel, _instance, out var debugReport, out _debugReportCallback);
+
+ DebugReportApi = debugReport;
+
+ var supportedExtensions = VulkanInitialization.GetSupportedExtensions(api, _physicalDevice);
+
+ uint propertiesCount;
+
+ api.GetPhysicalDeviceQueueFamilyProperties(_physicalDevice, &propertiesCount, null);
+
+ QueueFamilyProperties[] queueFamilyProperties = new QueueFamilyProperties[propertiesCount];
+
+ fixed (QueueFamilyProperties* pProperties = queueFamilyProperties)
+ {
+ api.GetPhysicalDeviceQueueFamilyProperties(_physicalDevice, &propertiesCount, pProperties);
+ }
+
+ LoadFeatures(supportedExtensions, queueFamilyProperties[0].QueueCount, _queueFamilyIndex);
+
+ _window = new ImageWindow(this, _physicalDevice, _device);
+ }
+
+ public BufferHandle CreateBuffer(int size)
+ {
+ return BufferManager.CreateWithHandle(this, size, false);
+ }
+
+ public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
+ {
+ bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
+
+ if (info.State.HasValue || isCompute)
+ {
+ return new ShaderCollection(this, _device, sources, info.State ?? default, info.FromCache);
+ }
+ else
+ {
+ return new ShaderCollection(this, _device, sources);
+ }
+ }
+
+ internal ShaderCollection CreateProgramWithMinimalLayout(ShaderSource[] sources)
+ {
+ return new ShaderCollection(this, _device, sources, isMinimal: true);
+ }
+
+ public ISampler CreateSampler(GAL.SamplerCreateInfo info)
+ {
+ return new SamplerHolder(this, _device, info);
+ }
+
+ public ITexture CreateTexture(TextureCreateInfo info, float scale)
+ {
+ if (info.Target == Target.TextureBuffer)
+ {
+ return new TextureBuffer(this, info, scale);
+ }
+
+ return CreateTextureView(info, scale);
+ }
+
+ internal TextureView CreateTextureView(TextureCreateInfo info, float scale)
+ {
+ // This should be disposed when all views are destroyed.
+ using var storage = CreateTextureStorage(info, scale);
+ return storage.CreateView(info, 0, 0);
+ }
+
+ internal TextureStorage CreateTextureStorage(TextureCreateInfo info, float scale)
+ {
+ return new TextureStorage(this, _physicalDevice, _device, info, scale);
+ }
+
+ public void DeleteBuffer(BufferHandle buffer)
+ {
+ BufferManager.Delete(buffer);
+ }
+
+ internal void FlushAllCommands()
+ {
+ _pipeline?.FlushCommandsImpl();
+ }
+
+ public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
+ {
+ return BufferManager.GetData(buffer, offset, size);
+ }
+
+ public Capabilities GetCapabilities()
+ {
+ FormatFeatureFlags compressedFormatFeatureFlags =
+ FormatFeatureFlags.FormatFeatureSampledImageBit |
+ FormatFeatureFlags.FormatFeatureSampledImageFilterLinearBit |
+ FormatFeatureFlags.FormatFeatureBlitSrcBit |
+ FormatFeatureFlags.FormatFeatureTransferSrcBit |
+ FormatFeatureFlags.FormatFeatureTransferDstBit;
+
+ bool supportsBc123CompressionFormat = FormatCapabilities.FormatsSupports(compressedFormatFeatureFlags,
+ GAL.Format.Bc1RgbaSrgb,
+ GAL.Format.Bc1RgbaUnorm,
+ GAL.Format.Bc2Srgb,
+ GAL.Format.Bc2Unorm,
+ GAL.Format.Bc3Srgb,
+ GAL.Format.Bc3Unorm);
+
+ bool supportsBc45CompressionFormat = FormatCapabilities.FormatsSupports(compressedFormatFeatureFlags,
+ GAL.Format.Bc4Snorm,
+ GAL.Format.Bc4Unorm,
+ GAL.Format.Bc5Snorm,
+ GAL.Format.Bc5Unorm);
+
+ bool supportsBc67CompressionFormat = FormatCapabilities.FormatsSupports(compressedFormatFeatureFlags,
+ GAL.Format.Bc6HSfloat,
+ GAL.Format.Bc6HUfloat,
+ GAL.Format.Bc7Srgb,
+ GAL.Format.Bc7Unorm);
+
+ Api.GetPhysicalDeviceFeatures(_physicalDevice, out var features);
+ Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties);
+
+ var limits = properties.Limits;
+
+ return new Capabilities(
+ api: TargetApi.Vulkan,
+ GpuVendor,
+ hasFrontFacingBug: IsIntelWindows,
+ hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
+ supportsAstcCompression: features.TextureCompressionAstcLdr,
+ supportsBc123Compression: supportsBc123CompressionFormat,
+ supportsBc45Compression: supportsBc45CompressionFormat,
+ supportsBc67Compression: supportsBc67CompressionFormat,
+ supports3DTextureCompression: true,
+ supportsBgraFormat: true,
+ supportsR4G4Format: false,
+ supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock,
+ supportsFragmentShaderOrderingIntel: false,
+ supportsGeometryShaderPassthrough: Capabilities.SupportsGeometryShaderPassthrough,
+ supportsImageLoadFormatted: features.ShaderStorageImageReadWithoutFormat,
+ supportsMismatchingViewFormat: true,
+ supportsCubemapView: !IsAmdGcn,
+ supportsNonConstantTextureOffset: false,
+ supportsShaderBallot: false,
+ supportsTextureShadowLod: false,
+ supportsViewportSwizzle: false,
+ supportsIndirectParameters: Capabilities.SupportsIndirectParameters,
+ maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
+ maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
+ maximumTexturesPerStage: Constants.MaxTexturesPerStage,
+ maximumImagesPerStage: Constants.MaxImagesPerStage,
+ maximumComputeSharedMemorySize: (int)limits.MaxComputeSharedMemorySize,
+ maximumSupportedAnisotropy: (int)limits.MaxSamplerAnisotropy,
+ storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment);
+ }
+
+ public HardwareInfo GetHardwareInfo()
+ {
+ return new HardwareInfo(GpuVendor, GpuRenderer);
+ }
+
+ public static DeviceInfo[] GetPhysicalDevices()
+ {
+ try
+ {
+ return VulkanInitialization.GetSuitablePhysicalDevices(Vk.GetApi());
+ }
+ catch (Exception)
+ {
+ // If we got an exception here, Vulkan is most likely not supported.
+ return Array.Empty<DeviceInfo>();
+ }
+ }
+
+ private static string ParseStandardVulkanVersion(uint version)
+ {
+ return $"{version >> 22}.{(version >> 12) & 0x3FF}.{version & 0xFFF}";
+ }
+
+ private static string ParseDriverVersion(ref PhysicalDeviceProperties properties)
+ {
+ uint driverVersionRaw = properties.DriverVersion;
+
+ // NVIDIA differ from the standard here and uses a different format.
+ if (properties.VendorID == 0x10DE)
+ {
+ return $"{(driverVersionRaw >> 22) & 0x3FF}.{(driverVersionRaw >> 14) & 0xFF}.{(driverVersionRaw >> 6) & 0xFF}.{driverVersionRaw & 0x3F}";
+ }
+ else
+ {
+ return ParseStandardVulkanVersion(driverVersionRaw);
+ }
+ }
+
+ private unsafe void PrintGpuInformation()
+ {
+ Api.GetPhysicalDeviceProperties(_physicalDevice, out var properties);
+
+ string vendorName = VendorUtils.GetNameFromId(properties.VendorID);
+
+ Vendor = VendorUtils.FromId(properties.VendorID);
+
+ IsAmdWindows = Vendor == Vendor.Amd && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ IsIntelWindows = Vendor == Vendor.Intel && RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+
+ GpuVendor = vendorName;
+ GpuRenderer = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName);
+ GpuVersion = $"Vulkan v{ParseStandardVulkanVersion(properties.ApiVersion)}, Driver v{ParseDriverVersion(ref properties)}";
+
+ IsAmdGcn = Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex.IsMatch(GpuRenderer);
+
+ Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
+ }
+
+ public void Initialize(GraphicsDebugLevel logLevel)
+ {
+ if (IsOffScreen)
+ {
+ SetupOffScreenContext(logLevel);
+ }
+ else
+ {
+ SetupContext(logLevel);
+ }
+
+ PrintGpuInformation();
+ }
+
+ public void PreFrame()
+ {
+ _syncManager.Cleanup();
+ }
+
+ public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, bool hostReserved)
+ {
+ return _counters.QueueReport(type, resultHandler, hostReserved);
+ }
+
+ public void ResetCounter(CounterType type)
+ {
+ _counters.QueueReset(type);
+ }
+
+ public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
+ {
+ BufferManager.SetData(buffer, offset, data, _pipeline.CurrentCommandBuffer, _pipeline.EndRenderPass);
+ }
+
+ public void UpdateCounters()
+ {
+ _counters.Update();
+ }
+
+ public void BackgroundContextAction(Action action, bool alwaysBackground = false)
+ {
+ action();
+ }
+
+ public void CreateSync(ulong id)
+ {
+ _syncManager.Create(id);
+ }
+
+ public IProgram LoadProgramBinary(byte[] programBinary, bool isFragment, ShaderInfo info)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void WaitSync(ulong id)
+ {
+ _syncManager.Wait(id);
+ }
+
+ public void Screenshot()
+ {
+ _window.ScreenCaptureRequested = true;
+ }
+
+ public void OnScreenCaptured(ScreenCaptureImageInfo bitmap)
+ {
+ ScreenCaptured?.Invoke(this, bitmap);
+ }
+
+ public unsafe void Dispose()
+ {
+ CommandBufferPool.Dispose();
+ BackgroundResources.Dispose();
+ _counters.Dispose();
+ _window.Dispose();
+ HelperShader.Dispose();
+ _pipeline.Dispose();
+ BufferManager.Dispose();
+ DescriptorSetManager.Dispose();
+ PipelineLayoutCache.Dispose();
+
+ MemoryAllocator.Dispose();
+
+ if (_debugReportCallback.Handle != 0)
+ {
+ DebugReportApi.DestroyDebugReportCallback(_instance, _debugReportCallback, null);
+ }
+
+ foreach (var shader in Shaders)
+ {
+ shader.Dispose();
+ }
+
+ foreach (var texture in Textures)
+ {
+ texture.Release();
+ }
+
+ foreach (var sampler in Samplers)
+ {
+ sampler.Dispose();
+ }
+
+ if (!IsOffScreen)
+ {
+ SurfaceApi.DestroySurface(_instance, _surface, null);
+
+ Api.DestroyDevice(_device, null);
+
+ // Last step destroy the instance
+ Api.DestroyInstance(_instance, null);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Window.cs b/Ryujinx.Graphics.Vulkan/Window.cs
new file mode 100644
index 00000000..12212a7f
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/Window.cs
@@ -0,0 +1,432 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using System.Linq;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class Window : WindowBase, IDisposable
+ {
+ private const int SurfaceWidth = 1280;
+ private const int SurfaceHeight = 720;
+
+ private readonly VulkanRenderer _gd;
+ private readonly SurfaceKHR _surface;
+ private readonly PhysicalDevice _physicalDevice;
+ private readonly Device _device;
+ private SwapchainKHR _swapchain;
+
+ private Image[] _swapchainImages;
+ private Auto<DisposableImageView>[] _swapchainImageViews;
+
+ private Semaphore _imageAvailableSemaphore;
+ private Semaphore _renderFinishedSemaphore;
+
+ private int _width;
+ private int _height;
+ private VkFormat _format;
+
+ public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
+ {
+ _gd = gd;
+ _physicalDevice = physicalDevice;
+ _device = device;
+ _surface = surface;
+
+ CreateSwapchain();
+
+ var semaphoreCreateInfo = new SemaphoreCreateInfo()
+ {
+ SType = StructureType.SemaphoreCreateInfo
+ };
+
+ gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _imageAvailableSemaphore).ThrowOnError();
+ gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _renderFinishedSemaphore).ThrowOnError();
+ }
+
+ private void RecreateSwapchain()
+ {
+ for (int i = 0; i < _swapchainImageViews.Length; i++)
+ {
+ _swapchainImageViews[i].Dispose();
+ }
+
+ CreateSwapchain();
+ }
+
+ private unsafe void CreateSwapchain()
+ {
+ _gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out var capabilities);
+
+ uint surfaceFormatsCount;
+
+ _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, null);
+
+ var surfaceFormats = new SurfaceFormatKHR[surfaceFormatsCount];
+
+ fixed (SurfaceFormatKHR* pSurfaceFormats = surfaceFormats)
+ {
+ _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, pSurfaceFormats);
+ }
+
+ uint presentModesCount;
+
+ _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, null);
+
+ var presentModes = new PresentModeKHR[presentModesCount];
+
+ fixed (PresentModeKHR* pPresentModes = presentModes)
+ {
+ _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, pPresentModes);
+ }
+
+ uint imageCount = capabilities.MinImageCount + 1;
+ if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
+ {
+ imageCount = capabilities.MaxImageCount;
+ }
+
+ var surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats);
+
+ var extent = ChooseSwapExtent(capabilities);
+
+ _width = (int)extent.Width;
+ _height = (int)extent.Height;
+ _format = surfaceFormat.Format;
+
+ var oldSwapchain = _swapchain;
+
+ var swapchainCreateInfo = new SwapchainCreateInfoKHR()
+ {
+ SType = StructureType.SwapchainCreateInfoKhr,
+ Surface = _surface,
+ MinImageCount = imageCount,
+ ImageFormat = surfaceFormat.Format,
+ ImageColorSpace = surfaceFormat.ColorSpace,
+ ImageExtent = extent,
+ ImageUsage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit,
+ ImageSharingMode = SharingMode.Exclusive,
+ ImageArrayLayers = 1,
+ PreTransform = capabilities.CurrentTransform,
+ CompositeAlpha = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr,
+ PresentMode = ChooseSwapPresentMode(presentModes),
+ Clipped = true,
+ OldSwapchain = oldSwapchain
+ };
+
+ _gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError();
+
+ _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
+
+ _swapchainImages = new Image[imageCount];
+
+ fixed (Image* pSwapchainImages = _swapchainImages)
+ {
+ _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages);
+ }
+
+ _swapchainImageViews = new Auto<DisposableImageView>[imageCount];
+
+ for (int i = 0; i < imageCount; i++)
+ {
+ _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format);
+ }
+ }
+
+ private unsafe Auto<DisposableImageView> CreateSwapchainImageView(Image swapchainImage, VkFormat format)
+ {
+ var componentMapping = new ComponentMapping(
+ ComponentSwizzle.R,
+ ComponentSwizzle.G,
+ ComponentSwizzle.B,
+ ComponentSwizzle.A);
+
+ var aspectFlags = ImageAspectFlags.ImageAspectColorBit;
+
+ var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, 1, 0, 1);
+
+ var imageCreateInfo = new ImageViewCreateInfo()
+ {
+ SType = StructureType.ImageViewCreateInfo,
+ Image = swapchainImage,
+ ViewType = ImageViewType.ImageViewType2D,
+ Format = format,
+ Components = componentMapping,
+ SubresourceRange = subresourceRange
+ };
+
+ _gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
+ return new Auto<DisposableImageView>(new DisposableImageView(_gd.Api, _device, imageView));
+ }
+
+ private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats)
+ {
+ if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined)
+ {
+ return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.ColorspaceSrgbNonlinearKhr);
+ }
+
+ foreach (var format in availableFormats)
+ {
+ if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.ColorspaceSrgbNonlinearKhr)
+ {
+ return format;
+ }
+ }
+
+ return availableFormats[0];
+ }
+
+ private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes)
+ {
+ if (availablePresentModes.Contains(PresentModeKHR.PresentModeImmediateKhr))
+ {
+ return PresentModeKHR.PresentModeImmediateKhr;
+ }
+ else if (availablePresentModes.Contains(PresentModeKHR.PresentModeMailboxKhr))
+ {
+ return PresentModeKHR.PresentModeMailboxKhr;
+ }
+ else
+ {
+ return PresentModeKHR.PresentModeFifoKhr;
+ }
+ }
+
+ public static Extent2D ChooseSwapExtent(SurfaceCapabilitiesKHR capabilities)
+ {
+ if (capabilities.CurrentExtent.Width != uint.MaxValue)
+ {
+ return capabilities.CurrentExtent;
+ }
+ else
+ {
+ uint width = Math.Max(capabilities.MinImageExtent.Width, Math.Min(capabilities.MaxImageExtent.Width, SurfaceWidth));
+ uint height = Math.Max(capabilities.MinImageExtent.Height, Math.Min(capabilities.MaxImageExtent.Height, SurfaceHeight));
+
+ return new Extent2D(width, height);
+ }
+ }
+
+ public unsafe override void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
+ {
+ uint nextImage = 0;
+
+ while (true)
+ {
+ var acquireResult = _gd.SwapchainApi.AcquireNextImage(
+ _device,
+ _swapchain,
+ ulong.MaxValue,
+ _imageAvailableSemaphore,
+ new Fence(),
+ ref nextImage);
+
+ if (acquireResult == Result.ErrorOutOfDateKhr ||
+ acquireResult == Result.SuboptimalKhr)
+ {
+ RecreateSwapchain();
+ }
+ else
+ {
+ acquireResult.ThrowOnError();
+ break;
+ }
+ }
+
+ var swapchainImage = _swapchainImages[nextImage];
+
+ _gd.FlushAllCommands();
+
+ var cbs = _gd.CommandBufferPool.Rent();
+
+ Transition(
+ cbs.CommandBuffer,
+ swapchainImage,
+ 0,
+ AccessFlags.AccessTransferWriteBit,
+ ImageLayout.Undefined,
+ ImageLayout.General);
+
+ var view = (TextureView)texture;
+
+ int srcX0, srcX1, srcY0, srcY1;
+ float scale = view.ScaleFactor;
+
+ if (crop.Left == 0 && crop.Right == 0)
+ {
+ srcX0 = 0;
+ srcX1 = (int)(view.Width / scale);
+ }
+ else
+ {
+ srcX0 = crop.Left;
+ srcX1 = crop.Right;
+ }
+
+ if (crop.Top == 0 && crop.Bottom == 0)
+ {
+ srcY0 = 0;
+ srcY1 = (int)(view.Height / scale);
+ }
+ else
+ {
+ srcY0 = crop.Top;
+ srcY1 = crop.Bottom;
+ }
+
+ if (scale != 1f)
+ {
+ srcX0 = (int)(srcX0 * scale);
+ srcY0 = (int)(srcY0 * scale);
+ srcX1 = (int)Math.Ceiling(srcX1 * scale);
+ srcY1 = (int)Math.Ceiling(srcY1 * scale);
+ }
+
+ if (ScreenCaptureRequested)
+ {
+ CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY);
+
+ ScreenCaptureRequested = false;
+ }
+
+ float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY));
+ float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX));
+
+ int dstWidth = (int)(_width * ratioX);
+ int dstHeight = (int)(_height * ratioY);
+
+ int dstPaddingX = (_width - dstWidth) / 2;
+ int dstPaddingY = (_height - dstHeight) / 2;
+
+ int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX;
+ int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX;
+
+ int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
+ int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
+
+ _gd.HelperShader.Blit(
+ _gd,
+ cbs,
+ view,
+ _swapchainImageViews[nextImage],
+ _width,
+ _height,
+ _format,
+ new Extents2D(srcX0, srcY0, srcX1, srcY1),
+ new Extents2D(dstX0, dstY1, dstX1, dstY0),
+ true,
+ true);
+
+ Transition(
+ cbs.CommandBuffer,
+ swapchainImage,
+ 0,
+ 0,
+ ImageLayout.General,
+ ImageLayout.PresentSrcKhr);
+
+ _gd.CommandBufferPool.Return(
+ cbs,
+ stackalloc[] { _imageAvailableSemaphore },
+ stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
+ stackalloc[] { _renderFinishedSemaphore });
+
+ // TODO: Present queue.
+ var semaphore = _renderFinishedSemaphore;
+ var swapchain = _swapchain;
+
+ Result result;
+
+ var presentInfo = new PresentInfoKHR()
+ {
+ SType = StructureType.PresentInfoKhr,
+ WaitSemaphoreCount = 1,
+ PWaitSemaphores = &semaphore,
+ SwapchainCount = 1,
+ PSwapchains = &swapchain,
+ PImageIndices = &nextImage,
+ PResults = &result
+ };
+
+ lock (_gd.QueueLock)
+ {
+ _gd.SwapchainApi.QueuePresent(_gd.Queue, presentInfo);
+ }
+ }
+
+ private unsafe void Transition(
+ CommandBuffer commandBuffer,
+ Image image,
+ AccessFlags srcAccess,
+ AccessFlags dstAccess,
+ ImageLayout srcLayout,
+ ImageLayout dstLayout)
+ {
+ var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1);
+
+ var barrier = new ImageMemoryBarrier()
+ {
+ SType = StructureType.ImageMemoryBarrier,
+ SrcAccessMask = srcAccess,
+ DstAccessMask = dstAccess,
+ OldLayout = srcLayout,
+ NewLayout = dstLayout,
+ SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
+ Image = image,
+ SubresourceRange = subresourceRange
+ };
+
+ _gd.Api.CmdPipelineBarrier(
+ commandBuffer,
+ PipelineStageFlags.PipelineStageTopOfPipeBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ 0,
+ 0,
+ null,
+ 0,
+ null,
+ 1,
+ barrier);
+ }
+
+ private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
+ {
+ byte[] bitmap = texture.GetData(x, y, width, height);
+
+ _gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
+ }
+
+ public override void SetSize(int width, int height)
+ {
+ // Not needed as we can get the size from the surface.
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ unsafe
+ {
+ _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null);
+ _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null);
+
+ for (int i = 0; i < _swapchainImageViews.Length; i++)
+ {
+ _swapchainImageViews[i].Dispose();
+ }
+
+ _gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null);
+
+ }
+ }
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Vulkan/WindowBase.cs b/Ryujinx.Graphics.Vulkan/WindowBase.cs
new file mode 100644
index 00000000..4f1f0d16
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/WindowBase.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ internal abstract class WindowBase: IWindow
+ {
+ public bool ScreenCaptureRequested { get; set; }
+
+ public abstract void Dispose();
+ public abstract void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
+ public abstract void SetSize(int width, int height);
+ }
+} \ No newline at end of file