diff options
Diffstat (limited to 'Ryujinx.Graphics.Vulkan')
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, ®ion); + + 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 |