Move solution and projects to src
This commit is contained in:
parent
cd124bda58
commit
cee7121058
3466 changed files with 55 additions and 55 deletions
154
src/Ryujinx.Graphics.Vulkan/Auto.cs
Normal file
154
src/Ryujinx.Graphics.Vulkan/Auto.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
179
src/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs
Normal file
179
src/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs
Normal file
|
@ -0,0 +1,179 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal class AutoFlushCounter
|
||||
{
|
||||
// How often to flush on framebuffer change.
|
||||
private readonly static long FramebufferFlushTimer = Stopwatch.Frequency / 1000; // (1ms)
|
||||
|
||||
// How often to flush on draw when fast flush mode is enabled.
|
||||
private readonly static long DrawFlushTimer = Stopwatch.Frequency / 666; // (1.5ms)
|
||||
|
||||
// Average wait time that triggers fast flush mode to be entered.
|
||||
private readonly static long FastFlushEnterThreshold = Stopwatch.Frequency / 666; // (1.5ms)
|
||||
|
||||
// Average wait time that triggers fast flush mode to be exited.
|
||||
private readonly static long FastFlushExitThreshold = Stopwatch.Frequency / 10000; // (0.1ms)
|
||||
|
||||
// Number of frames to average waiting times over.
|
||||
private const int SyncWaitAverageCount = 20;
|
||||
|
||||
private const int MinDrawCountForFlush = 10;
|
||||
private const int MinConsecutiveQueryForFlush = 10;
|
||||
private const int InitialQueryCountForFlush = 32;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
private long _lastFlush;
|
||||
private ulong _lastDrawCount;
|
||||
private bool _hasPendingQuery;
|
||||
private int _consecutiveQueries;
|
||||
private int _queryCount;
|
||||
|
||||
private int[] _queryCountHistory = new int[3];
|
||||
private int _queryCountHistoryIndex;
|
||||
private int _remainingQueries;
|
||||
|
||||
private long[] _syncWaitHistory = new long[SyncWaitAverageCount];
|
||||
private int _syncWaitHistoryIndex;
|
||||
|
||||
private bool _fastFlushMode;
|
||||
|
||||
public AutoFlushCounter(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
public void RegisterFlush(ulong drawCount)
|
||||
{
|
||||
_lastFlush = Stopwatch.GetTimestamp();
|
||||
_lastDrawCount = drawCount;
|
||||
|
||||
_hasPendingQuery = false;
|
||||
_consecutiveQueries = 0;
|
||||
}
|
||||
|
||||
public bool RegisterPendingQuery()
|
||||
{
|
||||
_hasPendingQuery = true;
|
||||
_consecutiveQueries++;
|
||||
_remainingQueries--;
|
||||
|
||||
_queryCountHistory[_queryCountHistoryIndex]++;
|
||||
|
||||
// Interrupt render passes to flush queries, so that early results arrive sooner.
|
||||
if (++_queryCount == InitialQueryCountForFlush)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetRemainingQueries()
|
||||
{
|
||||
if (_remainingQueries <= 0)
|
||||
{
|
||||
_remainingQueries = 16;
|
||||
}
|
||||
|
||||
if (_queryCount < InitialQueryCountForFlush)
|
||||
{
|
||||
return Math.Min(InitialQueryCountForFlush - _queryCount, _remainingQueries);
|
||||
}
|
||||
|
||||
return _remainingQueries;
|
||||
}
|
||||
|
||||
public bool ShouldFlushQuery()
|
||||
{
|
||||
return _hasPendingQuery;
|
||||
}
|
||||
|
||||
public bool ShouldFlushDraw(ulong drawCount)
|
||||
{
|
||||
if (_fastFlushMode)
|
||||
{
|
||||
long draws = (long)(drawCount - _lastDrawCount);
|
||||
|
||||
if (draws < MinDrawCountForFlush)
|
||||
{
|
||||
if (draws == 0)
|
||||
{
|
||||
_lastFlush = Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
long flushTimeout = DrawFlushTimer;
|
||||
|
||||
long now = Stopwatch.GetTimestamp();
|
||||
|
||||
return now > _lastFlush + flushTimeout;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ShouldFlushAttachmentChange(ulong drawCount)
|
||||
{
|
||||
_queryCount = 0;
|
||||
|
||||
// Flush when there's an attachment change out of a large block of queries.
|
||||
if (_consecutiveQueries > MinConsecutiveQueryForFlush)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_consecutiveQueries = 0;
|
||||
|
||||
long draws = (long)(drawCount - _lastDrawCount);
|
||||
|
||||
if (draws < MinDrawCountForFlush)
|
||||
{
|
||||
if (draws == 0)
|
||||
{
|
||||
_lastFlush = Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
long flushTimeout = FramebufferFlushTimer;
|
||||
|
||||
long now = Stopwatch.GetTimestamp();
|
||||
|
||||
return now > _lastFlush + flushTimeout;
|
||||
}
|
||||
|
||||
public void Present()
|
||||
{
|
||||
// Query flush prediction.
|
||||
|
||||
_queryCountHistoryIndex = (_queryCountHistoryIndex + 1) % 3;
|
||||
|
||||
_remainingQueries = _queryCountHistory.Max() + 10;
|
||||
|
||||
_queryCountHistory[_queryCountHistoryIndex] = 0;
|
||||
|
||||
// Fast flush mode toggle.
|
||||
|
||||
_syncWaitHistory[_syncWaitHistoryIndex] = _gd.SyncManager.GetAndResetWaitTicks();
|
||||
|
||||
_syncWaitHistoryIndex = (_syncWaitHistoryIndex + 1) % SyncWaitAverageCount;
|
||||
|
||||
long averageWait = (long)_syncWaitHistory.Average();
|
||||
|
||||
if (_fastFlushMode ? averageWait < FastFlushExitThreshold : averageWait > FastFlushEnterThreshold)
|
||||
{
|
||||
_fastFlushMode = !_fastFlushMode;
|
||||
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Switched fast flush mode: ({_fastFlushMode})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
src/Ryujinx.Graphics.Vulkan/BackgroundResources.cs
Normal file
117
src/Ryujinx.Graphics.Vulkan/BackgroundResources.cs
Normal file
|
@ -0,0 +1,117 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
157
src/Ryujinx.Graphics.Vulkan/BitMap.cs
Normal file
157
src/Ryujinx.Graphics.Vulkan/BitMap.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs
Normal file
12
src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal enum BufferAllocationType
|
||||
{
|
||||
Auto = 0,
|
||||
|
||||
HostMappedNoCache,
|
||||
HostMapped,
|
||||
DeviceLocal,
|
||||
DeviceLocalMapped
|
||||
}
|
||||
}
|
788
src/Ryujinx.Graphics.Vulkan/BufferHolder.cs
Normal file
788
src/Ryujinx.Graphics.Vulkan/BufferHolder.cs
Normal file
|
@ -0,0 +1,788 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class BufferHolder : IDisposable
|
||||
{
|
||||
private const int MaxUpdateBufferSize = 0x10000;
|
||||
|
||||
private const int SetCountThreshold = 100;
|
||||
private const int WriteCountThreshold = 50;
|
||||
private const int FlushCountThreshold = 5;
|
||||
|
||||
public const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
|
||||
|
||||
public const AccessFlags DefaultAccessFlags =
|
||||
AccessFlags.IndirectCommandReadBit |
|
||||
AccessFlags.ShaderReadBit |
|
||||
AccessFlags.ShaderWriteBit |
|
||||
AccessFlags.TransferReadBit |
|
||||
AccessFlags.TransferWriteBit |
|
||||
AccessFlags.UniformReadBit;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
private MemoryAllocation _allocation;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
private Auto<MemoryAllocation> _allocationAuto;
|
||||
private ulong _bufferHandle;
|
||||
|
||||
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
||||
|
||||
public int Size { get; }
|
||||
|
||||
private IntPtr _map;
|
||||
|
||||
private MultiFenceHolder _waitable;
|
||||
|
||||
private bool _lastAccessIsWrite;
|
||||
|
||||
private BufferAllocationType _baseType;
|
||||
private BufferAllocationType _currentType;
|
||||
private bool _swapQueued;
|
||||
|
||||
public BufferAllocationType DesiredType { get; private set; }
|
||||
|
||||
private int _setCount;
|
||||
private int _writeCount;
|
||||
private int _flushCount;
|
||||
private int _flushTemp;
|
||||
|
||||
private ReaderWriterLock _flushLock;
|
||||
private FenceHolder _flushFence;
|
||||
private int _flushWaiting;
|
||||
|
||||
private List<Action> _swapActions;
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
|
||||
{
|
||||
_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;
|
||||
|
||||
_baseType = type;
|
||||
_currentType = currentType;
|
||||
DesiredType = currentType;
|
||||
|
||||
_flushLock = new ReaderWriterLock();
|
||||
}
|
||||
|
||||
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
|
||||
{
|
||||
if (_swapQueued && DesiredType != _currentType)
|
||||
{
|
||||
// Only swap if the buffer is not used in any queued command buffer.
|
||||
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
|
||||
|
||||
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld)
|
||||
{
|
||||
var currentAllocation = _allocationAuto;
|
||||
var currentBuffer = _buffer;
|
||||
IntPtr currentMap = _map;
|
||||
|
||||
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, _currentType);
|
||||
|
||||
if (buffer.Handle != 0)
|
||||
{
|
||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_waitable = new MultiFenceHolder(Size);
|
||||
|
||||
_allocation = allocation;
|
||||
_allocationAuto = new Auto<MemoryAllocation>(allocation);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
|
||||
_bufferHandle = buffer.Handle;
|
||||
_map = allocation.HostPointer;
|
||||
|
||||
if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
|
||||
{
|
||||
// Copy data directly. Readbacks don't have to wait if this is done.
|
||||
|
||||
unsafe
|
||||
{
|
||||
new Span<byte>((void*)currentMap, Size).CopyTo(new Span<byte>((void*)_map, Size));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cbs == null)
|
||||
{
|
||||
cbs = _gd.CommandBufferPool.Rent();
|
||||
}
|
||||
|
||||
CommandBufferScoped cbsV = cbs.Value;
|
||||
|
||||
Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
|
||||
|
||||
// Need to wait for the data to reach the new buffer before data can be flushed.
|
||||
|
||||
_flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
|
||||
_flushFence.Get();
|
||||
}
|
||||
|
||||
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
|
||||
|
||||
_currentType = resultType;
|
||||
|
||||
if (_swapActions != null)
|
||||
{
|
||||
foreach (var action in _swapActions)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
_swapActions.Clear();
|
||||
}
|
||||
|
||||
currentBuffer.Dispose();
|
||||
currentAllocation.Dispose();
|
||||
|
||||
_gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
|
||||
|
||||
_flushLock.ReleaseWriterLock();
|
||||
}
|
||||
|
||||
_swapQueued = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_swapQueued = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ConsiderBackingSwap()
|
||||
{
|
||||
if (_baseType == BufferAllocationType.Auto)
|
||||
{
|
||||
if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
|
||||
{
|
||||
if (_flushCount > 0 || _flushTemp-- > 0)
|
||||
{
|
||||
// Buffers that flush should ideally be mapped in host address space for easy copies.
|
||||
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
|
||||
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
|
||||
DesiredType = Size > DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
|
||||
|
||||
// It's harder for a buffer that is flushed to revert to another type of mapping.
|
||||
if (_flushCount > 0)
|
||||
{
|
||||
_flushTemp = 1000;
|
||||
}
|
||||
}
|
||||
else if (_writeCount >= WriteCountThreshold)
|
||||
{
|
||||
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
|
||||
DesiredType = BufferAllocationType.DeviceLocal;
|
||||
}
|
||||
else if (_setCount > SetCountThreshold)
|
||||
{
|
||||
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
|
||||
DesiredType = BufferAllocationType.HostMapped;
|
||||
}
|
||||
|
||||
_flushCount = 0;
|
||||
_writeCount = 0;
|
||||
_setCount = 0;
|
||||
}
|
||||
|
||||
if (!_swapQueued && DesiredType != _currentType)
|
||||
{
|
||||
_swapQueued = true;
|
||||
|
||||
_gd.PipelineInternal.AddBackingSwap(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView)
|
||||
{
|
||||
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();
|
||||
|
||||
(_swapActions ??= new List<Action>()).Add(invalidateView);
|
||||
|
||||
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
|
||||
}
|
||||
|
||||
public void InheritMetrics(BufferHolder other)
|
||||
{
|
||||
_setCount = other._setCount;
|
||||
_writeCount = other._writeCount;
|
||||
_flushCount = other._flushCount;
|
||||
_flushTemp = other._flushTemp;
|
||||
}
|
||||
|
||||
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.AllCommandsBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
DependencyFlags.DeviceGroupBit,
|
||||
1,
|
||||
memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
_writeCount++;
|
||||
|
||||
SignalWrite(0, Size);
|
||||
}
|
||||
else if (isSSBO)
|
||||
{
|
||||
// Always consider SSBO access for swapping to device local memory.
|
||||
|
||||
_writeCount++;
|
||||
|
||||
ConsiderBackingSwap();
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, int offset, int size, bool isWrite = false)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
_writeCount++;
|
||||
|
||||
SignalWrite(offset, size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public void SignalWrite(int offset, int size)
|
||||
{
|
||||
ConsiderBackingSwap();
|
||||
|
||||
if (offset == 0 && size == Size)
|
||||
{
|
||||
_cachedConvertedBuffers.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedConvertedBuffers.ClearRange(offset, size);
|
||||
}
|
||||
}
|
||||
|
||||
public BufferHandle GetHandle()
|
||||
{
|
||||
var handle = _bufferHandle;
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle);
|
||||
}
|
||||
|
||||
public unsafe IntPtr Map(int offset, int mappingSize)
|
||||
{
|
||||
return _map;
|
||||
}
|
||||
|
||||
private void ClearFlushFence()
|
||||
{
|
||||
// Asusmes _flushLock is held as writer.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
if (_flushWaiting == 0)
|
||||
{
|
||||
_flushFence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForFlushFence()
|
||||
{
|
||||
// Assumes the _flushLock is held as reader, returns in same state.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
||||
|
||||
var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite);
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
var fence = _flushFence;
|
||||
Interlocked.Increment(ref _flushWaiting);
|
||||
|
||||
// Don't wait in the lock.
|
||||
|
||||
var restoreCookie = _flushLock.ReleaseLock();
|
||||
|
||||
fence.Wait();
|
||||
|
||||
_flushLock.RestoreLock(ref restoreCookie);
|
||||
|
||||
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
||||
{
|
||||
fence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
|
||||
_flushLock.DowngradeFromWriterLock(ref cookie);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe PinnedSpan<byte> GetData(int offset, int size)
|
||||
{
|
||||
_flushLock.AcquireReaderLock(Timeout.Infinite);
|
||||
|
||||
WaitForFlushFence();
|
||||
|
||||
_flushCount++;
|
||||
|
||||
Span<byte> result;
|
||||
|
||||
if (_map != IntPtr.Zero)
|
||||
{
|
||||
result = GetDataStorage(offset, size);
|
||||
|
||||
// Need to be careful here, the buffer can't be unmapped while the data is being used.
|
||||
_buffer.IncrementReferenceCount();
|
||||
|
||||
_flushLock.ReleaseReaderLock();
|
||||
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
BackgroundResource resource = _gd.BackgroundResources.Get();
|
||||
|
||||
if (_gd.CommandBufferPool.OwnedByCurrentThread)
|
||||
{
|
||||
_gd.FlushAllCommands();
|
||||
|
||||
result = resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
||||
}
|
||||
|
||||
_flushLock.ReleaseReaderLock();
|
||||
|
||||
// Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
_setCount++;
|
||||
|
||||
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));
|
||||
|
||||
SignalWrite(offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cbs != null &&
|
||||
_gd.PipelineInternal.RenderPassActive &&
|
||||
!(_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, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
|
||||
|
||||
_writeCount--;
|
||||
|
||||
InsertBufferBarrier(
|
||||
_gd,
|
||||
cbs.CommandBuffer,
|
||||
dstBuffer,
|
||||
DefaultAccessFlags,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
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.TransferWriteBit,
|
||||
DefaultAccessFlags,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
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,
|
||||
DefaultAccessFlags,
|
||||
AccessFlags.TransferWriteBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.TransferBit,
|
||||
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.TransferWriteBit,
|
||||
DefaultAccessFlags,
|
||||
PipelineStageFlags.TransferBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
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);
|
||||
}
|
||||
|
||||
private bool BoundToRange(int offset, ref int size)
|
||||
{
|
||||
if (offset >= Size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size = Math.Min(Size - offset, size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
|
||||
{
|
||||
if (!BoundToRange(offset, ref size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = new I8ToI16CacheKey(_gd);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
|
||||
{
|
||||
holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
|
||||
|
||||
_gd.PipelineInternal.EndRenderPass();
|
||||
_gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
|
||||
|
||||
key.SetBuffer(holder.GetBuffer());
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
return holder.GetBuffer();
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetAlignedVertexBuffer(CommandBufferScoped cbs, int offset, int size, int stride, int alignment)
|
||||
{
|
||||
if (!BoundToRange(offset, ref size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = new AlignedVertexBufferCacheKey(_gd, stride, alignment);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
|
||||
{
|
||||
int alignedStride = (stride + (alignment - 1)) & -alignment;
|
||||
|
||||
holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride);
|
||||
|
||||
_gd.PipelineInternal.EndRenderPass();
|
||||
_gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride);
|
||||
|
||||
key.SetBuffer(holder.GetBuffer());
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
return holder.GetBuffer();
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
if (!BoundToRange(offset, ref size))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = new TopologyConversionCacheKey(_gd, pattern, indexSize);
|
||||
|
||||
if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
|
||||
{
|
||||
// The destination index size is always I32.
|
||||
|
||||
int indexCount = size / indexSize;
|
||||
|
||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||
|
||||
holder = _gd.BufferManager.Create(_gd, convertedCount * 4);
|
||||
|
||||
_gd.PipelineInternal.EndRenderPass();
|
||||
_gd.HelperShader.ConvertIndexBuffer(_gd, cbs, this, holder, pattern, indexSize, offset, indexCount);
|
||||
|
||||
key.SetBuffer(holder.GetBuffer());
|
||||
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
return holder.GetBuffer();
|
||||
}
|
||||
|
||||
public bool TryGetCachedConvertedBuffer(int offset, int size, ICacheKey key, out BufferHolder holder)
|
||||
{
|
||||
return _cachedConvertedBuffers.TryGetValue(offset, size, key, out holder);
|
||||
}
|
||||
|
||||
public void AddCachedConvertedBuffer(int offset, int size, ICacheKey key, BufferHolder holder)
|
||||
{
|
||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||
}
|
||||
|
||||
public void AddCachedConvertedBufferDependency(int offset, int size, ICacheKey key, Dependency dependency)
|
||||
{
|
||||
_cachedConvertedBuffers.AddDependency(offset, size, key, dependency);
|
||||
}
|
||||
|
||||
public void RemoveCachedConvertedBuffer(int offset, int size, ICacheKey key)
|
||||
{
|
||||
_cachedConvertedBuffers.Remove(offset, size, key);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_swapQueued = false;
|
||||
|
||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||
|
||||
_buffer.Dispose();
|
||||
_allocationAuto.Dispose();
|
||||
_cachedConvertedBuffers.Dispose();
|
||||
|
||||
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_flushLock.ReleaseWriterLock();
|
||||
}
|
||||
}
|
||||
}
|
455
src/Ryujinx.Graphics.Vulkan/BufferManager.cs
Normal file
455
src/Ryujinx.Graphics.Vulkan/BufferManager.cs
Normal file
|
@ -0,0 +1,455 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class BufferManager : IDisposable
|
||||
{
|
||||
private const MemoryPropertyFlags DefaultBufferMemoryFlags =
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit |
|
||||
MemoryPropertyFlags.HostCachedBit;
|
||||
|
||||
// Some drivers don't expose a "HostCached" memory type,
|
||||
// so we need those alternative flags for the allocation to succeed there.
|
||||
private const MemoryPropertyFlags DefaultBufferMemoryNoCacheFlags =
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit;
|
||||
|
||||
private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags =
|
||||
MemoryPropertyFlags.DeviceLocalBit;
|
||||
|
||||
private const MemoryPropertyFlags DeviceLocalMappedBufferMemoryFlags =
|
||||
MemoryPropertyFlags.DeviceLocalBit |
|
||||
MemoryPropertyFlags.HostVisibleBit |
|
||||
MemoryPropertyFlags.HostCoherentBit;
|
||||
|
||||
private const BufferUsageFlags DefaultBufferUsageFlags =
|
||||
BufferUsageFlags.TransferSrcBit |
|
||||
BufferUsageFlags.TransferDstBit |
|
||||
BufferUsageFlags.UniformTexelBufferBit |
|
||||
BufferUsageFlags.StorageTexelBufferBit |
|
||||
BufferUsageFlags.UniformBufferBit |
|
||||
BufferUsageFlags.StorageBufferBit |
|
||||
BufferUsageFlags.IndexBufferBit |
|
||||
BufferUsageFlags.VertexBufferBit |
|
||||
BufferUsageFlags.TransformFeedbackBufferBitExt;
|
||||
|
||||
private readonly Device _device;
|
||||
|
||||
private readonly IdList<BufferHolder> _buffers;
|
||||
|
||||
public int BufferCount { get; private set; }
|
||||
|
||||
public StagingBuffer StagingBuffer { get; }
|
||||
|
||||
public BufferManager(VulkanRenderer gd, Device device)
|
||||
{
|
||||
_device = device;
|
||||
_buffers = new IdList<BufferHolder>();
|
||||
StagingBuffer = new StagingBuffer(gd, this);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
|
||||
{
|
||||
return CreateWithHandle(gd, size, out _, baseType, storageHint);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, out BufferHolder holder, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
|
||||
{
|
||||
holder = Create(gd, size, baseType: baseType, storageHint: storageHint);
|
||||
if (holder == null)
|
||||
{
|
||||
return BufferHandle.Null;
|
||||
}
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public unsafe (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) CreateBacking(
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
BufferAllocationType type,
|
||||
bool forConditionalRendering = false,
|
||||
BufferAllocationType fallbackType = BufferAllocationType.Auto)
|
||||
{
|
||||
var usage = DefaultBufferUsageFlags;
|
||||
|
||||
if (forConditionalRendering && gd.Capabilities.SupportsConditionalRendering)
|
||||
{
|
||||
usage |= BufferUsageFlags.ConditionalRenderingBitExt;
|
||||
}
|
||||
else if (gd.Capabilities.SupportsIndirectParameters)
|
||||
{
|
||||
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
MemoryAllocation allocation;
|
||||
|
||||
do
|
||||
{
|
||||
var allocateFlags = type switch
|
||||
{
|
||||
BufferAllocationType.HostMappedNoCache => DefaultBufferMemoryNoCacheFlags,
|
||||
BufferAllocationType.HostMapped => DefaultBufferMemoryFlags,
|
||||
BufferAllocationType.DeviceLocal => DeviceLocalBufferMemoryFlags,
|
||||
BufferAllocationType.DeviceLocalMapped => DeviceLocalMappedBufferMemoryFlags,
|
||||
_ => DefaultBufferMemoryFlags
|
||||
};
|
||||
|
||||
// If an allocation with this memory type fails, fall back to the previous one.
|
||||
try
|
||||
{
|
||||
allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, allocateFlags, true);
|
||||
}
|
||||
catch (VulkanException)
|
||||
{
|
||||
allocation = default;
|
||||
}
|
||||
}
|
||||
while (allocation.Memory.Handle == 0 && (--type != fallbackType));
|
||||
|
||||
if (allocation.Memory.Handle == 0UL)
|
||||
{
|
||||
gd.Api.DestroyBuffer(_device, buffer, null);
|
||||
return default;
|
||||
}
|
||||
|
||||
gd.Api.BindBufferMemory(_device, buffer, allocation.Memory, allocation.Offset);
|
||||
|
||||
return (buffer, allocation, type);
|
||||
}
|
||||
|
||||
public unsafe BufferHolder Create(
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
bool forConditionalRendering = false,
|
||||
BufferAllocationType baseType = BufferAllocationType.HostMapped,
|
||||
BufferHandle storageHint = default)
|
||||
{
|
||||
BufferAllocationType type = baseType;
|
||||
BufferHolder storageHintHolder = null;
|
||||
|
||||
if (baseType == BufferAllocationType.Auto)
|
||||
{
|
||||
if (gd.IsSharedMemory)
|
||||
{
|
||||
baseType = BufferAllocationType.HostMapped;
|
||||
type = baseType;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = size >= BufferHolder.DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocal : BufferAllocationType.HostMapped;
|
||||
}
|
||||
|
||||
if (storageHint != BufferHandle.Null)
|
||||
{
|
||||
if (TryGetBuffer(storageHint, out storageHintHolder))
|
||||
{
|
||||
type = storageHintHolder.DesiredType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) =
|
||||
CreateBacking(gd, size, type, forConditionalRendering);
|
||||
|
||||
if (buffer.Handle != 0)
|
||||
{
|
||||
var holder = new BufferHolder(gd, _device, buffer, allocation, size, baseType, resultType);
|
||||
|
||||
if (storageHintHolder != null)
|
||||
{
|
||||
holder.InheritMetrics(storageHintHolder);
|
||||
}
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBufferView> CreateView(BufferHandle handle, VkFormat format, int offset, int size, Action invalidateView)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.CreateView(format, offset, size, invalidateView);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, bool isSSBO = false)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBuffer(commandBuffer, isWrite, isSSBO);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, int offset, int size, bool isWrite)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBuffer(commandBuffer, offset, size, 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> GetAlignedVertexBuffer(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, int stride, int alignment)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetAlignedVertexBuffer(cbs, offset, size, stride, alignment);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetBufferTopologyConversion(cbs, offset, size, pattern, indexSize);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public (Auto<DisposableBuffer>, Auto<DisposableBuffer>) GetBufferTopologyConversionIndirect(
|
||||
VulkanRenderer gd,
|
||||
CommandBufferScoped cbs,
|
||||
BufferRange indexBuffer,
|
||||
BufferRange indirectBuffer,
|
||||
BufferRange drawCountBuffer,
|
||||
IndexBufferPattern pattern,
|
||||
int indexSize,
|
||||
bool hasDrawCount,
|
||||
int maxDrawCount,
|
||||
int indirectDataStride)
|
||||
{
|
||||
BufferHolder drawCountBufferHolder = null;
|
||||
|
||||
if (!TryGetBuffer(indexBuffer.Handle, out var indexBufferHolder) ||
|
||||
!TryGetBuffer(indirectBuffer.Handle, out var indirectBufferHolder) ||
|
||||
(hasDrawCount && !TryGetBuffer(drawCountBuffer.Handle, out drawCountBufferHolder)))
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
var indexBufferKey = new TopologyConversionIndirectCacheKey(
|
||||
gd,
|
||||
pattern,
|
||||
indexSize,
|
||||
indirectBufferHolder,
|
||||
indirectBuffer.Offset,
|
||||
indirectBuffer.Size);
|
||||
|
||||
bool hasConvertedIndexBuffer = indexBufferHolder.TryGetCachedConvertedBuffer(
|
||||
indexBuffer.Offset,
|
||||
indexBuffer.Size,
|
||||
indexBufferKey,
|
||||
out var convertedIndexBuffer);
|
||||
|
||||
var indirectBufferKey = new IndirectDataCacheKey(pattern);
|
||||
bool hasConvertedIndirectBuffer = indirectBufferHolder.TryGetCachedConvertedBuffer(
|
||||
indirectBuffer.Offset,
|
||||
indirectBuffer.Size,
|
||||
indirectBufferKey,
|
||||
out var convertedIndirectBuffer);
|
||||
|
||||
var drawCountBufferKey = new DrawCountCacheKey();
|
||||
bool hasCachedDrawCount = true;
|
||||
|
||||
if (hasDrawCount)
|
||||
{
|
||||
hasCachedDrawCount = drawCountBufferHolder.TryGetCachedConvertedBuffer(
|
||||
drawCountBuffer.Offset,
|
||||
drawCountBuffer.Size,
|
||||
drawCountBufferKey,
|
||||
out _);
|
||||
}
|
||||
|
||||
if (!hasConvertedIndexBuffer || !hasConvertedIndirectBuffer || !hasCachedDrawCount)
|
||||
{
|
||||
// The destination index size is always I32.
|
||||
|
||||
int indexCount = indexBuffer.Size / indexSize;
|
||||
|
||||
int convertedCount = pattern.GetConvertedCount(indexCount);
|
||||
|
||||
if (!hasConvertedIndexBuffer)
|
||||
{
|
||||
convertedIndexBuffer = Create(gd, convertedCount * 4);
|
||||
indexBufferKey.SetBuffer(convertedIndexBuffer.GetBuffer());
|
||||
indexBufferHolder.AddCachedConvertedBuffer(indexBuffer.Offset, indexBuffer.Size, indexBufferKey, convertedIndexBuffer);
|
||||
}
|
||||
|
||||
if (!hasConvertedIndirectBuffer)
|
||||
{
|
||||
convertedIndirectBuffer = Create(gd, indirectBuffer.Size);
|
||||
indirectBufferHolder.AddCachedConvertedBuffer(indirectBuffer.Offset, indirectBuffer.Size, indirectBufferKey, convertedIndirectBuffer);
|
||||
}
|
||||
|
||||
gd.PipelineInternal.EndRenderPass();
|
||||
gd.HelperShader.ConvertIndexBufferIndirect(
|
||||
gd,
|
||||
cbs,
|
||||
indirectBufferHolder,
|
||||
convertedIndirectBuffer,
|
||||
drawCountBuffer,
|
||||
indexBufferHolder,
|
||||
convertedIndexBuffer,
|
||||
pattern,
|
||||
indexSize,
|
||||
indexBuffer.Offset,
|
||||
indexBuffer.Size,
|
||||
indirectBuffer.Offset,
|
||||
hasDrawCount,
|
||||
maxDrawCount,
|
||||
indirectDataStride);
|
||||
|
||||
// Any modification of the indirect buffer should invalidate the index buffers that are associated with it,
|
||||
// since we used the indirect data to find the range of the index buffer that is used.
|
||||
|
||||
var indexBufferDependency = new Dependency(
|
||||
indexBufferHolder,
|
||||
indexBuffer.Offset,
|
||||
indexBuffer.Size,
|
||||
indexBufferKey);
|
||||
|
||||
indirectBufferHolder.AddCachedConvertedBufferDependency(
|
||||
indirectBuffer.Offset,
|
||||
indirectBuffer.Size,
|
||||
indirectBufferKey,
|
||||
indexBufferDependency);
|
||||
|
||||
if (hasDrawCount)
|
||||
{
|
||||
if (!hasCachedDrawCount)
|
||||
{
|
||||
drawCountBufferHolder.AddCachedConvertedBuffer(drawCountBuffer.Offset, drawCountBuffer.Size, drawCountBufferKey, null);
|
||||
}
|
||||
|
||||
// If we have a draw count, any modification of the draw count should invalidate all indirect buffers
|
||||
// where we used it to find the range of indirect data that is actually used.
|
||||
|
||||
var indirectBufferDependency = new Dependency(
|
||||
indirectBufferHolder,
|
||||
indirectBuffer.Offset,
|
||||
indirectBuffer.Size,
|
||||
indirectBufferKey);
|
||||
|
||||
drawCountBufferHolder.AddCachedConvertedBufferDependency(
|
||||
drawCountBuffer.Offset,
|
||||
drawCountBuffer.Size,
|
||||
drawCountBufferKey,
|
||||
indirectBufferDependency);
|
||||
}
|
||||
}
|
||||
|
||||
return (convertedIndexBuffer.GetBuffer(), convertedIndirectBuffer.GetBuffer());
|
||||
}
|
||||
|
||||
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 PinnedSpan<byte> GetData(BufferHandle handle, int offset, int size)
|
||||
{
|
||||
if (TryGetBuffer(handle, out var holder))
|
||||
{
|
||||
return holder.GetData(offset, size);
|
||||
}
|
||||
|
||||
return new PinnedSpan<byte>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
48
src/Ryujinx.Graphics.Vulkan/BufferState.cs
Normal file
48
src/Ryujinx.Graphics.Vulkan/BufferState.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
struct BufferState : IDisposable
|
||||
{
|
||||
public static BufferState Null => new BufferState(null, 0, 0);
|
||||
|
||||
private readonly int _offset;
|
||||
private readonly int _size;
|
||||
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public BufferState(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
buffer?.IncrementReferenceCount();
|
||||
}
|
||||
|
||||
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 Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
if (_buffer == from)
|
||||
{
|
||||
_buffer.DecrementReferenceCount();
|
||||
to.IncrementReferenceCount();
|
||||
|
||||
_buffer = to;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_buffer?.DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
77
src/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs
Normal file
77
src/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
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)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
398
src/Ryujinx.Graphics.Vulkan/CacheByRange.cs
Normal file
398
src/Ryujinx.Graphics.Vulkan/CacheByRange.cs
Normal file
|
@ -0,0 +1,398 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
interface ICacheKey : IDisposable
|
||||
{
|
||||
bool KeyEqual(ICacheKey other);
|
||||
}
|
||||
|
||||
struct I8ToI16CacheKey : ICacheKey
|
||||
{
|
||||
// Used to notify the pipeline that bindings have invalidated on dispose.
|
||||
private readonly VulkanRenderer _gd;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public I8ToI16CacheKey(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is I8ToI16CacheKey;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gd.PipelineInternal.DirtyIndexBuffer(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
struct AlignedVertexBufferCacheKey : ICacheKey
|
||||
{
|
||||
private readonly int _stride;
|
||||
private readonly int _alignment;
|
||||
|
||||
// Used to notify the pipeline that bindings have invalidated on dispose.
|
||||
private readonly VulkanRenderer _gd;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public AlignedVertexBufferCacheKey(VulkanRenderer gd, int stride, int alignment)
|
||||
{
|
||||
_gd = gd;
|
||||
_stride = stride;
|
||||
_alignment = alignment;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is AlignedVertexBufferCacheKey entry &&
|
||||
entry._stride == _stride &&
|
||||
entry._alignment == _alignment;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gd.PipelineInternal.DirtyVertexBuffer(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
struct TopologyConversionCacheKey : ICacheKey
|
||||
{
|
||||
private IndexBufferPattern _pattern;
|
||||
private int _indexSize;
|
||||
|
||||
// Used to notify the pipeline that bindings have invalidated on dispose.
|
||||
private readonly VulkanRenderer _gd;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public TopologyConversionCacheKey(VulkanRenderer gd, IndexBufferPattern pattern, int indexSize)
|
||||
{
|
||||
_gd = gd;
|
||||
_pattern = pattern;
|
||||
_indexSize = indexSize;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is TopologyConversionCacheKey entry &&
|
||||
entry._pattern == _pattern &&
|
||||
entry._indexSize == _indexSize;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gd.PipelineInternal.DirtyIndexBuffer(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct TopologyConversionIndirectCacheKey : ICacheKey
|
||||
{
|
||||
private readonly TopologyConversionCacheKey _baseKey;
|
||||
private readonly BufferHolder _indirectDataBuffer;
|
||||
private readonly int _indirectDataOffset;
|
||||
private readonly int _indirectDataSize;
|
||||
|
||||
public TopologyConversionIndirectCacheKey(
|
||||
VulkanRenderer gd,
|
||||
IndexBufferPattern pattern,
|
||||
int indexSize,
|
||||
BufferHolder indirectDataBuffer,
|
||||
int indirectDataOffset,
|
||||
int indirectDataSize)
|
||||
{
|
||||
_baseKey = new TopologyConversionCacheKey(gd, pattern, indexSize);
|
||||
_indirectDataBuffer = indirectDataBuffer;
|
||||
_indirectDataOffset = indirectDataOffset;
|
||||
_indirectDataSize = indirectDataSize;
|
||||
}
|
||||
|
||||
public bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is TopologyConversionIndirectCacheKey entry &&
|
||||
entry._baseKey.KeyEqual(_baseKey) &&
|
||||
entry._indirectDataBuffer == _indirectDataBuffer &&
|
||||
entry._indirectDataOffset == _indirectDataOffset &&
|
||||
entry._indirectDataSize == _indirectDataSize;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_baseKey.SetBuffer(buffer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_baseKey.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
struct IndirectDataCacheKey : ICacheKey
|
||||
{
|
||||
private IndexBufferPattern _pattern;
|
||||
|
||||
public IndirectDataCacheKey(IndexBufferPattern pattern)
|
||||
{
|
||||
_pattern = pattern;
|
||||
}
|
||||
|
||||
public bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is IndirectDataCacheKey entry && entry._pattern == _pattern;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
struct DrawCountCacheKey : ICacheKey
|
||||
{
|
||||
public bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is DrawCountCacheKey;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct Dependency
|
||||
{
|
||||
private readonly BufferHolder _buffer;
|
||||
private readonly int _offset;
|
||||
private readonly int _size;
|
||||
private readonly ICacheKey _key;
|
||||
|
||||
public Dependency(BufferHolder buffer, int offset, int size, ICacheKey key)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_key = key;
|
||||
}
|
||||
|
||||
public void RemoveFromOwner()
|
||||
{
|
||||
_buffer.RemoveCachedConvertedBuffer(_offset, _size, _key);
|
||||
}
|
||||
}
|
||||
|
||||
struct CacheByRange<T> where T : IDisposable
|
||||
{
|
||||
private struct Entry
|
||||
{
|
||||
public ICacheKey Key;
|
||||
public T Value;
|
||||
public List<Dependency> DependencyList;
|
||||
|
||||
public Entry(ICacheKey key, T value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
DependencyList = null;
|
||||
}
|
||||
|
||||
public void InvalidateDependencies()
|
||||
{
|
||||
if (DependencyList != null)
|
||||
{
|
||||
foreach (Dependency dependency in DependencyList)
|
||||
{
|
||||
dependency.RemoveFromOwner();
|
||||
}
|
||||
|
||||
DependencyList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<ulong, List<Entry>> _ranges;
|
||||
|
||||
public void Add(int offset, int size, ICacheKey key, T value)
|
||||
{
|
||||
List<Entry> entries = GetEntries(offset, size);
|
||||
|
||||
entries.Add(new Entry(key, value));
|
||||
}
|
||||
|
||||
public void AddDependency(int offset, int size, ICacheKey key, Dependency dependency)
|
||||
{
|
||||
List<Entry> entries = GetEntries(offset, size);
|
||||
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
Entry entry = entries[i];
|
||||
|
||||
if (entry.Key.KeyEqual(key))
|
||||
{
|
||||
if (entry.DependencyList == null)
|
||||
{
|
||||
entry.DependencyList = new List<Dependency>();
|
||||
entries[i] = entry;
|
||||
}
|
||||
|
||||
entry.DependencyList.Add(dependency);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(int offset, int size, ICacheKey key)
|
||||
{
|
||||
List<Entry> entries = GetEntries(offset, size);
|
||||
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
Entry entry = entries[i];
|
||||
|
||||
if (entry.Key.KeyEqual(key))
|
||||
{
|
||||
entries.RemoveAt(i--);
|
||||
|
||||
DestroyEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.Count == 0)
|
||||
{
|
||||
_ranges.Remove(PackRange(offset, size));
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(int offset, int size, ICacheKey key, out T value)
|
||||
{
|
||||
List<Entry> entries = GetEntries(offset, size);
|
||||
|
||||
foreach (Entry entry in entries)
|
||||
{
|
||||
if (entry.Key.KeyEqual(key))
|
||||
{
|
||||
value = entry.Value;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_ranges != null)
|
||||
{
|
||||
foreach (List<Entry> entries in _ranges.Values)
|
||||
{
|
||||
foreach (Entry entry in entries)
|
||||
{
|
||||
DestroyEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
_ranges.Clear();
|
||||
_ranges = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearRange(int offset, int size)
|
||||
{
|
||||
if (_ranges != null && _ranges.Count > 0)
|
||||
{
|
||||
int end = offset + size;
|
||||
|
||||
List<ulong> toRemove = null;
|
||||
|
||||
foreach (KeyValuePair<ulong, List<Entry>> range in _ranges)
|
||||
{
|
||||
(int rOffset, int rSize) = UnpackRange(range.Key);
|
||||
|
||||
int rEnd = rOffset + rSize;
|
||||
|
||||
if (rEnd > offset && rOffset < end)
|
||||
{
|
||||
List<Entry> entries = range.Value;
|
||||
|
||||
foreach (Entry entry in entries)
|
||||
{
|
||||
DestroyEntry(entry);
|
||||
}
|
||||
|
||||
(toRemove ??= new List<ulong>()).Add(range.Key);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (ulong range in toRemove)
|
||||
{
|
||||
_ranges.Remove(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Entry> GetEntries(int offset, int size)
|
||||
{
|
||||
if (_ranges == null)
|
||||
{
|
||||
_ranges = new Dictionary<ulong, List<Entry>>();
|
||||
}
|
||||
|
||||
ulong key = PackRange(offset, size);
|
||||
|
||||
List<Entry> value;
|
||||
if (!_ranges.TryGetValue(key, out value))
|
||||
{
|
||||
value = new List<Entry>();
|
||||
_ranges.Add(key, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void DestroyEntry(Entry entry)
|
||||
{
|
||||
entry.Key.Dispose();
|
||||
entry.Value?.Dispose();
|
||||
entry.InvalidateDependencies();
|
||||
}
|
||||
|
||||
private static ulong PackRange(int offset, int size)
|
||||
{
|
||||
return (uint)offset | ((ulong)size << 32);
|
||||
}
|
||||
|
||||
private static (int offset, int size) UnpackRange(ulong range)
|
||||
{
|
||||
return ((int)range, (int)(range >> 32));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
}
|
368
src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
Normal file
368
src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs
Normal file
|
@ -0,0 +1,368 @@
|
|||
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.TransientBit |
|
||||
CommandPoolCreateFlags.ResetCommandBufferBit
|
||||
};
|
||||
|
||||
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 AddInUseWaitable(MultiFenceHolder waitable)
|
||||
{
|
||||
lock (_commandBuffers)
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[i];
|
||||
|
||||
if (entry.InUse)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
44
src/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs
Normal file
44
src/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
20
src/Ryujinx.Graphics.Vulkan/Constants.cs
Normal file
20
src/Ryujinx.Graphics.Vulkan/Constants.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
246
src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs
Normal file
246
src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
201
src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
Normal file
201
src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
674
src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
Normal file
674
src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
Normal file
|
@ -0,0 +1,674 @@
|
|||
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, baseType: BufferAllocationType.DeviceLocal);
|
||||
}
|
||||
|
||||
_dummyTexture = gd.CreateTextureView(new 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 Initialize()
|
||||
{
|
||||
Span<byte> dummyTextureData = stackalloc byte[4];
|
||||
_dummyTexture.SetData(dummyTextureData);
|
||||
}
|
||||
|
||||
public void SetProgram(ShaderCollection program)
|
||||
{
|
||||
_program = program;
|
||||
_dirty = DirtyFlags.All;
|
||||
}
|
||||
|
||||
public void SetImage(int binding, ITexture image, GAL.Format imageFormat)
|
||||
{
|
||||
if (image is TextureBuffer imageBuffer)
|
||||
{
|
||||
_bufferImageRefs[binding] = imageBuffer;
|
||||
_bufferImageFormats[binding] = imageFormat;
|
||||
}
|
||||
else if (image is TextureView view)
|
||||
{
|
||||
_imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView();
|
||||
}
|
||||
else
|
||||
{
|
||||
_imageRefs[binding] = null;
|
||||
_bufferImageRefs[binding] = null;
|
||||
_bufferImageFormats[binding] = default;
|
||||
}
|
||||
|
||||
SignalDirty(DirtyFlags.Image);
|
||||
}
|
||||
|
||||
public void SetImage(int binding, Auto<DisposableImageView> image)
|
||||
{
|
||||
_imageRefs[binding] = image;
|
||||
|
||||
SignalDirty(DirtyFlags.Image);
|
||||
}
|
||||
|
||||
public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
|
||||
{
|
||||
for (int i = 0; i < buffers.Length; i++)
|
||||
{
|
||||
var assignment = buffers[i];
|
||||
var buffer = assignment.Range;
|
||||
int index = assignment.Binding;
|
||||
|
||||
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false, isSSBO: true);
|
||||
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 SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
|
||||
{
|
||||
for (int i = 0; i < buffers.Length; i++)
|
||||
{
|
||||
var vkBuffer = buffers[i];
|
||||
int index = first + i;
|
||||
|
||||
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
|
||||
|
||||
DescriptorBufferInfo info = new DescriptorBufferInfo()
|
||||
{
|
||||
Offset = 0,
|
||||
Range = Vk.WholeSize
|
||||
};
|
||||
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 is TextureBuffer textureBuffer)
|
||||
{
|
||||
_bufferTextureRefs[binding] = textureBuffer;
|
||||
}
|
||||
else if (texture is TextureView view)
|
||||
{
|
||||
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
|
||||
|
||||
_textureRefs[binding] = view.GetImageView();
|
||||
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
|
||||
}
|
||||
else
|
||||
{
|
||||
_textureRefs[binding] = null;
|
||||
_samplerRefs[binding] = null;
|
||||
_bufferTextureRefs[binding] = null;
|
||||
}
|
||||
|
||||
SignalDirty(DirtyFlags.Texture);
|
||||
}
|
||||
|
||||
public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
|
||||
{
|
||||
for (int i = 0; i < buffers.Length; i++)
|
||||
{
|
||||
var assignment = buffers[i];
|
||||
var buffer = assignment.Range;
|
||||
int index = assignment.Binding;
|
||||
|
||||
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;
|
||||
if (program.HasMinimalLayout)
|
||||
{
|
||||
dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private void SwapBuffer(Auto<DisposableBuffer>[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
for (int i = 0; i < list.Length; i++)
|
||||
{
|
||||
if (list[i] == from)
|
||||
{
|
||||
list[i] = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
SwapBuffer(_uniformBufferRefs, from, to);
|
||||
SwapBuffer(_storageBufferRefs, from, to);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_dummyTexture.Dispose();
|
||||
_dummySampler.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs
Normal file
25
src/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs
Normal file
25
src/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly struct DisposableBufferView : 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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs
Normal file
25
src/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Vulkan/DisposableImage.cs
Normal file
25
src/Ryujinx.Graphics.Vulkan/DisposableImage.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Vulkan/DisposableImageView.cs
Normal file
25
src/Ryujinx.Graphics.Vulkan/DisposableImageView.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
24
src/Ryujinx.Graphics.Vulkan/DisposableMemory.cs
Normal file
24
src/Ryujinx.Graphics.Vulkan/DisposableMemory.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs
Normal file
25
src/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs
Normal file
25
src/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Vulkan/DisposableSampler.cs
Normal file
25
src/Ryujinx.Graphics.Vulkan/DisposableSampler.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
179
src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
Normal file
179
src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
Normal file
|
@ -0,0 +1,179 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal partial class FsrScalingFilter : IScalingFilter
|
||||
{
|
||||
private readonly VulkanRenderer _renderer;
|
||||
private PipelineHelperShader _pipeline;
|
||||
private ISampler _sampler;
|
||||
private ShaderCollection _scalingProgram;
|
||||
private ShaderCollection _sharpeningProgram;
|
||||
private float _sharpeningLevel = 1;
|
||||
private Device _device;
|
||||
private TextureView _intermediaryTexture;
|
||||
|
||||
public float Level
|
||||
{
|
||||
get => _sharpeningLevel;
|
||||
set
|
||||
{
|
||||
_sharpeningLevel = MathF.Max(0.01f, value);
|
||||
}
|
||||
}
|
||||
|
||||
public FsrScalingFilter(VulkanRenderer renderer, Device device)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pipeline.Dispose();
|
||||
_scalingProgram.Dispose();
|
||||
_sharpeningProgram.Dispose();
|
||||
_sampler.Dispose();
|
||||
_intermediaryTexture?.Dispose();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_pipeline = new PipelineHelperShader(_renderer, _device);
|
||||
|
||||
_pipeline.Initialize();
|
||||
|
||||
var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv");
|
||||
var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv");
|
||||
|
||||
var computeBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
|
||||
var sharpeningBindings = new ShaderBindings(
|
||||
new[] { 2, 3, 4 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
|
||||
_sampler = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(scalingShader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
|
||||
_sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(sharpeningShader, sharpeningBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
}
|
||||
|
||||
public void Run(
|
||||
TextureView view,
|
||||
CommandBufferScoped cbs,
|
||||
Auto<DisposableImageView> destinationTexture,
|
||||
Silk.NET.Vulkan.Format format,
|
||||
int width,
|
||||
int height,
|
||||
Extent2D source,
|
||||
Extent2D destination)
|
||||
{
|
||||
if (_intermediaryTexture == null
|
||||
|| _intermediaryTexture.Info.Width != width
|
||||
|| _intermediaryTexture.Info.Height != height
|
||||
|| !_intermediaryTexture.Info.Equals(view.Info))
|
||||
{
|
||||
var originalInfo = view.Info;
|
||||
|
||||
var swapRB = originalInfo.Format.IsBgr() && originalInfo.SwizzleR == SwizzleComponent.Red;
|
||||
|
||||
var info = new TextureCreateInfo(
|
||||
width,
|
||||
height,
|
||||
originalInfo.Depth,
|
||||
originalInfo.Levels,
|
||||
originalInfo.Samples,
|
||||
originalInfo.BlockWidth,
|
||||
originalInfo.BlockHeight,
|
||||
originalInfo.BytesPerPixel,
|
||||
originalInfo.Format,
|
||||
originalInfo.DepthStencilMode,
|
||||
originalInfo.Target,
|
||||
swapRB ? originalInfo.SwizzleB : originalInfo.SwizzleR,
|
||||
originalInfo.SwizzleG,
|
||||
swapRB ? originalInfo.SwizzleR : originalInfo.SwizzleB,
|
||||
originalInfo.SwizzleA);
|
||||
_intermediaryTexture?.Dispose();
|
||||
_intermediaryTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
||||
}
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
_pipeline.SetProgram(_scalingProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
|
||||
|
||||
float srcWidth = Math.Abs(source.X2 - source.X1);
|
||||
float srcHeight = Math.Abs(source.Y2 - source.Y1);
|
||||
float scaleX = srcWidth / view.Width;
|
||||
float scaleY = srcHeight / view.Height;
|
||||
|
||||
ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
|
||||
{
|
||||
source.X1,
|
||||
source.X2,
|
||||
source.Y1,
|
||||
source.Y2,
|
||||
destination.X1,
|
||||
destination.X2,
|
||||
destination.Y1,
|
||||
destination.Y2,
|
||||
scaleX,
|
||||
scaleY
|
||||
};
|
||||
|
||||
int rangeSize = dimensionsBuffer.Length * sizeof(float);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||
_renderer.BufferManager.SetData(bufferHandle, 0, dimensionsBuffer);
|
||||
|
||||
ReadOnlySpan<float> sharpeningBuffer = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f)};
|
||||
var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float));
|
||||
_renderer.BufferManager.SetData(sharpeningBufferHandle, 0, sharpeningBuffer);
|
||||
|
||||
int threadGroupWorkRegionDim = 16;
|
||||
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
|
||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||
_pipeline.SetImage(0, _intermediaryTexture, GAL.Format.R8G8B8A8Unorm);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
// Sharpening pass
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
_pipeline.SetProgram(_sharpeningProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||
var sharpeningRange = new BufferRange(sharpeningBufferHandle, 0, sizeof(float));
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningRange) });
|
||||
_pipeline.SetImage(0, destinationTexture);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
|
||||
_renderer.BufferManager.Delete(bufferHandle);
|
||||
_renderer.BufferManager.Delete(sharpeningBufferHandle);
|
||||
}
|
||||
}
|
||||
}
|
111
src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
Normal file
111
src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
Normal file
|
@ -0,0 +1,111 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal partial class FxaaPostProcessingEffect : IPostProcessingEffect
|
||||
{
|
||||
private readonly VulkanRenderer _renderer;
|
||||
private ISampler _samplerLinear;
|
||||
private ShaderCollection _shaderProgram;
|
||||
|
||||
private PipelineHelperShader _pipeline;
|
||||
private TextureView _texture;
|
||||
|
||||
public FxaaPostProcessingEffect(VulkanRenderer renderer, Device device)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_pipeline = new PipelineHelperShader(renderer, device);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_shaderProgram.Dispose();
|
||||
_pipeline.Dispose();
|
||||
_samplerLinear.Dispose();
|
||||
_texture?.Dispose();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_pipeline.Initialize();
|
||||
|
||||
var shader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv");
|
||||
|
||||
var computeBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(shader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
}
|
||||
|
||||
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
|
||||
{
|
||||
if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height)
|
||||
{
|
||||
_texture?.Dispose();
|
||||
|
||||
var info = view.Info;
|
||||
|
||||
if (view.Info.Format.IsBgr())
|
||||
{
|
||||
info = new TextureCreateInfo(info.Width,
|
||||
info.Height,
|
||||
info.Depth,
|
||||
info.Levels,
|
||||
info.Samples,
|
||||
info.BlockWidth,
|
||||
info.BlockHeight,
|
||||
info.BytesPerPixel,
|
||||
info.Format,
|
||||
info.DepthStencilMode,
|
||||
info.Target,
|
||||
info.SwizzleB,
|
||||
info.SwizzleG,
|
||||
info.SwizzleR,
|
||||
info.SwizzleA);
|
||||
}
|
||||
_texture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
||||
}
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
_pipeline.SetProgram(_shaderProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
|
||||
|
||||
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
||||
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||
|
||||
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
||||
|
||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||
|
||||
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
||||
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
||||
|
||||
_pipeline.SetImage(0, _texture, GAL.Format.R8G8B8A8Unorm);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
_renderer.BufferManager.Delete(bufferHandle);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
|
||||
return _texture;
|
||||
}
|
||||
}
|
||||
}
|
10
src/Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs
Normal file
10
src/Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal interface IPostProcessingEffect : IDisposable
|
||||
{
|
||||
const int LocalGroupSize = 64;
|
||||
TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height);
|
||||
}
|
||||
}
|
20
src/Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs
Normal file
20
src/Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal interface IScalingFilter : IDisposable
|
||||
{
|
||||
float Level { get; set; }
|
||||
void Run(
|
||||
TextureView view,
|
||||
CommandBufferScoped cbs,
|
||||
Auto<DisposableImageView> destinationTexture,
|
||||
Format format,
|
||||
int width,
|
||||
int height,
|
||||
Extent2D source,
|
||||
Extent2D destination);
|
||||
}
|
||||
}
|
3945
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl
Normal file
3945
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv
Normal file
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv
Normal file
Binary file not shown.
3904
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.glsl
Normal file
3904
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv
Normal file
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv
Normal file
Binary file not shown.
1177
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.glsl
Normal file
1177
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv
Normal file
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv
Normal file
Binary file not shown.
1404
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.glsl
Normal file
1404
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv
Normal file
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv
Normal file
Binary file not shown.
1402
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.glsl
Normal file
1402
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv
Normal file
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv
Normal file
Binary file not shown.
1403
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.glsl
Normal file
1403
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv
Normal file
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv
Normal file
Binary file not shown.
15
src/Ryujinx.Graphics.Vulkan/Effects/SmaaConstants.cs
Normal file
15
src/Ryujinx.Graphics.Vulkan/Effects/SmaaConstants.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
internal struct SmaaConstants
|
||||
{
|
||||
public int QualityLow;
|
||||
public int QualityMedium;
|
||||
public int QualityHigh;
|
||||
public int QualityUltra;
|
||||
public float Width;
|
||||
public float Height;
|
||||
}
|
||||
}
|
289
src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
Normal file
289
src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs
Normal file
|
@ -0,0 +1,289 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Format = Ryujinx.Graphics.GAL.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal partial class SmaaPostProcessingEffect : IPostProcessingEffect
|
||||
{
|
||||
public const int AreaWidth = 160;
|
||||
public const int AreaHeight = 560;
|
||||
public const int SearchWidth = 64;
|
||||
public const int SearchHeight = 16;
|
||||
|
||||
private readonly VulkanRenderer _renderer;
|
||||
private ISampler _samplerLinear;
|
||||
private SmaaConstants _specConstants;
|
||||
private ShaderCollection _edgeProgram;
|
||||
private ShaderCollection _blendProgram;
|
||||
private ShaderCollection _neighbourProgram;
|
||||
|
||||
private PipelineHelperShader _pipeline;
|
||||
|
||||
private TextureView _outputTexture;
|
||||
private TextureView _edgeOutputTexture;
|
||||
private TextureView _blendOutputTexture;
|
||||
private TextureView _areaTexture;
|
||||
private TextureView _searchTexture;
|
||||
private Device _device;
|
||||
private bool _recreatePipelines;
|
||||
private int _quality;
|
||||
|
||||
public SmaaPostProcessingEffect(VulkanRenderer renderer, Device device, int quality)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
_quality = quality;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public int Quality
|
||||
{
|
||||
get => _quality;
|
||||
set
|
||||
{
|
||||
_quality = value;
|
||||
|
||||
_recreatePipelines = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DeletePipelines();
|
||||
_samplerLinear?.Dispose();
|
||||
_outputTexture?.Dispose();
|
||||
_edgeOutputTexture?.Dispose();
|
||||
_blendOutputTexture?.Dispose();
|
||||
_areaTexture?.Dispose();
|
||||
_searchTexture?.Dispose();
|
||||
}
|
||||
|
||||
private unsafe void RecreateShaders(int width, int height)
|
||||
{
|
||||
_recreatePipelines = false;
|
||||
|
||||
DeletePipelines();
|
||||
_pipeline = new PipelineHelperShader(_renderer, _device);
|
||||
|
||||
_pipeline.Initialize();
|
||||
|
||||
var edgeShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv");
|
||||
var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv");
|
||||
var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv");
|
||||
|
||||
var edgeBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
|
||||
var blendBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1, 3, 4 },
|
||||
new[] { 0 });
|
||||
|
||||
var neighbourBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1, 3 },
|
||||
new[] { 0 });
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_specConstants = new SmaaConstants()
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
QualityLow = Quality == 0 ? 1 : 0,
|
||||
QualityMedium = Quality == 1 ? 1 : 0,
|
||||
QualityHigh = Quality == 2 ? 1 : 0,
|
||||
QualityUltra = Quality == 3 ? 1 : 0,
|
||||
};
|
||||
|
||||
var specInfo = new SpecDescription(
|
||||
(0, SpecConstType.Int32),
|
||||
(1, SpecConstType.Int32),
|
||||
(2, SpecConstType.Int32),
|
||||
(3, SpecConstType.Int32),
|
||||
(4, SpecConstType.Float32),
|
||||
(5, SpecConstType.Float32));
|
||||
|
||||
_edgeProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(edgeShader, edgeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, new[] { specInfo });
|
||||
|
||||
_blendProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(blendShader, blendBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, new[] { specInfo });
|
||||
|
||||
_neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(neighbourShader, neighbourBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, new[] { specInfo });
|
||||
}
|
||||
|
||||
public void DeletePipelines()
|
||||
{
|
||||
_pipeline?.Dispose();
|
||||
_edgeProgram?.Dispose();
|
||||
_blendProgram?.Dispose();
|
||||
_neighbourProgram?.Dispose();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
var areaInfo = new TextureCreateInfo(AreaWidth,
|
||||
AreaHeight,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
Format.R8G8Unorm,
|
||||
DepthStencilMode.Depth,
|
||||
Target.Texture2D,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha);
|
||||
|
||||
var searchInfo = new TextureCreateInfo(SearchWidth,
|
||||
SearchHeight,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
Format.R8Unorm,
|
||||
DepthStencilMode.Depth,
|
||||
Target.Texture2D,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha);
|
||||
|
||||
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin");
|
||||
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin");
|
||||
|
||||
_areaTexture = _renderer.CreateTexture(areaInfo, 1) as TextureView;
|
||||
_searchTexture = _renderer.CreateTexture(searchInfo, 1) as TextureView;
|
||||
|
||||
_areaTexture.SetData(areaTexture);
|
||||
_searchTexture.SetData(searchTexture);
|
||||
}
|
||||
|
||||
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
|
||||
{
|
||||
if (_recreatePipelines || _outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height)
|
||||
{
|
||||
RecreateShaders(view.Width, view.Height);
|
||||
_outputTexture?.Dispose();
|
||||
_edgeOutputTexture?.Dispose();
|
||||
_blendOutputTexture?.Dispose();
|
||||
|
||||
var info = view.Info;
|
||||
|
||||
if (view.Info.Format.IsBgr())
|
||||
{
|
||||
info = new TextureCreateInfo(info.Width,
|
||||
info.Height,
|
||||
info.Depth,
|
||||
info.Levels,
|
||||
info.Samples,
|
||||
info.BlockWidth,
|
||||
info.BlockHeight,
|
||||
info.BytesPerPixel,
|
||||
info.Format,
|
||||
info.DepthStencilMode,
|
||||
info.Target,
|
||||
info.SwizzleB,
|
||||
info.SwizzleG,
|
||||
info.SwizzleR,
|
||||
info.SwizzleA);
|
||||
}
|
||||
|
||||
_outputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
||||
_edgeOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
||||
_blendOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
||||
}
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
|
||||
Clear(_edgeOutputTexture);
|
||||
Clear(_blendOutputTexture);
|
||||
|
||||
_renderer.Pipeline.TextureBarrier();
|
||||
|
||||
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
||||
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
||||
|
||||
// Edge pass
|
||||
_pipeline.SetProgram(_edgeProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
|
||||
_pipeline.Specialize(_specConstants);
|
||||
|
||||
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
||||
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||
|
||||
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||
_pipeline.SetImage(0, _edgeOutputTexture, GAL.Format.R8G8B8A8Unorm);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
// Blend pass
|
||||
_pipeline.SetProgram(_blendProgram);
|
||||
_pipeline.Specialize(_specConstants);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
|
||||
_pipeline.SetImage(0, _blendOutputTexture, GAL.Format.R8G8B8A8Unorm);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
// Neighbour pass
|
||||
_pipeline.SetProgram(_neighbourProgram);
|
||||
_pipeline.Specialize(_specConstants);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
|
||||
_pipeline.SetImage(0, _outputTexture, GAL.Format.R8G8B8A8Unorm);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
|
||||
_renderer.BufferManager.Delete(bufferHandle);
|
||||
|
||||
return _outputTexture;
|
||||
}
|
||||
|
||||
private void Clear(TextureView texture)
|
||||
{
|
||||
Span<uint> colorMasks = stackalloc uint[1];
|
||||
|
||||
colorMasks[0] = 0xf;
|
||||
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
||||
|
||||
scissors[0] = new Rectangle<int>(0, 0, texture.Width, texture.Height);
|
||||
|
||||
_pipeline.SetRenderTarget(texture.GetImageViewForAttachment(), (uint)texture.Width, (uint)texture.Height, false, texture.VkFormat);
|
||||
_pipeline.SetRenderTargetColorMasks(colorMasks);
|
||||
_pipeline.SetScissors(scissors);
|
||||
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
|
||||
}
|
||||
}
|
||||
}
|
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin
Normal file
BIN
src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin
Normal file
Binary file not shown.
Binary file not shown.
374
src/Ryujinx.Graphics.Vulkan/EnumConversion.cs
Normal file
374
src/Ryujinx.Graphics.Vulkan/EnumConversion.cs
Normal file
|
@ -0,0 +1,374 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
static class EnumConversion
|
||||
{
|
||||
public static ShaderStageFlags Convert(this ShaderStage stage)
|
||||
{
|
||||
return stage switch
|
||||
{
|
||||
ShaderStage.Vertex => ShaderStageFlags.VertexBit,
|
||||
ShaderStage.Geometry => ShaderStageFlags.GeometryBit,
|
||||
ShaderStage.TessellationControl => ShaderStageFlags.TessellationControlBit,
|
||||
ShaderStage.TessellationEvaluation => ShaderStageFlags.TessellationEvaluationBit,
|
||||
ShaderStage.Fragment => ShaderStageFlags.FragmentBit,
|
||||
ShaderStage.Compute => ShaderStageFlags.ComputeBit,
|
||||
_ => LogInvalidAndReturn(stage, nameof(ShaderStage), (ShaderStageFlags)0)
|
||||
};
|
||||
}
|
||||
|
||||
public static PipelineStageFlags ConvertToPipelineStageFlags(this ShaderStage stage)
|
||||
{
|
||||
return stage switch
|
||||
{
|
||||
ShaderStage.Vertex => PipelineStageFlags.VertexShaderBit,
|
||||
ShaderStage.Geometry => PipelineStageFlags.GeometryShaderBit,
|
||||
ShaderStage.TessellationControl => PipelineStageFlags.TessellationControlShaderBit,
|
||||
ShaderStage.TessellationEvaluation => PipelineStageFlags.TessellationEvaluationShaderBit,
|
||||
ShaderStage.Fragment => PipelineStageFlags.FragmentShaderBit,
|
||||
ShaderStage.Compute => PipelineStageFlags.ComputeShaderBit,
|
||||
_ => 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.AdvancedBlendOp op)
|
||||
{
|
||||
return op switch
|
||||
{
|
||||
GAL.AdvancedBlendOp.Zero => Silk.NET.Vulkan.BlendOp.ZeroExt,
|
||||
GAL.AdvancedBlendOp.Src => Silk.NET.Vulkan.BlendOp.SrcExt,
|
||||
GAL.AdvancedBlendOp.Dst => Silk.NET.Vulkan.BlendOp.DstExt,
|
||||
GAL.AdvancedBlendOp.SrcOver => Silk.NET.Vulkan.BlendOp.SrcOverExt,
|
||||
GAL.AdvancedBlendOp.DstOver => Silk.NET.Vulkan.BlendOp.DstOverExt,
|
||||
GAL.AdvancedBlendOp.SrcIn => Silk.NET.Vulkan.BlendOp.SrcInExt,
|
||||
GAL.AdvancedBlendOp.DstIn => Silk.NET.Vulkan.BlendOp.DstInExt,
|
||||
GAL.AdvancedBlendOp.SrcOut => Silk.NET.Vulkan.BlendOp.SrcOutExt,
|
||||
GAL.AdvancedBlendOp.DstOut => Silk.NET.Vulkan.BlendOp.DstOutExt,
|
||||
GAL.AdvancedBlendOp.SrcAtop => Silk.NET.Vulkan.BlendOp.SrcAtopExt,
|
||||
GAL.AdvancedBlendOp.DstAtop => Silk.NET.Vulkan.BlendOp.DstAtopExt,
|
||||
GAL.AdvancedBlendOp.Xor => Silk.NET.Vulkan.BlendOp.XorExt,
|
||||
GAL.AdvancedBlendOp.Plus => Silk.NET.Vulkan.BlendOp.PlusExt,
|
||||
GAL.AdvancedBlendOp.PlusClamped => Silk.NET.Vulkan.BlendOp.PlusClampedExt,
|
||||
GAL.AdvancedBlendOp.PlusClampedAlpha => Silk.NET.Vulkan.BlendOp.PlusClampedAlphaExt,
|
||||
GAL.AdvancedBlendOp.PlusDarker => Silk.NET.Vulkan.BlendOp.PlusDarkerExt,
|
||||
GAL.AdvancedBlendOp.Multiply => Silk.NET.Vulkan.BlendOp.MultiplyExt,
|
||||
GAL.AdvancedBlendOp.Screen => Silk.NET.Vulkan.BlendOp.ScreenExt,
|
||||
GAL.AdvancedBlendOp.Overlay => Silk.NET.Vulkan.BlendOp.OverlayExt,
|
||||
GAL.AdvancedBlendOp.Darken => Silk.NET.Vulkan.BlendOp.DarkenExt,
|
||||
GAL.AdvancedBlendOp.Lighten => Silk.NET.Vulkan.BlendOp.LightenExt,
|
||||
GAL.AdvancedBlendOp.ColorDodge => Silk.NET.Vulkan.BlendOp.ColordodgeExt,
|
||||
GAL.AdvancedBlendOp.ColorBurn => Silk.NET.Vulkan.BlendOp.ColorburnExt,
|
||||
GAL.AdvancedBlendOp.HardLight => Silk.NET.Vulkan.BlendOp.HardlightExt,
|
||||
GAL.AdvancedBlendOp.SoftLight => Silk.NET.Vulkan.BlendOp.SoftlightExt,
|
||||
GAL.AdvancedBlendOp.Difference => Silk.NET.Vulkan.BlendOp.DifferenceExt,
|
||||
GAL.AdvancedBlendOp.Minus => Silk.NET.Vulkan.BlendOp.MinusExt,
|
||||
GAL.AdvancedBlendOp.MinusClamped => Silk.NET.Vulkan.BlendOp.MinusClampedExt,
|
||||
GAL.AdvancedBlendOp.Exclusion => Silk.NET.Vulkan.BlendOp.ExclusionExt,
|
||||
GAL.AdvancedBlendOp.Contrast => Silk.NET.Vulkan.BlendOp.ContrastExt,
|
||||
GAL.AdvancedBlendOp.Invert => Silk.NET.Vulkan.BlendOp.InvertExt,
|
||||
GAL.AdvancedBlendOp.InvertRGB => Silk.NET.Vulkan.BlendOp.InvertRgbExt,
|
||||
GAL.AdvancedBlendOp.InvertOvg => Silk.NET.Vulkan.BlendOp.InvertOvgExt,
|
||||
GAL.AdvancedBlendOp.LinearDodge => Silk.NET.Vulkan.BlendOp.LineardodgeExt,
|
||||
GAL.AdvancedBlendOp.LinearBurn => Silk.NET.Vulkan.BlendOp.LinearburnExt,
|
||||
GAL.AdvancedBlendOp.VividLight => Silk.NET.Vulkan.BlendOp.VividlightExt,
|
||||
GAL.AdvancedBlendOp.LinearLight => Silk.NET.Vulkan.BlendOp.LinearlightExt,
|
||||
GAL.AdvancedBlendOp.PinLight => Silk.NET.Vulkan.BlendOp.PinlightExt,
|
||||
GAL.AdvancedBlendOp.HardMix => Silk.NET.Vulkan.BlendOp.HardmixExt,
|
||||
GAL.AdvancedBlendOp.Red => Silk.NET.Vulkan.BlendOp.RedExt,
|
||||
GAL.AdvancedBlendOp.Green => Silk.NET.Vulkan.BlendOp.GreenExt,
|
||||
GAL.AdvancedBlendOp.Blue => Silk.NET.Vulkan.BlendOp.BlueExt,
|
||||
GAL.AdvancedBlendOp.HslHue => Silk.NET.Vulkan.BlendOp.HslHueExt,
|
||||
GAL.AdvancedBlendOp.HslSaturation => Silk.NET.Vulkan.BlendOp.HslSaturationExt,
|
||||
GAL.AdvancedBlendOp.HslColor => Silk.NET.Vulkan.BlendOp.HslColorExt,
|
||||
GAL.AdvancedBlendOp.HslLuminosity => Silk.NET.Vulkan.BlendOp.HslLuminosityExt,
|
||||
_ => LogInvalidAndReturn(op, nameof(GAL.AdvancedBlendOp), Silk.NET.Vulkan.BlendOp.Add)
|
||||
};
|
||||
}
|
||||
|
||||
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.BlendOverlapEXT Convert(this GAL.AdvancedBlendOverlap overlap)
|
||||
{
|
||||
return overlap switch
|
||||
{
|
||||
GAL.AdvancedBlendOverlap.Uncorrelated => Silk.NET.Vulkan.BlendOverlapEXT.UncorrelatedExt,
|
||||
GAL.AdvancedBlendOverlap.Disjoint => Silk.NET.Vulkan.BlendOverlapEXT.DisjointExt,
|
||||
GAL.AdvancedBlendOverlap.Conjoint => Silk.NET.Vulkan.BlendOverlapEXT.ConjointExt,
|
||||
_ => LogInvalidAndReturn(overlap, nameof(GAL.AdvancedBlendOverlap), Silk.NET.Vulkan.BlendOverlapEXT.UncorrelatedExt)
|
||||
};
|
||||
}
|
||||
|
||||
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.BackBit,
|
||||
Face.Front => CullModeFlags.FrontBit,
|
||||
Face.FrontAndBack => CullModeFlags.FrontAndBack,
|
||||
_ => LogInvalidAndReturn(face, nameof(Face), CullModeFlags.BackBit)
|
||||
};
|
||||
}
|
||||
|
||||
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.Polygon => Silk.NET.Vulkan.PrimitiveTopology.TriangleFan,
|
||||
GAL.PrimitiveTopology.Quads => throw new NotSupportedException("Quad topology is not available in Vulkan."),
|
||||
GAL.PrimitiveTopology.QuadStrip => throw new NotSupportedException("QuadStrip topology is not available in Vulkan."),
|
||||
_ => 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.Type1D,
|
||||
Target.Texture2D or
|
||||
Target.Texture2DArray or
|
||||
Target.Texture2DMultisample or
|
||||
Target.Cubemap or
|
||||
Target.CubemapArray => ImageType.Type2D,
|
||||
Target.Texture3D => ImageType.Type3D,
|
||||
_ => LogInvalidAndReturn(target, nameof(Target), ImageType.Type2D)
|
||||
};
|
||||
}
|
||||
|
||||
public static ImageViewType ConvertView(this Target target)
|
||||
{
|
||||
return target switch
|
||||
{
|
||||
Target.Texture1D => ImageViewType.Type1D,
|
||||
Target.Texture2D or Target.Texture2DMultisample => ImageViewType.Type2D,
|
||||
Target.Texture3D => ImageViewType.Type3D,
|
||||
Target.Texture1DArray => ImageViewType.Type1DArray,
|
||||
Target.Texture2DArray => ImageViewType.Type2DArray,
|
||||
Target.Cubemap => ImageViewType.TypeCube,
|
||||
Target.CubemapArray => ImageViewType.TypeCubeArray,
|
||||
_ => LogInvalidAndReturn(target, nameof(Target), ImageViewType.Type2D)
|
||||
};
|
||||
}
|
||||
|
||||
public static ImageAspectFlags ConvertAspectFlags(this GAL.Format format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.DepthBit,
|
||||
GAL.Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
GAL.Format.D24UnormS8Uint or
|
||||
GAL.Format.D32FloatS8Uint or
|
||||
GAL.Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit,
|
||||
_ => ImageAspectFlags.ColorBit
|
||||
};
|
||||
}
|
||||
|
||||
public static ImageAspectFlags ConvertAspectFlags(this GAL.Format format, DepthStencilMode depthStencilMode)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
GAL.Format.D16Unorm or GAL.Format.D32Float => ImageAspectFlags.DepthBit,
|
||||
GAL.Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
GAL.Format.D24UnormS8Uint or
|
||||
GAL.Format.D32FloatS8Uint or
|
||||
GAL.Format.S8UintD24Unorm => depthStencilMode == DepthStencilMode.Stencil ? ImageAspectFlags.StencilBit : ImageAspectFlags.DepthBit,
|
||||
_ => ImageAspectFlags.ColorBit
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
30
src/Ryujinx.Graphics.Vulkan/FenceHelper.cs
Normal file
30
src/Ryujinx.Graphics.Vulkan/FenceHelper.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
79
src/Ryujinx.Graphics.Vulkan/FenceHolder.cs
Normal file
79
src/Ryujinx.Graphics.Vulkan/FenceHolder.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
164
src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
Normal file
164
src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs
Normal file
|
@ -0,0 +1,164 @@
|
|||
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[] _bufferTable;
|
||||
private readonly FormatFeatureFlags[] _optimalTable;
|
||||
|
||||
private readonly Vk _api;
|
||||
private readonly PhysicalDevice _physicalDevice;
|
||||
|
||||
public FormatCapabilities(Vk api, PhysicalDevice physicalDevice)
|
||||
{
|
||||
_api = api;
|
||||
_physicalDevice = physicalDevice;
|
||||
|
||||
int totalFormats = Enum.GetNames(typeof(GAL.Format)).Length;
|
||||
|
||||
_bufferTable = new FormatFeatureFlags[totalFormats];
|
||||
_optimalTable = new FormatFeatureFlags[totalFormats];
|
||||
}
|
||||
|
||||
public bool BufferFormatsSupport(FormatFeatureFlags flags, params GAL.Format[] formats)
|
||||
{
|
||||
foreach (GAL.Format format in formats)
|
||||
{
|
||||
if (!BufferFormatSupports(flags, format))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OptimalFormatsSupport(FormatFeatureFlags flags, params GAL.Format[] formats)
|
||||
{
|
||||
foreach (GAL.Format format in formats)
|
||||
{
|
||||
if (!OptimalFormatSupports(flags, format))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool BufferFormatSupports(FormatFeatureFlags flags, GAL.Format format)
|
||||
{
|
||||
var formatFeatureFlags = _bufferTable[(int)format];
|
||||
|
||||
if (formatFeatureFlags == 0)
|
||||
{
|
||||
_api.GetPhysicalDeviceFormatProperties(_physicalDevice, FormatTable.GetFormat(format), out var fp);
|
||||
formatFeatureFlags = fp.BufferFeatures;
|
||||
_bufferTable[(int)format] = formatFeatureFlags;
|
||||
}
|
||||
|
||||
return (formatFeatureFlags & flags) == flags;
|
||||
}
|
||||
|
||||
public bool OptimalFormatSupports(FormatFeatureFlags flags, GAL.Format format)
|
||||
{
|
||||
var formatFeatureFlags = _optimalTable[(int)format];
|
||||
|
||||
if (formatFeatureFlags == 0)
|
||||
{
|
||||
_api.GetPhysicalDeviceFormatProperties(_physicalDevice, FormatTable.GetFormat(format), out var fp);
|
||||
formatFeatureFlags = fp.OptimalTilingFeatures;
|
||||
_optimalTable[(int)format] = formatFeatureFlags;
|
||||
}
|
||||
|
||||
return (formatFeatureFlags & flags) == flags;
|
||||
}
|
||||
|
||||
public VkFormat ConvertToVkFormat(GAL.Format srcFormat)
|
||||
{
|
||||
var format = FormatTable.GetFormat(srcFormat);
|
||||
|
||||
var requiredFeatures = FormatFeatureFlags.SampledImageBit |
|
||||
FormatFeatureFlags.TransferSrcBit |
|
||||
FormatFeatureFlags.TransferDstBit;
|
||||
|
||||
if (srcFormat.IsDepthOrStencil())
|
||||
{
|
||||
requiredFeatures |= FormatFeatureFlags.DepthStencilAttachmentBit;
|
||||
}
|
||||
else if (srcFormat.IsRtColorCompatible())
|
||||
{
|
||||
requiredFeatures |= FormatFeatureFlags.ColorAttachmentBit;
|
||||
}
|
||||
|
||||
if (srcFormat.IsImageCompatible())
|
||||
{
|
||||
requiredFeatures |= FormatFeatureFlags.StorageImageBit;
|
||||
}
|
||||
|
||||
if (!OptimalFormatSupports(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 if (srcFormat == GAL.Format.R4G4B4A4Unorm)
|
||||
{
|
||||
format = VkFormat.R4G4B4A4UnormPack16;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Format {srcFormat} is not supported by the host.");
|
||||
}
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
public VkFormat ConvertToVertexVkFormat(GAL.Format srcFormat)
|
||||
{
|
||||
var format = FormatTable.GetFormat(srcFormat);
|
||||
|
||||
if (!BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, srcFormat) ||
|
||||
(IsRGB16IntFloat(srcFormat) && VulkanConfiguration.ForceRGB16IntFloatUnsupported))
|
||||
{
|
||||
// The format is not supported. Can we convert it to an alternative format?
|
||||
switch (srcFormat)
|
||||
{
|
||||
case GAL.Format.R16G16B16Float:
|
||||
format = VkFormat.R16G16B16A16Sfloat;
|
||||
break;
|
||||
case GAL.Format.R16G16B16Sint:
|
||||
format = VkFormat.R16G16B16A16Sint;
|
||||
break;
|
||||
case GAL.Format.R16G16B16Uint:
|
||||
format = VkFormat.R16G16B16A16Uint;
|
||||
break;
|
||||
default:
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Format {srcFormat} is not supported by the host.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
public static bool IsD24S8(GAL.Format format)
|
||||
{
|
||||
return format == GAL.Format.D24UnormS8Uint || format == GAL.Format.S8UintD24Unorm;
|
||||
}
|
||||
|
||||
private static bool IsRGB16IntFloat(GAL.Format format)
|
||||
{
|
||||
return format == GAL.Format.R16G16B16Float ||
|
||||
format == GAL.Format.R16G16B16Sint ||
|
||||
format == GAL.Format.R16G16B16Uint;
|
||||
}
|
||||
}
|
||||
}
|
49
src/Ryujinx.Graphics.Vulkan/FormatConverter.cs
Normal file
49
src/Ryujinx.Graphics.Vulkan/FormatConverter.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
172
src/Ryujinx.Graphics.Vulkan/FormatTable.cs
Normal file
172
src/Ryujinx.Graphics.Vulkan/FormatTable.cs
Normal file
|
@ -0,0 +1,172 @@
|
|||
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.R8G8B8A8Srgb, VkFormat.R8G8B8A8Srgb);
|
||||
Add(Format.R4G4Unorm, VkFormat.R4G4UnormPack8);
|
||||
Add(Format.R4G4B4A4Unorm, VkFormat.A4B4G4R4UnormPack16Ext);
|
||||
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.Etc2RgbUnorm, VkFormat.Etc2R8G8B8UnormBlock);
|
||||
Add(Format.Etc2RgbaUnorm, VkFormat.Etc2R8G8B8A8UnormBlock);
|
||||
Add(Format.Etc2RgbPtaUnorm, VkFormat.Etc2R8G8B8A1UnormBlock);
|
||||
Add(Format.Etc2RgbSrgb, VkFormat.Etc2R8G8B8SrgbBlock);
|
||||
Add(Format.Etc2RgbaSrgb, VkFormat.Etc2R8G8B8A8SrgbBlock);
|
||||
Add(Format.Etc2RgbPtaSrgb, VkFormat.Etc2R8G8B8A1SrgbBlock);
|
||||
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.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.B5G5R5A1Unorm, VkFormat.A1R5G5B5UnormPack16);
|
||||
Add(Format.A1B5G5R5Unorm, VkFormat.R5G5B5A1UnormPack16);
|
||||
Add(Format.B8G8R8A8Unorm, VkFormat.B8G8R8A8Unorm);
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
240
src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
Normal file
240
src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
Normal file
|
@ -0,0 +1,240 @@
|
|||
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 => AttachmentIndices.Length > 0 ? AttachmentIndices[AttachmentIndices.Length - 1] : -1;
|
||||
public bool HasDepthStencil { get; }
|
||||
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
|
||||
|
||||
public FramebufferParams(
|
||||
Device device,
|
||||
Auto<DisposableImageView> view,
|
||||
uint width,
|
||||
uint height,
|
||||
uint samples,
|
||||
bool isDepthStencil,
|
||||
VkFormat format)
|
||||
{
|
||||
_device = device;
|
||||
_attachments = new[] { view };
|
||||
_validColorAttachments = isDepthStencil ? 0u : 1u;
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Layers = 1;
|
||||
|
||||
AttachmentSamples = new[] { samples };
|
||||
AttachmentFormats = new[] { format };
|
||||
AttachmentIndices = isDepthStencil ? Array.Empty<int>() : 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[colorsCount];
|
||||
|
||||
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 ComponentType GetAttachmentComponentType(int index)
|
||||
{
|
||||
if (_colors != null && (uint)index < _colors.Length)
|
||||
{
|
||||
var format = _colors[index].Info.Format;
|
||||
|
||||
if (format.IsSint())
|
||||
{
|
||||
return ComponentType.SignedInteger;
|
||||
}
|
||||
else if (format.IsUint())
|
||||
{
|
||||
return ComponentType.UnsignedInteger;
|
||||
}
|
||||
}
|
||||
|
||||
return ComponentType.Float;
|
||||
}
|
||||
|
||||
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 layerCount)
|
||||
{
|
||||
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, (uint)layerCount);
|
||||
}
|
||||
|
||||
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.ColorAttachmentWriteBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
}
|
||||
|
||||
_depthStencil?.Storage.SetModification(
|
||||
AccessFlags.DepthStencilAttachmentWriteBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
|
||||
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
|
||||
{
|
||||
if (_colors != null)
|
||||
{
|
||||
int realIndex = Array.IndexOf(AttachmentIndices, index);
|
||||
|
||||
if (realIndex != -1)
|
||||
{
|
||||
_colors[realIndex].Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertClearBarrierDS(CommandBufferScoped cbs)
|
||||
{
|
||||
_depthStencil?.Storage?.InsertReadToWriteBarrier(cbs, AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit);
|
||||
}
|
||||
}
|
||||
}
|
120
src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
Normal file
120
src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
[Flags]
|
||||
enum PortabilitySubsetFlags
|
||||
{
|
||||
None = 0,
|
||||
|
||||
NoTriangleFans = 1,
|
||||
NoPointMode = 1 << 1,
|
||||
No3DImageView = 1 << 2,
|
||||
NoLodBias = 1 << 3
|
||||
}
|
||||
|
||||
readonly struct HardwareCapabilities
|
||||
{
|
||||
public readonly bool SupportsIndexTypeUint8;
|
||||
public readonly bool SupportsCustomBorderColor;
|
||||
public readonly bool SupportsBlendEquationAdvanced;
|
||||
public readonly bool SupportsBlendEquationAdvancedCorrelatedOverlap;
|
||||
public readonly bool SupportsBlendEquationAdvancedNonPreMultipliedSrcColor;
|
||||
public readonly bool SupportsBlendEquationAdvancedNonPreMultipliedDstColor;
|
||||
public readonly bool SupportsIndirectParameters;
|
||||
public readonly bool SupportsFragmentShaderInterlock;
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
public readonly bool SupportsSubgroupSizeControl;
|
||||
public readonly bool SupportsShaderInt8;
|
||||
public readonly bool SupportsShaderStencilExport;
|
||||
public readonly bool SupportsConditionalRendering;
|
||||
public readonly bool SupportsExtendedDynamicState;
|
||||
public readonly bool SupportsMultiView;
|
||||
public readonly bool SupportsNullDescriptors;
|
||||
public readonly bool SupportsPushDescriptors;
|
||||
public readonly bool SupportsPrimitiveTopologyListRestart;
|
||||
public readonly bool SupportsPrimitiveTopologyPatchListRestart;
|
||||
public readonly bool SupportsTransformFeedback;
|
||||
public readonly bool SupportsTransformFeedbackQueries;
|
||||
public readonly bool SupportsPreciseOcclusionQueries;
|
||||
public readonly bool SupportsPipelineStatisticsQuery;
|
||||
public readonly bool SupportsGeometryShader;
|
||||
public readonly bool SupportsViewportArray2;
|
||||
public readonly uint MinSubgroupSize;
|
||||
public readonly uint MaxSubgroupSize;
|
||||
public readonly ShaderStageFlags RequiredSubgroupSizeStages;
|
||||
public readonly SampleCountFlags SupportedSampleCounts;
|
||||
public readonly PortabilitySubsetFlags PortabilitySubset;
|
||||
public readonly uint VertexBufferAlignment;
|
||||
public readonly uint SubTexelPrecisionBits;
|
||||
|
||||
public HardwareCapabilities(
|
||||
bool supportsIndexTypeUint8,
|
||||
bool supportsCustomBorderColor,
|
||||
bool supportsBlendEquationAdvanced,
|
||||
bool supportsBlendEquationAdvancedCorrelatedOverlap,
|
||||
bool supportsBlendEquationAdvancedNonPreMultipliedSrcColor,
|
||||
bool supportsBlendEquationAdvancedNonPreMultipliedDstColor,
|
||||
bool supportsIndirectParameters,
|
||||
bool supportsFragmentShaderInterlock,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
bool supportsSubgroupSizeControl,
|
||||
bool supportsShaderInt8,
|
||||
bool supportsShaderStencilExport,
|
||||
bool supportsConditionalRendering,
|
||||
bool supportsExtendedDynamicState,
|
||||
bool supportsMultiView,
|
||||
bool supportsNullDescriptors,
|
||||
bool supportsPushDescriptors,
|
||||
bool supportsPrimitiveTopologyListRestart,
|
||||
bool supportsPrimitiveTopologyPatchListRestart,
|
||||
bool supportsTransformFeedback,
|
||||
bool supportsTransformFeedbackQueries,
|
||||
bool supportsPreciseOcclusionQueries,
|
||||
bool supportsPipelineStatisticsQuery,
|
||||
bool supportsGeometryShader,
|
||||
bool supportsViewportArray2,
|
||||
uint minSubgroupSize,
|
||||
uint maxSubgroupSize,
|
||||
ShaderStageFlags requiredSubgroupSizeStages,
|
||||
SampleCountFlags supportedSampleCounts,
|
||||
PortabilitySubsetFlags portabilitySubset,
|
||||
uint vertexBufferAlignment,
|
||||
uint subTexelPrecisionBits)
|
||||
{
|
||||
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
|
||||
SupportsCustomBorderColor = supportsCustomBorderColor;
|
||||
SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced;
|
||||
SupportsBlendEquationAdvancedCorrelatedOverlap = supportsBlendEquationAdvancedCorrelatedOverlap;
|
||||
SupportsBlendEquationAdvancedNonPreMultipliedSrcColor = supportsBlendEquationAdvancedNonPreMultipliedSrcColor;
|
||||
SupportsBlendEquationAdvancedNonPreMultipliedDstColor = supportsBlendEquationAdvancedNonPreMultipliedDstColor;
|
||||
SupportsIndirectParameters = supportsIndirectParameters;
|
||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
|
||||
SupportsShaderInt8 = supportsShaderInt8;
|
||||
SupportsShaderStencilExport = supportsShaderStencilExport;
|
||||
SupportsConditionalRendering = supportsConditionalRendering;
|
||||
SupportsExtendedDynamicState = supportsExtendedDynamicState;
|
||||
SupportsMultiView = supportsMultiView;
|
||||
SupportsNullDescriptors = supportsNullDescriptors;
|
||||
SupportsPushDescriptors = supportsPushDescriptors;
|
||||
SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart;
|
||||
SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart;
|
||||
SupportsTransformFeedback = supportsTransformFeedback;
|
||||
SupportsTransformFeedbackQueries = supportsTransformFeedbackQueries;
|
||||
SupportsPreciseOcclusionQueries = supportsPreciseOcclusionQueries;
|
||||
SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery;
|
||||
SupportsGeometryShader = supportsGeometryShader;
|
||||
SupportsViewportArray2 = supportsViewportArray2;
|
||||
MinSubgroupSize = minSubgroupSize;
|
||||
MaxSubgroupSize = maxSubgroupSize;
|
||||
RequiredSubgroupSizeStages = requiredSubgroupSizeStages;
|
||||
SupportedSampleCounts = supportedSampleCounts;
|
||||
PortabilitySubset = portabilitySubset;
|
||||
VertexBufferAlignment = vertexBufferAlignment;
|
||||
SubTexelPrecisionBits = subTexelPrecisionBits;
|
||||
}
|
||||
}
|
||||
}
|
112
src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs
Normal file
112
src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs
Normal file
|
@ -0,0 +1,112 @@
|
|||
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 int Hash;
|
||||
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()
|
||||
{
|
||||
Hash = key.GetHashCode(),
|
||||
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.Hash == hashCode && entry.Key.Equals(ref key))
|
||||
{
|
||||
value = entry.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
1683
src/Ryujinx.Graphics.Vulkan/HelperShader.cs
Normal file
1683
src/Ryujinx.Graphics.Vulkan/HelperShader.cs
Normal file
File diff suppressed because it is too large
Load diff
123
src/Ryujinx.Graphics.Vulkan/IdList.cs
Normal file
123
src/Ryujinx.Graphics.Vulkan/IdList.cs
Normal file
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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
|
||||
{
|
||||
if ((uint)id < (uint)_list.Count)
|
||||
{
|
||||
value = _list[id];
|
||||
return value != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
139
src/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
Normal file
139
src/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
Normal file
|
@ -0,0 +1,139 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal class IndexBufferPattern : IDisposable
|
||||
{
|
||||
public int PrimitiveVertices { get; }
|
||||
public int PrimitiveVerticesOut { get; }
|
||||
public int BaseIndex { get; }
|
||||
public int[] OffsetIndex { get; }
|
||||
public int IndexStride { get; }
|
||||
public bool RepeatStart { get; }
|
||||
|
||||
private VulkanRenderer _gd;
|
||||
private int _currentSize;
|
||||
private BufferHandle _repeatingBuffer;
|
||||
|
||||
public IndexBufferPattern(VulkanRenderer gd,
|
||||
int primitiveVertices,
|
||||
int primitiveVerticesOut,
|
||||
int baseIndex,
|
||||
int[] offsetIndex,
|
||||
int indexStride,
|
||||
bool repeatStart)
|
||||
{
|
||||
PrimitiveVertices = primitiveVertices;
|
||||
PrimitiveVerticesOut = primitiveVerticesOut;
|
||||
BaseIndex = baseIndex;
|
||||
OffsetIndex = offsetIndex;
|
||||
IndexStride = indexStride;
|
||||
RepeatStart = repeatStart;
|
||||
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
public int GetPrimitiveCount(int vertexCount)
|
||||
{
|
||||
return Math.Max(0, (vertexCount - BaseIndex) / IndexStride);
|
||||
}
|
||||
|
||||
public int GetConvertedCount(int indexCount)
|
||||
{
|
||||
int primitiveCount = GetPrimitiveCount(indexCount);
|
||||
return primitiveCount * OffsetIndex.Length;
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetIndexMapping(int indexCount)
|
||||
{
|
||||
int primitiveCount = GetPrimitiveCount(indexCount);
|
||||
int index = BaseIndex;
|
||||
|
||||
for (int i = 0; i < primitiveCount; i++)
|
||||
{
|
||||
if (RepeatStart)
|
||||
{
|
||||
// Used for triangle fan
|
||||
yield return 0;
|
||||
}
|
||||
|
||||
for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
|
||||
{
|
||||
yield return index + OffsetIndex[j];
|
||||
}
|
||||
|
||||
index += IndexStride;
|
||||
}
|
||||
}
|
||||
|
||||
public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
|
||||
{
|
||||
int primitiveCount = GetPrimitiveCount(vertexCount);
|
||||
indexCount = primitiveCount * PrimitiveVerticesOut;
|
||||
|
||||
int expectedSize = primitiveCount * OffsetIndex.Length;
|
||||
|
||||
if (expectedSize <= _currentSize && _repeatingBuffer != BufferHandle.Null)
|
||||
{
|
||||
return _repeatingBuffer;
|
||||
}
|
||||
|
||||
// Expand the repeating pattern to the number of requested primitives.
|
||||
BufferHandle newBuffer = _gd.BufferManager.CreateWithHandle(_gd, expectedSize * sizeof(int));
|
||||
|
||||
// Copy the old data to the new one.
|
||||
if (_repeatingBuffer != BufferHandle.Null)
|
||||
{
|
||||
_gd.Pipeline.CopyBuffer(_repeatingBuffer, newBuffer, 0, 0, _currentSize * sizeof(int));
|
||||
_gd.DeleteBuffer(_repeatingBuffer);
|
||||
}
|
||||
|
||||
_repeatingBuffer = newBuffer;
|
||||
|
||||
// Add the additional repeats on top.
|
||||
int newPrimitives = primitiveCount;
|
||||
int oldPrimitives = (_currentSize) / OffsetIndex.Length;
|
||||
|
||||
int[] newData;
|
||||
|
||||
newPrimitives -= oldPrimitives;
|
||||
newData = new int[expectedSize - _currentSize];
|
||||
|
||||
int outOffset = 0;
|
||||
int index = oldPrimitives * IndexStride + BaseIndex;
|
||||
|
||||
for (int i = 0; i < newPrimitives; i++)
|
||||
{
|
||||
if (RepeatStart)
|
||||
{
|
||||
// Used for triangle fan
|
||||
newData[outOffset++] = 0;
|
||||
}
|
||||
|
||||
for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
|
||||
{
|
||||
newData[outOffset++] = index + OffsetIndex[j];
|
||||
}
|
||||
|
||||
index += IndexStride;
|
||||
}
|
||||
|
||||
_gd.SetBufferData(newBuffer, _currentSize * sizeof(int), MemoryMarshal.Cast<int, byte>(newData));
|
||||
_currentSize = expectedSize;
|
||||
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_repeatingBuffer != BufferHandle.Null)
|
||||
{
|
||||
_gd.DeleteBuffer(_repeatingBuffer);
|
||||
_repeatingBuffer = BufferHandle.Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
161
src/Ryujinx.Graphics.Vulkan/IndexBufferState.cs
Normal file
161
src/Ryujinx.Graphics.Vulkan/IndexBufferState.cs
Normal file
|
@ -0,0 +1,161 @@
|
|||
using Silk.NET.Vulkan;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal struct IndexBufferState
|
||||
{
|
||||
public static IndexBufferState Null => new IndexBufferState(GAL.BufferHandle.Null, 0, 0);
|
||||
|
||||
private readonly int _offset;
|
||||
private readonly int _size;
|
||||
private readonly IndexType _type;
|
||||
|
||||
private readonly GAL.BufferHandle _handle;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public IndexBufferState(GAL.BufferHandle handle, int offset, int size, IndexType type)
|
||||
{
|
||||
_handle = handle;
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_type = type;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public IndexBufferState(GAL.BufferHandle handle, int offset, int size)
|
||||
{
|
||||
_handle = handle;
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_type = IndexType.Uint16;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public void BindIndexBuffer(VulkanRenderer gd, CommandBufferScoped cbs)
|
||||
{
|
||||
Auto<DisposableBuffer> autoBuffer;
|
||||
int offset, size;
|
||||
IndexType type = _type;
|
||||
|
||||
if (_type == IndexType.Uint8Ext && !gd.Capabilities.SupportsIndexTypeUint8)
|
||||
{
|
||||
// Index type is not supported. Convert to I16.
|
||||
autoBuffer = gd.BufferManager.GetBufferI8ToI16(cbs, _handle, _offset, _size);
|
||||
|
||||
type = IndexType.Uint16;
|
||||
offset = 0;
|
||||
size = _size * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int bufferSize);
|
||||
|
||||
if (_offset >= bufferSize)
|
||||
{
|
||||
autoBuffer = null;
|
||||
}
|
||||
|
||||
offset = _offset;
|
||||
size = _size;
|
||||
}
|
||||
|
||||
_buffer = autoBuffer;
|
||||
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, offset, size).Value, (ulong)offset, type);
|
||||
}
|
||||
}
|
||||
|
||||
public void BindConvertedIndexBuffer(
|
||||
VulkanRenderer gd,
|
||||
CommandBufferScoped cbs,
|
||||
int firstIndex,
|
||||
int indexCount,
|
||||
int convertedCount,
|
||||
IndexBufferPattern pattern)
|
||||
{
|
||||
Auto<DisposableBuffer> autoBuffer;
|
||||
|
||||
// Convert the index buffer using the given pattern.
|
||||
int indexSize = GetIndexSize();
|
||||
|
||||
int firstIndexOffset = firstIndex * indexSize;
|
||||
|
||||
autoBuffer = gd.BufferManager.GetBufferTopologyConversion(cbs, _handle, _offset + firstIndexOffset, indexCount * indexSize, pattern, indexSize);
|
||||
|
||||
int size = convertedCount * 4;
|
||||
|
||||
_buffer = autoBuffer;
|
||||
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, 0, size).Value, 0, IndexType.Uint32);
|
||||
}
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> BindConvertedIndexBufferIndirect(
|
||||
VulkanRenderer gd,
|
||||
CommandBufferScoped cbs,
|
||||
GAL.BufferRange indirectBuffer,
|
||||
GAL.BufferRange drawCountBuffer,
|
||||
IndexBufferPattern pattern,
|
||||
bool hasDrawCount,
|
||||
int maxDrawCount,
|
||||
int indirectDataStride)
|
||||
{
|
||||
// Convert the index buffer using the given pattern.
|
||||
int indexSize = GetIndexSize();
|
||||
|
||||
(var indexBufferAuto, var indirectBufferAuto) = gd.BufferManager.GetBufferTopologyConversionIndirect(
|
||||
gd,
|
||||
cbs,
|
||||
new GAL.BufferRange(_handle, _offset, _size),
|
||||
indirectBuffer,
|
||||
drawCountBuffer,
|
||||
pattern,
|
||||
indexSize,
|
||||
hasDrawCount,
|
||||
maxDrawCount,
|
||||
indirectDataStride);
|
||||
|
||||
int convertedCount = pattern.GetConvertedCount(_size / indexSize);
|
||||
int size = convertedCount * 4;
|
||||
|
||||
_buffer = indexBufferAuto;
|
||||
|
||||
if (indexBufferAuto != null)
|
||||
{
|
||||
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, indexBufferAuto.Get(cbs, 0, size).Value, 0, IndexType.Uint32);
|
||||
}
|
||||
|
||||
return indirectBufferAuto;
|
||||
}
|
||||
|
||||
private int GetIndexSize()
|
||||
{
|
||||
return _type switch
|
||||
{
|
||||
IndexType.Uint32 => 4,
|
||||
IndexType.Uint16 => 2,
|
||||
_ => 1,
|
||||
};
|
||||
}
|
||||
|
||||
public bool BoundEquals(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
return _buffer == buffer;
|
||||
}
|
||||
|
||||
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
if (_buffer == from)
|
||||
{
|
||||
_buffer.DecrementReferenceCount();
|
||||
to.IncrementReferenceCount();
|
||||
|
||||
_buffer = to;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
src/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs
Normal file
37
src/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
readonly 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);
|
||||
}
|
||||
}
|
||||
}
|
101
src/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs
Normal file
101
src/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
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 VulkanPhysicalDevice _physicalDevice;
|
||||
private readonly Device _device;
|
||||
private readonly List<MemoryAllocatorBlockList> _blockLists;
|
||||
private readonly int _blockAlignment;
|
||||
|
||||
public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device)
|
||||
{
|
||||
_api = api;
|
||||
_physicalDevice = physicalDevice;
|
||||
_device = device;
|
||||
_blockLists = new List<MemoryAllocatorBlockList>();
|
||||
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / (ulong)_physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount);
|
||||
}
|
||||
|
||||
public MemoryAllocation AllocateDeviceMemory(
|
||||
MemoryRequirements requirements,
|
||||
MemoryPropertyFlags flags = 0,
|
||||
bool isBuffer = false)
|
||||
{
|
||||
int memoryTypeIndex = FindSuitableMemoryTypeIndex(requirements.MemoryTypeBits, flags);
|
||||
if (memoryTypeIndex < 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
bool map = flags.HasFlag(MemoryPropertyFlags.HostVisibleBit);
|
||||
return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map, isBuffer);
|
||||
}
|
||||
|
||||
private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer)
|
||||
{
|
||||
for (int i = 0; i < _blockLists.Count; i++)
|
||||
{
|
||||
var bl = _blockLists[i];
|
||||
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
|
||||
{
|
||||
lock (bl)
|
||||
{
|
||||
return bl.Allocate(size, alignment, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer);
|
||||
_blockLists.Add(newBl);
|
||||
return newBl.Allocate(size, alignment, map);
|
||||
}
|
||||
|
||||
private int FindSuitableMemoryTypeIndex(
|
||||
uint memoryTypeBits,
|
||||
MemoryPropertyFlags flags)
|
||||
{
|
||||
for (int i = 0; i < _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypeCount; i++)
|
||||
{
|
||||
var type = _physicalDevice.PhysicalDeviceMemoryProperties.MemoryTypes[i];
|
||||
|
||||
if ((memoryTypeBits & (1 << i)) != 0)
|
||||
{
|
||||
if (type.PropertyFlags.HasFlag(flags))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static bool IsDeviceMemoryShared(VulkanPhysicalDevice physicalDevice)
|
||||
{
|
||||
for (int i = 0; i < physicalDevice.PhysicalDeviceMemoryProperties.MemoryHeapCount; i++)
|
||||
{
|
||||
if (!physicalDevice.PhysicalDeviceMemoryProperties.MemoryHeaps[i].Flags.HasFlag(MemoryHeapFlags.DeviceLocalBit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _blockLists.Count; i++)
|
||||
{
|
||||
_blockLists[i].Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
282
src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs
Normal file
282
src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs
Normal file
|
@ -0,0 +1,282 @@
|
|||
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 readonly 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<ulong>(range.Offset, 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; }
|
||||
public bool ForBuffer { get; }
|
||||
|
||||
private readonly int _blockAlignment;
|
||||
|
||||
public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer)
|
||||
{
|
||||
_blocks = new List<Block>();
|
||||
_api = api;
|
||||
_device = device;
|
||||
MemoryTypeIndex = memoryTypeIndex;
|
||||
ForBuffer = forBuffer;
|
||||
_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<ulong>(size, (ulong)_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
104
src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs
Normal file
104
src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||
{
|
||||
enum MVKConfigLogLevel : int
|
||||
{
|
||||
None = 0,
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Info = 3,
|
||||
Debug = 4
|
||||
}
|
||||
|
||||
enum MVKConfigTraceVulkanCalls : int
|
||||
{
|
||||
None = 0,
|
||||
Enter = 1,
|
||||
EnterExit = 2,
|
||||
Duration = 3
|
||||
}
|
||||
|
||||
enum MVKConfigAutoGPUCaptureScope : int
|
||||
{
|
||||
None = 0,
|
||||
Device = 1,
|
||||
Frame = 2
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum MVKConfigAdvertiseExtensions : int
|
||||
{
|
||||
All = 0x00000001,
|
||||
MoltenVK = 0x00000002,
|
||||
WSI = 0x00000004,
|
||||
Portability = 0x00000008
|
||||
}
|
||||
|
||||
enum MVKVkSemaphoreSupportStyle : int
|
||||
{
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE = 0,
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS_WHERE_SAFE = 1,
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_METAL_EVENTS = 2,
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_CALLBACK = 3,
|
||||
MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_MAX_ENUM = 0x7FFFFFFF
|
||||
}
|
||||
|
||||
readonly struct Bool32
|
||||
{
|
||||
uint Value { get; }
|
||||
|
||||
public Bool32(uint value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Bool32(bool value)
|
||||
{
|
||||
Value = value ? 1u : 0u;
|
||||
}
|
||||
|
||||
public static implicit operator bool(Bool32 val) => val.Value == 1;
|
||||
public static implicit operator Bool32(bool val) => new Bool32(val);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct MVKConfiguration
|
||||
{
|
||||
public Bool32 DebugMode;
|
||||
public Bool32 ShaderConversionFlipVertexY;
|
||||
public Bool32 SynchronousQueueSubmits;
|
||||
public Bool32 PrefillMetalCommandBuffers;
|
||||
public uint MaxActiveMetalCommandBuffersPerQueue;
|
||||
public Bool32 SupportLargeQueryPools;
|
||||
public Bool32 PresentWithCommandBuffer;
|
||||
public Bool32 SwapchainMagFilterUseNearest;
|
||||
public ulong MetalCompileTimeout;
|
||||
public Bool32 PerformanceTracking;
|
||||
public uint PerformanceLoggingFrameCount;
|
||||
public Bool32 DisplayWatermark;
|
||||
public Bool32 SpecializedQueueFamilies;
|
||||
public Bool32 SwitchSystemGPU;
|
||||
public Bool32 FullImageViewSwizzle;
|
||||
public uint DefaultGPUCaptureScopeQueueFamilyIndex;
|
||||
public uint DefaultGPUCaptureScopeQueueIndex;
|
||||
public Bool32 FastMathEnabled;
|
||||
public MVKConfigLogLevel LogLevel;
|
||||
public MVKConfigTraceVulkanCalls TraceVulkanCalls;
|
||||
public Bool32 ForceLowPowerGPU;
|
||||
public Bool32 SemaphoreUseMTLFence;
|
||||
public MVKVkSemaphoreSupportStyle SemaphoreSupportStyle;
|
||||
public MVKConfigAutoGPUCaptureScope AutoGPUCaptureScope;
|
||||
public IntPtr AutoGPUCaptureOutputFilepath;
|
||||
public Bool32 Texture1DAs2D;
|
||||
public Bool32 PreallocateDescriptors;
|
||||
public Bool32 UseCommandPooling;
|
||||
public Bool32 UseMTLHeap;
|
||||
public Bool32 LogActivityPerformanceInline;
|
||||
public uint ApiVersionToAdvertise;
|
||||
public MVKConfigAdvertiseExtensions AdvertiseExtensions;
|
||||
public Bool32 ResumeLostDevice;
|
||||
public Bool32 UseMetalArgumentBuffers;
|
||||
}
|
||||
}
|
31
src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
Normal file
31
src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.MoltenVK
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public static partial class MVKInitialization
|
||||
{
|
||||
[LibraryImport("libMoltenVK.dylib")]
|
||||
private static partial Result vkGetMoltenVKConfigurationMVK(IntPtr unusedInstance, out MVKConfiguration config, in IntPtr configSize);
|
||||
|
||||
[LibraryImport("libMoltenVK.dylib")]
|
||||
private static partial Result vkSetMoltenVKConfigurationMVK(IntPtr unusedInstance, in MVKConfiguration config, in IntPtr configSize);
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
var configSize = (IntPtr)Marshal.SizeOf<MVKConfiguration>();
|
||||
|
||||
vkGetMoltenVKConfigurationMVK(IntPtr.Zero, out MVKConfiguration config, configSize);
|
||||
|
||||
config.UseMetalArgumentBuffers = true;
|
||||
|
||||
config.SemaphoreSupportStyle = MVKVkSemaphoreSupportStyle.MVK_CONFIG_VK_SEMAPHORE_SUPPORT_STYLE_SINGLE_QUEUE;
|
||||
config.SynchronousQueueSubmits = false;
|
||||
|
||||
vkSetMoltenVKConfigurationMVK(IntPtr.Zero, config, configSize);
|
||||
}
|
||||
}
|
||||
}
|
212
src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
Normal file
212
src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
48
src/Ryujinx.Graphics.Vulkan/NativeArray.cs
Normal file
48
src/Ryujinx.Graphics.Vulkan/NativeArray.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
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> AsSpan()
|
||||
{
|
||||
return new Span<T>(Pointer, Length);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Pointer != null)
|
||||
{
|
||||
Marshal.FreeHGlobal((IntPtr)Pointer);
|
||||
Pointer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
89
src/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs
Normal file
89
src/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
1742
src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
Normal file
1742
src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
Normal file
File diff suppressed because it is too large
Load diff
318
src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
Normal file
318
src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
Normal file
|
@ -0,0 +1,318 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
static class PipelineConverter
|
||||
{
|
||||
private const AccessFlags SubpassSrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ColorAttachmentWriteBit;
|
||||
private const AccessFlags SubpassDstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit | AccessFlags.ShaderReadBit;
|
||||
|
||||
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 = -1;
|
||||
|
||||
for (int i = 0; i < state.AttachmentEnable.Length; i++)
|
||||
{
|
||||
if (state.AttachmentEnable[i])
|
||||
{
|
||||
attachmentFormats[attachmentCount] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]);
|
||||
|
||||
attachmentIndices[attachmentCount++] = i;
|
||||
colorCount++;
|
||||
maxColorAttachmentIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
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(gd.Capabilities.SupportedSampleCounts, (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)
|
||||
{
|
||||
subpass.ColorAttachmentCount = (uint)maxColorAttachmentIndex + 1;
|
||||
subpass.PColorAttachments = &attachmentReferences[0];
|
||||
|
||||
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
|
||||
for (int i = 0; i <= maxColorAttachmentIndex; 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 = CreateSubpassDependency();
|
||||
|
||||
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 SubpassDependency CreateSubpassDependency()
|
||||
{
|
||||
return new SubpassDependency(
|
||||
0,
|
||||
0,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
SubpassSrcAccessMask,
|
||||
SubpassDstAccessMask,
|
||||
0);
|
||||
}
|
||||
|
||||
public unsafe static SubpassDependency2 CreateSubpassDependency2()
|
||||
{
|
||||
return new SubpassDependency2(
|
||||
StructureType.SubpassDependency2,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
SubpassSrcAccessMask,
|
||||
SubpassDstAccessMask,
|
||||
0);
|
||||
}
|
||||
|
||||
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.CullMode = state.CullEnable ? state.CullMode.Convert() : CullModeFlags.None;
|
||||
|
||||
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 = gd.TopologyRemap(state.Topology).Convert();
|
||||
|
||||
int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
||||
int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
|
||||
|
||||
Span<int> vbScalarSizes = stackalloc int[vbCount];
|
||||
|
||||
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,
|
||||
gd.FormatCapabilities.ConvertToVertexVkFormat(attribute.Format),
|
||||
(uint)attribute.Offset);
|
||||
|
||||
if (!attribute.IsZero && bufferIndex < vbCount)
|
||||
{
|
||||
vbScalarSizes[bufferIndex - 1] = Math.Max(attribute.Format.GetScalarSize(), vbScalarSizes[bufferIndex - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
int descriptorIndex = 1;
|
||||
pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
|
||||
|
||||
for (int i = 0; i < vbCount; i++)
|
||||
{
|
||||
var vertexBuffer = state.VertexBuffers[i];
|
||||
|
||||
if (vertexBuffer.Enable)
|
||||
{
|
||||
var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex;
|
||||
|
||||
int alignedStride = vertexBuffer.Stride;
|
||||
|
||||
if (gd.NeedsVertexBufferAlignment(vbScalarSizes[i], out int alignment))
|
||||
{
|
||||
alignedStride = BitUtils.AlignUp(vertexBuffer.Stride, alignment);
|
||||
}
|
||||
|
||||
// TODO: Support divisor > 1
|
||||
pipeline.Internal.VertexBindingDescriptions[descriptorIndex++] = new VertexInputBindingDescription(
|
||||
(uint)i + 1,
|
||||
(uint)alignedStride,
|
||||
inputRate);
|
||||
}
|
||||
}
|
||||
|
||||
pipeline.VertexBindingDescriptionsCount = (uint)descriptorIndex;
|
||||
|
||||
// NOTE: Viewports, Scissors are dynamic.
|
||||
|
||||
for (int i = 0; i < Constants.MaxRenderTargets; i++)
|
||||
{
|
||||
var blend = state.BlendDescriptors[i];
|
||||
|
||||
if (blend.Enable && state.ColorWriteMask[i] != 0)
|
||||
{
|
||||
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]);
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline.Internal.ColorBlendAttachmentState[i] = new PipelineColorBlendAttachmentState(
|
||||
colorWriteMask: (ColorComponentFlags)state.ColorWriteMask[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int attachmentCount = 0;
|
||||
int maxColorAttachmentIndex = -1;
|
||||
|
||||
for (int i = 0; i < Constants.MaxRenderTargets; i++)
|
||||
{
|
||||
if (state.AttachmentEnable[i])
|
||||
{
|
||||
pipeline.Internal.AttachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i]);
|
||||
maxColorAttachmentIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.DepthStencilEnable)
|
||||
{
|
||||
pipeline.Internal.AttachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat);
|
||||
}
|
||||
|
||||
pipeline.ColorBlendAttachmentStateCount = (uint)(maxColorAttachmentIndex + 1);
|
||||
pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
}
|
170
src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs
Normal file
170
src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs
Normal file
|
@ -0,0 +1,170 @@
|
|||
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;
|
||||
|
||||
private Array4<float> _blendConstants;
|
||||
|
||||
public uint ViewportsCount;
|
||||
public Array16<Viewport> Viewports;
|
||||
|
||||
private enum DirtyFlags
|
||||
{
|
||||
None = 0,
|
||||
Blend = 1 << 0,
|
||||
DepthBias = 1 << 1,
|
||||
Scissor = 1 << 2,
|
||||
Stencil = 1 << 3,
|
||||
Viewport = 1 << 4,
|
||||
All = Blend | DepthBias | Scissor | Stencil | Viewport
|
||||
}
|
||||
|
||||
private DirtyFlags _dirty;
|
||||
|
||||
public void SetBlendConstants(float r, float g, float b, float a)
|
||||
{
|
||||
_blendConstants[0] = r;
|
||||
_blendConstants[1] = g;
|
||||
_blendConstants[2] = b;
|
||||
_blendConstants[3] = a;
|
||||
|
||||
_dirty |= DirtyFlags.Blend;
|
||||
}
|
||||
|
||||
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 SetViewports(ref Array16<Viewport> viewports, uint viewportsCount)
|
||||
{
|
||||
Viewports = viewports;
|
||||
ViewportsCount = viewportsCount;
|
||||
|
||||
if (ViewportsCount != 0)
|
||||
{
|
||||
_dirty |= DirtyFlags.Viewport;
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceAllDirty()
|
||||
{
|
||||
_dirty = DirtyFlags.All;
|
||||
}
|
||||
|
||||
public void ReplayIfDirty(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
if (_dirty.HasFlag(DirtyFlags.Blend))
|
||||
{
|
||||
RecordBlend(api, 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 RecordBlend(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetBlendConstants(commandBuffer, _blendConstants.AsSpan());
|
||||
}
|
||||
|
||||
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.AsSpan());
|
||||
}
|
||||
|
||||
private void RecordStencilMasks(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backCompareMask);
|
||||
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceBackBit, _backWriteMask);
|
||||
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceBackBit, _backReference);
|
||||
api.CmdSetStencilCompareMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontCompareMask);
|
||||
api.CmdSetStencilWriteMask(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontWriteMask);
|
||||
api.CmdSetStencilReference(commandBuffer, StencilFaceFlags.FaceFrontBit, _frontReference);
|
||||
}
|
||||
|
||||
private void RecordViewport(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
if (ViewportsCount != 0)
|
||||
{
|
||||
api.CmdSetViewport(commandBuffer, 0, ViewportsCount, Viewports.AsSpan());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
314
src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
Normal file
314
src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
Normal file
|
@ -0,0 +1,314 @@
|
|||
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; // MiB
|
||||
|
||||
private readonly List<(QueryPool, bool)> _activeQueries;
|
||||
private CounterQueueEvent _activeConditionalRender;
|
||||
|
||||
private readonly List<BufferedQuery> _pendingQueryCopies;
|
||||
|
||||
private ulong _byteWeight;
|
||||
|
||||
private List<BufferHolder> _backingSwaps;
|
||||
|
||||
public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device)
|
||||
{
|
||||
_activeQueries = new List<(QueryPool, bool)>();
|
||||
_pendingQueryCopies = new();
|
||||
_backingSwaps = new();
|
||||
|
||||
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
||||
}
|
||||
|
||||
private void CopyPendingQuery()
|
||||
{
|
||||
foreach (var query in _pendingQueryCopies)
|
||||
{
|
||||
query.PoolCopy(Cbs);
|
||||
}
|
||||
|
||||
_pendingQueryCopies.Clear();
|
||||
}
|
||||
|
||||
public void ClearRenderTargetColor(int index, int layer, int layerCount, 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],
|
||||
FramebufferParams.GetAttachmentComponentType(index),
|
||||
ClearScissor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearRenderTargetColor(index, layer, layerCount, 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.InvertedBitExt : 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 (AutoFlush.ShouldFlushQuery())
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryBackingSwaps()
|
||||
{
|
||||
CommandBufferScoped? cbs = null;
|
||||
|
||||
_backingSwaps.RemoveAll((holder) => holder.TryBackingSwap(ref cbs));
|
||||
|
||||
cbs?.Dispose();
|
||||
}
|
||||
|
||||
public void AddBackingSwap(BufferHolder holder)
|
||||
{
|
||||
_backingSwaps.Add(holder);
|
||||
}
|
||||
|
||||
public void Restore()
|
||||
{
|
||||
if (Pipeline != null)
|
||||
{
|
||||
Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value);
|
||||
}
|
||||
|
||||
SignalCommandBufferChange();
|
||||
|
||||
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
||||
}
|
||||
|
||||
public void FlushCommandsImpl()
|
||||
{
|
||||
AutoFlush.RegisterFlush(DrawCount);
|
||||
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;
|
||||
Gd.RegisterFlush();
|
||||
|
||||
// Restore per-command buffer state.
|
||||
|
||||
foreach ((var queryPool, var isOcclusion) in _activeQueries)
|
||||
{
|
||||
bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion;
|
||||
|
||||
Gd.Api.CmdResetQueryPool(CommandBuffer, queryPool, 0, 1);
|
||||
Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0);
|
||||
}
|
||||
|
||||
Gd.ResetCounterPool();
|
||||
|
||||
TryBackingSwaps();
|
||||
|
||||
Restore();
|
||||
}
|
||||
|
||||
public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset, bool isOcclusion, bool fromSamplePool)
|
||||
{
|
||||
if (needsReset)
|
||||
{
|
||||
EndRenderPass();
|
||||
|
||||
Gd.Api.CmdResetQueryPool(CommandBuffer, pool, 0, 1);
|
||||
|
||||
if (fromSamplePool)
|
||||
{
|
||||
// Try reset some additional queries in advance.
|
||||
|
||||
Gd.ResetFutureCounters(CommandBuffer, AutoFlush.GetRemainingQueries());
|
||||
}
|
||||
}
|
||||
|
||||
bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion;
|
||||
Gd.Api.CmdBeginQuery(CommandBuffer, pool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0);
|
||||
|
||||
_activeQueries.Add((pool, isOcclusion));
|
||||
}
|
||||
|
||||
public void EndQuery(QueryPool pool)
|
||||
{
|
||||
Gd.Api.CmdEndQuery(CommandBuffer, pool, 0);
|
||||
|
||||
for (int i = 0; i < _activeQueries.Count; i++)
|
||||
{
|
||||
if (_activeQueries[i].Item1.Handle == pool.Handle)
|
||||
{
|
||||
_activeQueries.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyQueryResults(BufferedQuery query)
|
||||
{
|
||||
_pendingQueryCopies.Add(query);
|
||||
|
||||
if (AutoFlush.RegisterPendingQuery())
|
||||
{
|
||||
FlushCommandsImpl();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SignalAttachmentChange()
|
||||
{
|
||||
if (AutoFlush.ShouldFlushAttachmentChange(DrawCount))
|
||||
{
|
||||
FlushCommandsImpl();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SignalRenderPassEnd()
|
||||
{
|
||||
CopyPendingQuery();
|
||||
}
|
||||
}
|
||||
}
|
59
src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs
Normal file
59
src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
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)
|
||||
{
|
||||
SetRenderTarget(view, width, height, 1u, isDepthStencil, format);
|
||||
}
|
||||
|
||||
public void SetRenderTarget(Auto<DisposableImageView> view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format)
|
||||
{
|
||||
CreateFramebuffer(view, width, height, samples, isDepthStencil, format);
|
||||
CreateRenderPass();
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
private void CreateFramebuffer(Auto<DisposableImageView> view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format)
|
||||
{
|
||||
FramebufferParams = new FramebufferParams(Device, view, width, height, samples, 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();
|
||||
}
|
||||
|
||||
public void Finish(VulkanRenderer gd, CommandBufferScoped cbs)
|
||||
{
|
||||
Finish();
|
||||
|
||||
if (gd.PipelineInternal.IsCommandBufferActive(cbs.CommandBuffer))
|
||||
{
|
||||
gd.PipelineInternal.Restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
58
src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs
Normal file
58
src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
112
src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
Normal file
112
src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
244
src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
Normal file
244
src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
Normal file
|
@ -0,0 +1,244 @@
|
|||
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.VertexBit |
|
||||
ShaderStageFlags.FragmentBit |
|
||||
ShaderStageFlags.ComputeBit;
|
||||
|
||||
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.FragmentBit,
|
||||
2 => ShaderStageFlags.GeometryBit,
|
||||
3 => ShaderStageFlags.TessellationControlBit,
|
||||
4 => ShaderStageFlags.TessellationEvaluationBit,
|
||||
_ => ShaderStageFlags.VertexBit | ShaderStageFlags.ComputeBit
|
||||
};
|
||||
|
||||
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[PipelineBase.DescriptorSetLayouts];
|
||||
|
||||
var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
|
||||
{
|
||||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
PBindings = uLayoutBindings,
|
||||
BindingCount = (uint)uCount,
|
||||
Flags = usePd ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : 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[PipelineBase.UniformSetIndex]).ThrowOnError();
|
||||
gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.StorageSetIndex]).ThrowOnError();
|
||||
gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.TextureSetIndex]).ThrowOnError();
|
||||
gd.Api.CreateDescriptorSetLayout(device, iDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.ImageSetIndex]).ThrowOnError();
|
||||
|
||||
fixed (DescriptorSetLayout* pLayouts = layouts)
|
||||
{
|
||||
var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo()
|
||||
{
|
||||
SType = StructureType.PipelineLayoutCreateInfo,
|
||||
PSetLayouts = pLayouts,
|
||||
SetLayoutCount = PipelineBase.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 sCount = 0;
|
||||
int tCount = 0;
|
||||
int iCount = 0;
|
||||
|
||||
foreach (var shader in shaders)
|
||||
{
|
||||
uCount += shader.Bindings.UniformBufferBindings.Count;
|
||||
sCount += shader.Bindings.StorageBufferBindings.Count;
|
||||
tCount += shader.Bindings.TextureBindings.Count;
|
||||
iCount += shader.Bindings.ImageBindings.Count;
|
||||
}
|
||||
|
||||
DescriptorSetLayoutBinding* uLayoutBindings = stackalloc DescriptorSetLayoutBinding[uCount];
|
||||
DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[sCount];
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
Set(sLayoutBindings, DescriptorType.StorageBuffer, ref sIndex, shader.Bindings.StorageBufferBindings);
|
||||
Set(tLayoutBindings, DescriptorType.CombinedImageSampler, ref tIndex, shader.Bindings.TextureBindings);
|
||||
Set(iLayoutBindings, DescriptorType.StorageImage, ref iIndex, shader.Bindings.ImageBindings);
|
||||
}
|
||||
|
||||
DescriptorSetLayout[] layouts = new DescriptorSetLayout[PipelineBase.DescriptorSetLayouts];
|
||||
|
||||
var uDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
|
||||
{
|
||||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
PBindings = uLayoutBindings,
|
||||
BindingCount = (uint)uCount
|
||||
};
|
||||
|
||||
var sDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo()
|
||||
{
|
||||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
PBindings = sLayoutBindings,
|
||||
BindingCount = (uint)sCount
|
||||
};
|
||||
|
||||
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[PipelineBase.UniformSetIndex]).ThrowOnError();
|
||||
gd.Api.CreateDescriptorSetLayout(device, sDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.StorageSetIndex]).ThrowOnError();
|
||||
gd.Api.CreateDescriptorSetLayout(device, tDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.TextureSetIndex]).ThrowOnError();
|
||||
gd.Api.CreateDescriptorSetLayout(device, iDescriptorSetLayoutCreateInfo, null, out layouts[PipelineBase.ImageSetIndex]).ThrowOnError();
|
||||
|
||||
fixed (DescriptorSetLayout* pLayouts = layouts)
|
||||
{
|
||||
var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo()
|
||||
{
|
||||
SType = StructureType.PipelineLayoutCreateInfo,
|
||||
PSetLayouts = pLayouts,
|
||||
SetLayoutCount = PipelineBase.DescriptorSetLayouts
|
||||
};
|
||||
|
||||
gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
|
||||
}
|
||||
|
||||
return layouts;
|
||||
}
|
||||
}
|
||||
}
|
621
src/Ryujinx.Graphics.Vulkan/PipelineState.cs
Normal file
621
src/Ryujinx.Graphics.Vulkan/PipelineState.cs
Normal file
|
@ -0,0 +1,621 @@
|
|||
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 PolygonMode PolygonMode
|
||||
{
|
||||
get => (PolygonMode)((Internal.Id6 >> 0) & 0x3FFFFFFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint StagesCount
|
||||
{
|
||||
get => (byte)((Internal.Id6 >> 30) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
|
||||
}
|
||||
|
||||
public uint VertexAttributeDescriptionsCount
|
||||
{
|
||||
get => (byte)((Internal.Id6 >> 38) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
|
||||
}
|
||||
|
||||
public uint VertexBindingDescriptionsCount
|
||||
{
|
||||
get => (byte)((Internal.Id6 >> 46) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
|
||||
}
|
||||
|
||||
public uint ViewportsCount
|
||||
{
|
||||
get => (byte)((Internal.Id6 >> 54) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
|
||||
}
|
||||
|
||||
public uint ScissorsCount
|
||||
{
|
||||
get => (byte)((Internal.Id7 >> 0) & 0xFF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint ColorBlendAttachmentStateCount
|
||||
{
|
||||
get => (byte)((Internal.Id7 >> 8) & 0xFF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
|
||||
}
|
||||
|
||||
public PrimitiveTopology Topology
|
||||
{
|
||||
get => (PrimitiveTopology)((Internal.Id7 >> 16) & 0xF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
|
||||
}
|
||||
|
||||
public LogicOp LogicOp
|
||||
{
|
||||
get => (LogicOp)((Internal.Id7 >> 20) & 0xF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
|
||||
}
|
||||
|
||||
public CompareOp DepthCompareOp
|
||||
{
|
||||
get => (CompareOp)((Internal.Id7 >> 24) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontFailOp
|
||||
{
|
||||
get => (StencilOp)((Internal.Id7 >> 27) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontPassOp
|
||||
{
|
||||
get => (StencilOp)((Internal.Id7 >> 30) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontDepthFailOp
|
||||
{
|
||||
get => (StencilOp)((Internal.Id7 >> 33) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
|
||||
}
|
||||
|
||||
public CompareOp StencilFrontCompareOp
|
||||
{
|
||||
get => (CompareOp)((Internal.Id7 >> 36) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackFailOp
|
||||
{
|
||||
get => (StencilOp)((Internal.Id7 >> 39) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackPassOp
|
||||
{
|
||||
get => (StencilOp)((Internal.Id7 >> 42) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackDepthFailOp
|
||||
{
|
||||
get => (StencilOp)((Internal.Id7 >> 45) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
|
||||
}
|
||||
|
||||
public CompareOp StencilBackCompareOp
|
||||
{
|
||||
get => (CompareOp)((Internal.Id7 >> 48) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
|
||||
}
|
||||
|
||||
public CullModeFlags CullMode
|
||||
{
|
||||
get => (CullModeFlags)((Internal.Id7 >> 51) & 0x3);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
|
||||
}
|
||||
|
||||
public bool PrimitiveRestartEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 53) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
|
||||
}
|
||||
|
||||
public bool DepthClampEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 54) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
|
||||
}
|
||||
|
||||
public bool RasterizerDiscardEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 55) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
|
||||
}
|
||||
|
||||
public FrontFace FrontFace
|
||||
{
|
||||
get => (FrontFace)((Internal.Id7 >> 56) & 0x1);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
|
||||
}
|
||||
|
||||
public bool DepthBiasEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 57) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
|
||||
}
|
||||
|
||||
public bool DepthTestEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 58) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
|
||||
}
|
||||
|
||||
public bool DepthWriteEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 59) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
|
||||
}
|
||||
|
||||
public bool DepthBoundsTestEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 60) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
|
||||
}
|
||||
|
||||
public bool StencilTestEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 61) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
|
||||
}
|
||||
|
||||
public bool LogicOpEnable
|
||||
{
|
||||
get => ((Internal.Id7 >> 62) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
|
||||
}
|
||||
|
||||
public bool HasDepthStencil
|
||||
{
|
||||
get => ((Internal.Id7 >> 63) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
|
||||
}
|
||||
|
||||
public uint PatchControlPoints
|
||||
{
|
||||
get => (uint)((Internal.Id8 >> 0) & 0xFFFFFFFF);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint SamplesCount
|
||||
{
|
||||
get => (uint)((Internal.Id8 >> 32) & 0xFFFFFFFF);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
}
|
||||
|
||||
public bool AlphaToCoverageEnable
|
||||
{
|
||||
get => ((Internal.Id9 >> 0) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
|
||||
}
|
||||
|
||||
public bool AlphaToOneEnable
|
||||
{
|
||||
get => ((Internal.Id9 >> 1) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
|
||||
}
|
||||
|
||||
public bool AdvancedBlendSrcPreMultiplied
|
||||
{
|
||||
get => ((Internal.Id9 >> 2) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
|
||||
}
|
||||
|
||||
public bool AdvancedBlendDstPreMultiplied
|
||||
{
|
||||
get => ((Internal.Id9 >> 3) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
|
||||
}
|
||||
|
||||
public BlendOverlapEXT AdvancedBlendOverlap
|
||||
{
|
||||
get => (BlendOverlapEXT)((Internal.Id9 >> 4) & 0x3);
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
|
||||
}
|
||||
|
||||
public NativeArray<PipelineShaderStageCreateInfo> Stages;
|
||||
public NativeArray<PipelineShaderStageRequiredSubgroupSizeCreateInfoEXT> StageRequiredSubgroupSizes;
|
||||
public PipelineLayout PipelineLayout;
|
||||
public SpecData SpecializationData;
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
AdvancedBlendSrcPreMultiplied = true;
|
||||
AdvancedBlendDstPreMultiplied = true;
|
||||
AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt;
|
||||
|
||||
LineWidth = 1f;
|
||||
SamplesCount = 1;
|
||||
}
|
||||
|
||||
public unsafe Auto<DisposablePipeline> CreateComputePipeline(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ShaderCollection program,
|
||||
PipelineCache cache)
|
||||
{
|
||||
if (program.TryGetComputePipeline(ref SpecializationData, 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;
|
||||
|
||||
bool hasSpec = program.SpecDescriptions != null;
|
||||
|
||||
var desc = hasSpec ? program.SpecDescriptions[0] : SpecDescription.Empty;
|
||||
|
||||
if (hasSpec && SpecializationData.Length < (int)desc.Info.DataSize)
|
||||
{
|
||||
throw new InvalidOperationException("Specialization data size does not match description");
|
||||
}
|
||||
|
||||
fixed (SpecializationInfo* info = &desc.Info)
|
||||
fixed (SpecializationMapEntry* map = desc.Map)
|
||||
fixed (byte* data = SpecializationData.Span)
|
||||
{
|
||||
if (hasSpec)
|
||||
{
|
||||
info->PMapEntries = map;
|
||||
info->PData = data;
|
||||
pipelineCreateInfo.Stage.PSpecializationInfo = info;
|
||||
}
|
||||
|
||||
gd.Api.CreateComputePipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle).ThrowOnError();
|
||||
}
|
||||
|
||||
pipeline = new Auto<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle));
|
||||
|
||||
program.AddComputePipeline(ref SpecializationData, pipeline);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
bool topologySupportsRestart;
|
||||
|
||||
if (gd.Capabilities.SupportsPrimitiveTopologyListRestart)
|
||||
{
|
||||
topologySupportsRestart = gd.Capabilities.SupportsPrimitiveTopologyPatchListRestart || Topology != PrimitiveTopology.PatchList;
|
||||
}
|
||||
else
|
||||
{
|
||||
topologySupportsRestart = Topology == PrimitiveTopology.LineStrip ||
|
||||
Topology == PrimitiveTopology.TriangleStrip ||
|
||||
Topology == PrimitiveTopology.TriangleFan ||
|
||||
Topology == PrimitiveTopology.LineStripWithAdjacency ||
|
||||
Topology == PrimitiveTopology.TriangleStripWithAdjacency;
|
||||
}
|
||||
|
||||
primitiveRestartEnable &= topologySupportsRestart;
|
||||
|
||||
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(gd.Capabilities.SupportedSampleCounts, 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
|
||||
};
|
||||
|
||||
PipelineColorBlendAdvancedStateCreateInfoEXT colorBlendAdvancedState;
|
||||
|
||||
if (!AdvancedBlendSrcPreMultiplied ||
|
||||
!AdvancedBlendDstPreMultiplied ||
|
||||
AdvancedBlendOverlap != BlendOverlapEXT.UncorrelatedExt)
|
||||
{
|
||||
colorBlendAdvancedState = new PipelineColorBlendAdvancedStateCreateInfoEXT()
|
||||
{
|
||||
SType = StructureType.PipelineColorBlendAdvancedStateCreateInfoExt,
|
||||
SrcPremultiplied = AdvancedBlendSrcPreMultiplied,
|
||||
DstPremultiplied = AdvancedBlendDstPreMultiplied,
|
||||
BlendOverlap = AdvancedBlendOverlap
|
||||
};
|
||||
|
||||
colorBlendState.PNext = &colorBlendAdvancedState;
|
||||
}
|
||||
|
||||
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
||||
int dynamicStatesCount = supportsExtDynamicState ? 9 : 8;
|
||||
|
||||
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;
|
||||
dynamicStates[7] = DynamicState.BlendConstants;
|
||||
|
||||
if (supportsExtDynamicState)
|
||||
{
|
||||
dynamicStates[8] = 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();
|
||||
}
|
||||
}
|
||||
}
|
129
src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
Normal file
129
src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
Normal file
|
@ -0,0 +1,129 @@
|
|||
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;
|
||||
|
||||
private uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF);
|
||||
private uint VertexBindingDescriptionsCount => (byte)((Id6 >> 46) & 0xFF);
|
||||
private uint ViewportsCount => (byte)((Id6 >> 54) & 0xFF);
|
||||
private uint ScissorsCount => (byte)((Id7 >> 0) & 0xFF);
|
||||
private uint ColorBlendAttachmentStateCount => (byte)((Id7 >> 8) & 0xFF);
|
||||
private bool HasDepthStencil => ((Id7 >> 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, Vector128<byte>>(ref Id8).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id8)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SequenceEqual<VertexInputAttributeDescription>(VertexAttributeDescriptions.AsSpan(), other.VertexAttributeDescriptions.AsSpan(), VertexAttributeDescriptionsCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SequenceEqual<VertexInputBindingDescription>(VertexBindingDescriptions.AsSpan(), other.VertexBindingDescriptions.AsSpan(), VertexBindingDescriptionsCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SequenceEqual<PipelineColorBlendAttachmentState>(ColorBlendAttachmentState.AsSpan(), other.ColorBlendAttachmentState.AsSpan(), ColorBlendAttachmentStateCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SequenceEqual<Format>(AttachmentFormats.AsSpan(), other.AttachmentFormats.AsSpan(), 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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
216
src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs
Normal file
216
src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs
Normal file
|
@ -0,0 +1,216 @@
|
|||
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 const ulong HighMask = 0xFFFFFFFF00000000;
|
||||
|
||||
private readonly Vk _api;
|
||||
private readonly Device _device;
|
||||
private readonly PipelineFull _pipeline;
|
||||
|
||||
private QueryPool _queryPool;
|
||||
|
||||
private readonly BufferHolder _buffer;
|
||||
private readonly IntPtr _bufferMap;
|
||||
private readonly CounterType _type;
|
||||
private bool _result32Bit;
|
||||
private bool _isSupported;
|
||||
|
||||
private long _defaultValue;
|
||||
private int? _resetSequence;
|
||||
|
||||
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.GeometryShaderPrimitivesBit : 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.PrimitivesGenerated => gd.Capabilities.SupportsPipelineStatisticsQuery,
|
||||
CounterType.TransformFeedbackPrimitivesWritten => gd.Capabilities.SupportsTransformFeedbackQueries,
|
||||
_ => 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(null);
|
||||
}
|
||||
|
||||
public void Begin(int? resetSequence)
|
||||
{
|
||||
if (_isSupported)
|
||||
{
|
||||
bool needsReset = resetSequence == null || _resetSequence == null || resetSequence.Value != _resetSequence.Value;
|
||||
bool isOcclusion = _type == CounterType.SamplesPassed;
|
||||
_pipeline.BeginQuery(this, _queryPool, needsReset, isOcclusion, isOcclusion && resetSequence != null);
|
||||
}
|
||||
_resetSequence = null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private bool WaitingForValue(long data)
|
||||
{
|
||||
return data == _defaultValue ||
|
||||
(!_result32Bit && ((ulong)data & HighMask) == ((ulong)_defaultValue & HighMask));
|
||||
}
|
||||
|
||||
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 (WaitingForValue(data))
|
||||
{
|
||||
data = Marshal.ReadInt64(_bufferMap);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int iterations = 0;
|
||||
while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
|
||||
{
|
||||
data = Marshal.ReadInt64(_bufferMap);
|
||||
if (WaitingForValue(data))
|
||||
{
|
||||
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, int resetSequence)
|
||||
{
|
||||
if (_isSupported)
|
||||
{
|
||||
_api.CmdResetQueryPool(cmd, _queryPool, 0, 1);
|
||||
}
|
||||
|
||||
_resetSequence = resetSequence;
|
||||
}
|
||||
|
||||
public void PoolCopy(CommandBufferScoped cbs)
|
||||
{
|
||||
var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long)).Value;
|
||||
|
||||
QueryResultFlags flags = QueryResultFlags.ResultWaitBit;
|
||||
|
||||
if (!_result32Bit)
|
||||
{
|
||||
flags |= QueryResultFlags.Result64Bit;
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
245
src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs
Normal file
245
src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs
Normal file
|
@ -0,0 +1,245 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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;
|
||||
|
||||
public int ResetSequence { get; private set; }
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public void ResetCounterPool()
|
||||
{
|
||||
ResetSequence++;
|
||||
}
|
||||
|
||||
public void ResetFutureCounters(CommandBuffer cmd, int count)
|
||||
{
|
||||
// Pre-emptively reset queries to avoid render pass splitting.
|
||||
lock (_queryPool)
|
||||
{
|
||||
count = Math.Min(count, _queryPool.Count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
_queryPool.ElementAt(i).PoolReset(cmd, ResetSequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// The query will be reset when it dequeues.
|
||||
_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, _pipeline.GetCounterDivisor(Type));
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
170
src/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs
Normal file
170
src/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs
Normal file
|
@ -0,0 +1,170 @@
|
|||
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;
|
||||
private double _divisor = 1f;
|
||||
|
||||
public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex)
|
||||
{
|
||||
_queue = queue;
|
||||
|
||||
_counter = queue.GetQueryObject();
|
||||
Type = type;
|
||||
|
||||
DrawIndex = drawIndex;
|
||||
|
||||
_counter.Begin(_queue.ResetSequence);
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer()
|
||||
{
|
||||
return _counter.GetBuffer();
|
||||
}
|
||||
|
||||
internal void Clear(bool counterReset)
|
||||
{
|
||||
if (counterReset)
|
||||
{
|
||||
_counter.Reset();
|
||||
}
|
||||
|
||||
ClearCounter = true;
|
||||
}
|
||||
|
||||
internal void Complete(bool withResult, double divisor)
|
||||
{
|
||||
_counter.End(withResult);
|
||||
|
||||
_divisor = divisor;
|
||||
}
|
||||
|
||||
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 += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor);
|
||||
|
||||
_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();
|
||||
}
|
||||
}
|
||||
}
|
71
src/Ryujinx.Graphics.Vulkan/Queries/Counters.cs
Normal file
71
src/Ryujinx.Graphics.Vulkan/Queries/Counters.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
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 < _counterQueues.Length; index++)
|
||||
{
|
||||
CounterType type = (CounterType)index;
|
||||
_counterQueues[index] = new CounterQueue(gd, device, pipeline, type);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetCounterPool()
|
||||
{
|
||||
foreach (var queue in _counterQueues)
|
||||
{
|
||||
queue.ResetCounterPool();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetFutureCounters(CommandBuffer cmd, int count)
|
||||
{
|
||||
_counterQueues[(int)CounterType.SamplesPassed].ResetFutureCounters(cmd, count);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
Normal file
39
src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
Normal file
|
@ -0,0 +1,39 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" />
|
||||
<EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" />
|
||||
<EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" />
|
||||
<EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" />
|
||||
<EmbeddedResource Include="Effects\Shaders\Fxaa.spv" />
|
||||
<EmbeddedResource Include="Effects\Shaders\SmaaBlend.spv" />
|
||||
<EmbeddedResource Include="Effects\Shaders\SmaaEdge.spv" />
|
||||
<EmbeddedResource Include="Effects\Shaders\SmaaNeighbour.spv" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" />
|
||||
<PackageReference Include="shaderc.net" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
118
src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs
Normal file
118
src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs
Normal file
|
@ -0,0 +1,118 @@
|
|||
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;
|
||||
samplerCreateInfo.BorderColor = BorderColor.FloatCustomExt;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
src/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs
Normal file
60
src/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
163
src/Ryujinx.Graphics.Vulkan/Shader.cs
Normal file
163
src/Ryujinx.Graphics.Vulkan/Shader.cs
Normal file
|
@ -0,0 +1,163 @@
|
|||
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 : IDisposable
|
||||
{
|
||||
// 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 static readonly IntPtr _ptrMainEntryPointName = Marshal.StringToHGlobalAnsi("main");
|
||||
|
||||
private readonly Vk _api;
|
||||
private readonly Device _device;
|
||||
private readonly ShaderStageFlags _stage;
|
||||
|
||||
private bool _disposed;
|
||||
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();
|
||||
|
||||
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)
|
||||
{
|
||||
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*)_ptrMainEntryPointName
|
||||
};
|
||||
}
|
||||
|
||||
public void WaitForCompile()
|
||||
{
|
||||
CompileTask.Wait();
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_api.DestroyShaderModule(_device, _module, null);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
427
src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
Normal file
427
src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
Normal file
|
@ -0,0 +1,427 @@
|
|||
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 bool IsCompute { get; }
|
||||
|
||||
public uint Stages { get; }
|
||||
|
||||
public int[][][] Bindings { get; }
|
||||
|
||||
public ProgramLinkStatus LinkStatus { get; private set; }
|
||||
|
||||
public readonly SpecDescription[] SpecDescriptions;
|
||||
|
||||
public bool IsLinked
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LinkStatus == ProgramLinkStatus.Incomplete)
|
||||
{
|
||||
CheckProgramLink(true);
|
||||
}
|
||||
|
||||
return LinkStatus == ProgramLinkStatus.Success;
|
||||
}
|
||||
}
|
||||
|
||||
private HashTableSlim<PipelineUid, Auto<DisposablePipeline>> _graphicsPipelineCache;
|
||||
private HashTableSlim<SpecData, Auto<DisposablePipeline>> _computePipelineCache;
|
||||
|
||||
private VulkanRenderer _gd;
|
||||
private Device _device;
|
||||
private bool _initialized;
|
||||
|
||||
private ProgramPipelineState _state;
|
||||
private DisposableRenderPass _dummyRenderPass;
|
||||
private Task _compileTask;
|
||||
private bool _firstBackgroundUse;
|
||||
|
||||
public ShaderCollection(VulkanRenderer gd, Device device, ShaderSource[] shaders, SpecDescription[] specDescription = null, bool isMinimal = false)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
|
||||
if (specDescription != null && specDescription.Length != shaders.Length)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(specDescription)} array length must match {nameof(shaders)} array if provided");
|
||||
}
|
||||
|
||||
gd.Shaders.Add(this);
|
||||
|
||||
var internalShaders = new Shader[shaders.Length];
|
||||
|
||||
_infos = new PipelineShaderStageCreateInfo[shaders.Length];
|
||||
|
||||
SpecDescriptions = specDescription;
|
||||
|
||||
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.FragmentBit => 1,
|
||||
ShaderStageFlags.GeometryBit => 2,
|
||||
ShaderStageFlags.TessellationControlBit => 3,
|
||||
ShaderStageFlags.TessellationEvaluationBit => 4,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if (shader.StageFlags == ShaderStageFlags.ComputeBit)
|
||||
{
|
||||
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.AsSpan();
|
||||
|
||||
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(ref SpecData key, Auto<DisposablePipeline> pipeline)
|
||||
{
|
||||
(_computePipelineCache ??= new()).Add(ref key, pipeline);
|
||||
}
|
||||
|
||||
public void AddGraphicsPipeline(ref PipelineUid key, Auto<DisposablePipeline> pipeline)
|
||||
{
|
||||
(_graphicsPipelineCache ??= new()).Add(ref key, pipeline);
|
||||
}
|
||||
|
||||
public bool TryGetComputePipeline(ref SpecData key, out Auto<DisposablePipeline> pipeline)
|
||||
{
|
||||
if (_computePipelineCache == null)
|
||||
{
|
||||
pipeline = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_computePipelineCache.TryGetValue(ref key, out pipeline))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
if (_computePipelineCache != null)
|
||||
{
|
||||
foreach (Auto<DisposablePipeline> pipeline in _computePipelineCache.Values)
|
||||
{
|
||||
pipeline.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (_dummyRenderPass.Value.Handle != 0)
|
||||
{
|
||||
_dummyRenderPass.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
#version 450 core
|
||||
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
|
||||
layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout (std140, set = 0, binding = 0) uniform stride_arguments
|
||||
{
|
||||
ivec4 stride_arguments_data;
|
||||
};
|
||||
|
||||
layout (std430, set = 1, binding = 1) buffer in_s
|
||||
{
|
||||
uint8_t[] in_data;
|
||||
};
|
||||
|
||||
layout (std430, set = 1, binding = 2) buffer out_s
|
||||
{
|
||||
uint8_t[] out_data;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
// Determine what slice of the stride copies this invocation will perform.
|
||||
|
||||
int sourceStride = stride_arguments_data.x;
|
||||
int targetStride = stride_arguments_data.y;
|
||||
int bufferSize = stride_arguments_data.z;
|
||||
int sourceOffset = stride_arguments_data.w;
|
||||
|
||||
int strideRemainder = targetStride - sourceStride;
|
||||
int invocations = int(gl_WorkGroupSize.x);
|
||||
|
||||
int copiesRequired = bufferSize / sourceStride;
|
||||
|
||||
// Find the copies that this invocation should perform.
|
||||
|
||||
// - Copies that all invocations perform.
|
||||
int allInvocationCopies = copiesRequired / invocations;
|
||||
|
||||
// - Extra remainder copy that this invocation performs.
|
||||
int index = int(gl_LocalInvocationID.x);
|
||||
int extra = (index < (copiesRequired % invocations)) ? 1 : 0;
|
||||
|
||||
int copyCount = allInvocationCopies + extra;
|
||||
|
||||
// Finally, get the starting offset. Make sure to count extra copies.
|
||||
|
||||
int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index);
|
||||
|
||||
int srcOffset = sourceOffset + startCopy * sourceStride;
|
||||
int dstOffset = startCopy * targetStride;
|
||||
|
||||
// Perform the copies for this region
|
||||
for (int i=0; i<copyCount; i++) {
|
||||
for (int j=0; j<sourceStride; j++) {
|
||||
out_data[dstOffset++] = in_data[srcOffset++];
|
||||
}
|
||||
|
||||
for (int j=0; j<strideRemainder; j++) {
|
||||
out_data[dstOffset++] = uint8_t(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#version 450 core
|
||||
|
||||
layout (binding = 0, set = 2) uniform sampler2DMS tex;
|
||||
|
||||
layout (location = 0) in vec2 tex_coord;
|
||||
layout (location = 0) out vec4 colour;
|
||||
|
||||
void main()
|
||||
{
|
||||
colour = texelFetch(tex, ivec2(tex_coord * vec2(textureSize(tex).xy)), gl_SampleID);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#version 450 core
|
||||
|
||||
layout (location = 0) in vec4 clear_colour;
|
||||
layout (location = 0) out ivec4 colour;
|
||||
|
||||
void main()
|
||||
{
|
||||
colour = floatBitsToInt(clear_colour);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#version 450 core
|
||||
|
||||
layout (location = 0) in vec4 clear_colour;
|
||||
layout (location = 0) out uvec4 colour;
|
||||
|
||||
void main()
|
||||
{
|
||||
colour = floatBitsToUint(clear_colour);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#version 450 core
|
||||
|
||||
layout (std140, binding = 0) uniform ratio_in
|
||||
{
|
||||
int ratio;
|
||||
};
|
||||
|
||||
layout (set = 2, binding = 0) uniform usampler2D src;
|
||||
layout (set = 3, binding = 0) writeonly uniform uimage2D dst;
|
||||
|
||||
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 coords = gl_GlobalInvocationID.xy;
|
||||
ivec2 textureSz = textureSize(src, 0);
|
||||
|
||||
if (int(coords.x) >= textureSz.x || int(coords.y) >= textureSz.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint coordsShifted = coords.x << ratio;
|
||||
|
||||
uvec2 dstCoords0 = uvec2(coordsShifted, coords.y);
|
||||
uvec2 dstCoords1 = uvec2(coordsShifted + 1, coords.y);
|
||||
uvec2 dstCoords2 = uvec2(coordsShifted + 2, coords.y);
|
||||
uvec2 dstCoords3 = uvec2(coordsShifted + 3, coords.y);
|
||||
|
||||
uvec4 rgba = texelFetch(src, ivec2(coords), 0);
|
||||
|
||||
imageStore(dst, ivec2(dstCoords0), rgba.rrrr);
|
||||
imageStore(dst, ivec2(dstCoords1), rgba.gggg);
|
||||
imageStore(dst, ivec2(dstCoords2), rgba.bbbb);
|
||||
imageStore(dst, ivec2(dstCoords3), rgba.aaaa);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#version 450 core
|
||||
|
||||
layout (std140, binding = 0) uniform sample_counts_log2_in
|
||||
{
|
||||
ivec4 sample_counts_log2;
|
||||
};
|
||||
|
||||
layout (set = 2, binding = 0) uniform usampler2DMS srcMS;
|
||||
layout (set = 3, binding = 0) writeonly uniform uimage2D dst;
|
||||
|
||||
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 coords = gl_GlobalInvocationID.xy;
|
||||
ivec2 imageSz = imageSize(dst);
|
||||
|
||||
if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int deltaX = sample_counts_log2.x - sample_counts_log2.z;
|
||||
int deltaY = sample_counts_log2.y - sample_counts_log2.w;
|
||||
int samplesInXLog2 = sample_counts_log2.z;
|
||||
int samplesInYLog2 = sample_counts_log2.w;
|
||||
int samplesInX = 1 << samplesInXLog2;
|
||||
int samplesInY = 1 << samplesInYLog2;
|
||||
int sampleIdx = ((int(coords.x) >> deltaX) & (samplesInX - 1)) | (((int(coords.y) >> deltaY) & (samplesInY - 1)) << samplesInXLog2);
|
||||
|
||||
samplesInXLog2 = sample_counts_log2.x;
|
||||
samplesInYLog2 = sample_counts_log2.y;
|
||||
|
||||
ivec2 shiftedCoords = ivec2(int(coords.x) >> samplesInXLog2, int(coords.y) >> samplesInYLog2);
|
||||
|
||||
imageStore(dst, ivec2(coords), texelFetch(srcMS, shiftedCoords, sampleIdx));
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#version 450 core
|
||||
|
||||
layout (std140, binding = 0) uniform ratio_in
|
||||
{
|
||||
int ratio;
|
||||
};
|
||||
|
||||
layout (set = 2, binding = 0) uniform usampler2D src;
|
||||
layout (set = 3, binding = 0) writeonly uniform uimage2D dst;
|
||||
|
||||
layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
|
||||
|
||||
void main()
|
||||
{
|
||||
uvec2 coords = gl_GlobalInvocationID.xy;
|
||||
ivec2 imageSz = imageSize(dst);
|
||||
|
||||
if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uvec2 srcCoords = uvec2(coords.x << ratio, coords.y);
|
||||
|
||||
uint r = texelFetchOffset(src, ivec2(srcCoords), 0, ivec2(0, 0)).r;
|
||||
uint g = texelFetchOffset(src, ivec2(srcCoords), 0, ivec2(1, 0)).r;
|
||||
uint b = texelFetchOffset(src, ivec2(srcCoords), 0, ivec2(2, 0)).r;
|
||||
uint a = texelFetchOffset(src, ivec2(srcCoords), 0, ivec2(3, 0)).r;
|
||||
|
||||
imageStore(dst, ivec2(coords), uvec4(r, g, b, a));
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#version 450 core
|
||||
|
||||
layout (std140, binding = 0) uniform sample_counts_log2_in
|
||||
{
|
||||
ivec4 sample_counts_log2;
|
||||
};
|
||||
|
||||
layout (set = 2, binding = 0) uniform usampler2D src;
|
||||
|
||||
layout (location = 0) out uvec4 colour;
|
||||
|
||||
void main()
|
||||
{
|
||||
int deltaX = sample_counts_log2.x - sample_counts_log2.z;
|
||||
int deltaY = sample_counts_log2.y - sample_counts_log2.w;
|
||||
int samplesInXLog2 = sample_counts_log2.z;
|
||||
int samplesInYLog2 = sample_counts_log2.w;
|
||||
int samplesInX = 1 << samplesInXLog2;
|
||||
int samplesInY = 1 << samplesInYLog2;
|
||||
|
||||
int sampleIndex = gl_SampleID;
|
||||
|
||||
int inX = (int(gl_FragCoord.x) << sample_counts_log2.x) | ((sampleIndex & (samplesInX - 1)) << deltaX);
|
||||
int inY = (int(gl_FragCoord.y) << sample_counts_log2.y) | ((sampleIndex >> samplesInXLog2) << deltaY);
|
||||
|
||||
colour = texelFetch(src, ivec2(inX, inY), 0);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#version 450 core
|
||||
|
||||
void main()
|
||||
{
|
||||
int low = gl_VertexIndex & 1;
|
||||
int high = gl_VertexIndex >> 1;
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#version 450 core
|
||||
|
||||
#extension GL_EXT_scalar_block_layout : require
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
|
||||
layout (local_size_x = 16, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout (std430, set = 0, binding = 0) uniform index_buffer_pattern
|
||||
{
|
||||
int ibp_pattern[8];
|
||||
int ibp_primitive_vertices;
|
||||
int ibp_primitive_vertices_out;
|
||||
int ibp_index_size;
|
||||
int ibp_index_size_out;
|
||||
int ibp_base_index;
|
||||
int ibp_index_stride;
|
||||
int src_offset;
|
||||
int total_primitives;
|
||||
};
|
||||
|
||||
layout (std430, set = 1, binding = 1) buffer in_s
|
||||
{
|
||||
uint8_t[] in_data;
|
||||
};
|
||||
|
||||
layout (std430, set = 1, binding = 2) buffer out_s
|
||||
{
|
||||
uint8_t[] out_data;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
int primitiveIndex = int(gl_GlobalInvocationID.x);
|
||||
if (primitiveIndex >= total_primitives)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int inOffset = primitiveIndex * ibp_index_stride;
|
||||
int outOffset = primitiveIndex * ibp_primitive_vertices_out;
|
||||
|
||||
for (int i = 0; i < ibp_primitive_vertices_out; i++)
|
||||
{
|
||||
int j;
|
||||
int io = max(0, inOffset + ibp_base_index + ibp_pattern[i]) * ibp_index_size;
|
||||
int oo = (outOffset + i) * ibp_index_size_out;
|
||||
|
||||
for (j = 0; j < ibp_index_size; j++)
|
||||
{
|
||||
out_data[oo + j] = in_data[src_offset + io + j];
|
||||
}
|
||||
|
||||
for (; j < ibp_index_size_out; j++)
|
||||
{
|
||||
out_data[oo + j] = uint8_t(0);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue