mirror of
https://github.com/GreemDev/Ryujinx.git
synced 2025-01-09 11:17:20 -03:00
RDNA3 Vulkan project
This commit is contained in:
parent
a23d1d660e
commit
7ffc1f0d2f
119 changed files with 38581 additions and 0 deletions
|
@ -95,6 +95,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Rdna3Vulkan", "src\Ryujinx.Graphics.Rdna3Vulkan\Ryujinx.Graphics.Rdna3Vulkan.csproj", "{5D8C99F7-AC66-43CF-AE84-68ADA27CCED7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -259,6 +261,10 @@ Global
|
|||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5D8C99F7-AC66-43CF-AE84-68ADA27CCED7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5D8C99F7-AC66-43CF-AE84-68ADA27CCED7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5D8C99F7-AC66-43CF-AE84-68ADA27CCED7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5D8C99F7-AC66-43CF-AE84-68ADA27CCED7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
191
src/Ryujinx.Graphics.Rdna3Vulkan/Auto.cs
Normal file
191
src/Ryujinx.Graphics.Rdna3Vulkan/Auto.cs
Normal file
|
@ -0,0 +1,191 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
interface IAuto
|
||||
{
|
||||
bool HasCommandBufferDependency(CommandBufferScoped cbs);
|
||||
|
||||
void IncrementReferenceCount();
|
||||
void DecrementReferenceCount(int cbIndex);
|
||||
void DecrementReferenceCount();
|
||||
}
|
||||
|
||||
interface IAutoPrivate : IAuto
|
||||
{
|
||||
void AddCommandBufferDependencies(CommandBufferScoped cbs);
|
||||
}
|
||||
|
||||
interface IMirrorable<T> where T : IDisposable
|
||||
{
|
||||
Auto<T> GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored);
|
||||
void ClearMirrors(CommandBufferScoped cbs, int offset, int size);
|
||||
}
|
||||
|
||||
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 readonly IMirrorable<T> _mirrorable;
|
||||
|
||||
private bool _disposed;
|
||||
private bool _destroyed;
|
||||
|
||||
public Auto(T value)
|
||||
{
|
||||
_referenceCount = 1;
|
||||
_value = value;
|
||||
_cbOwnership = new BitMap(CommandBufferPool.MaxCommandBuffers);
|
||||
}
|
||||
|
||||
public Auto(T value, IMirrorable<T> mirrorable, MultiFenceHolder waitable, params IAutoPrivate[] referencedObjs) : this(value, waitable, referencedObjs)
|
||||
{
|
||||
_mirrorable = mirrorable;
|
||||
}
|
||||
|
||||
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 GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
|
||||
{
|
||||
var mirror = _mirrorable.GetMirrorable(cbs, ref offset, size, out mirrored);
|
||||
mirror._waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, false);
|
||||
return mirror.Get(cbs);
|
||||
}
|
||||
|
||||
public T Get(CommandBufferScoped cbs, int offset, int size, bool write = false)
|
||||
{
|
||||
_mirrorable?.ClearMirrors(cbs, offset, size);
|
||||
_waitable?.AddBufferUse(cbs.CommandBufferIndex, offset, size, write);
|
||||
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 bool TryIncrementReferenceCount()
|
||||
{
|
||||
int lastValue;
|
||||
do
|
||||
{
|
||||
lastValue = _referenceCount;
|
||||
|
||||
if (lastValue == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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.Rdna3Vulkan/AutoFlushCounter.cs
Normal file
179
src/Ryujinx.Graphics.Rdna3Vulkan/AutoFlushCounter.cs
Normal file
|
@ -0,0 +1,179 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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 readonly int[] _queryCountHistory = new int[3];
|
||||
private int _queryCountHistoryIndex;
|
||||
private int _remainingQueries;
|
||||
|
||||
private readonly 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})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
src/Ryujinx.Graphics.Rdna3Vulkan/BackgroundResources.cs
Normal file
120
src/Ryujinx.Graphics.Rdna3Vulkan/BackgroundResources.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class BackgroundResource : IDisposable
|
||||
{
|
||||
private readonly 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;
|
||||
Lock queueLock = useBackground ? _gd.BackgroundQueueLock : _gd.QueueLock;
|
||||
|
||||
lock (queueLock)
|
||||
{
|
||||
_pool = new CommandBufferPool(
|
||||
_gd.Api,
|
||||
_device,
|
||||
queue,
|
||||
queueLock,
|
||||
_gd.QueueFamilyIndex,
|
||||
_gd.IsQualcommProprietary,
|
||||
isLight: true);
|
||||
}
|
||||
}
|
||||
|
||||
return _pool;
|
||||
}
|
||||
|
||||
public PersistentFlushBuffer GetFlushBuffer()
|
||||
{
|
||||
_flushBuffer ??= new PersistentFlushBuffer(_gd);
|
||||
|
||||
return _flushBuffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pool?.Dispose();
|
||||
_flushBuffer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class BackgroundResources : IDisposable
|
||||
{
|
||||
private readonly VulkanRenderer _gd;
|
||||
private Device _device;
|
||||
|
||||
private readonly 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)
|
||||
{
|
||||
if (!_resources.TryGetValue(thread, out BackgroundResource 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
458
src/Ryujinx.Graphics.Rdna3Vulkan/BarrierBatch.cs
Normal file
458
src/Ryujinx.Graphics.Rdna3Vulkan/BarrierBatch.cs
Normal file
|
@ -0,0 +1,458 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
internal class BarrierBatch : IDisposable
|
||||
{
|
||||
private const int MaxBarriersPerCall = 16;
|
||||
|
||||
private const AccessFlags BaseAccess = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit;
|
||||
private const AccessFlags BufferAccess = AccessFlags.IndexReadBit | AccessFlags.VertexAttributeReadBit | AccessFlags.UniformReadBit;
|
||||
private const AccessFlags CommandBufferAccess = AccessFlags.IndirectCommandReadBit;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
||||
|
||||
private readonly List<BarrierWithStageFlags<MemoryBarrier, int>> _memoryBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier, int>> _bufferBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage>> _imageBarriers = new();
|
||||
private int _queuedBarrierCount;
|
||||
|
||||
private enum IncoherentBarrierType
|
||||
{
|
||||
None,
|
||||
Texture,
|
||||
All,
|
||||
CommandBuffer
|
||||
}
|
||||
|
||||
private bool _feedbackLoopActive;
|
||||
private PipelineStageFlags _incoherentBufferWriteStages;
|
||||
private PipelineStageFlags _incoherentTextureWriteStages;
|
||||
private PipelineStageFlags _extraStages;
|
||||
private IncoherentBarrierType _queuedIncoherentBarrier;
|
||||
private bool _queuedFeedbackLoopBarrier;
|
||||
|
||||
public BarrierBatch(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd)
|
||||
{
|
||||
AccessFlags access = BufferAccess;
|
||||
PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit;
|
||||
|
||||
if (gd.TransformFeedbackApi != null)
|
||||
{
|
||||
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
|
||||
return (access, stages);
|
||||
}
|
||||
|
||||
private readonly record struct StageFlags : IEquatable<StageFlags>
|
||||
{
|
||||
public readonly PipelineStageFlags Source;
|
||||
public readonly PipelineStageFlags Dest;
|
||||
|
||||
public StageFlags(PipelineStageFlags source, PipelineStageFlags dest)
|
||||
{
|
||||
Source = source;
|
||||
Dest = dest;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct BarrierWithStageFlags<T, T2> where T : unmanaged
|
||||
{
|
||||
public readonly StageFlags Flags;
|
||||
public readonly T Barrier;
|
||||
public readonly T2 Resource;
|
||||
|
||||
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
||||
{
|
||||
Flags = flags;
|
||||
Barrier = barrier;
|
||||
Resource = default;
|
||||
}
|
||||
|
||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier, T2 resource)
|
||||
{
|
||||
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
||||
Barrier = barrier;
|
||||
Resource = resource;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueBarrier<T, T2>(List<BarrierWithStageFlags<T, T2>> list, T barrier, T2 resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
||||
{
|
||||
list.Add(new BarrierWithStageFlags<T, T2>(srcStageFlags, dstStageFlags, barrier, resource));
|
||||
_queuedBarrierCount++;
|
||||
}
|
||||
|
||||
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_memoryBarriers, barrier, default, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_bufferBarriers, barrier, default, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
public void QueueBarrier(ImageMemoryBarrier barrier, TextureStorage resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||
{
|
||||
QueueBarrier(_imageBarriers, barrier, resource, srcStageFlags, dstStageFlags);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe void FlushMemoryBarrier(ShaderCollection program, bool inRenderPass)
|
||||
{
|
||||
if (_queuedIncoherentBarrier > IncoherentBarrierType.None)
|
||||
{
|
||||
// We should emit a memory barrier if there's a write access in the program (current program, or program since last barrier)
|
||||
bool hasTextureWrite = _incoherentTextureWriteStages != PipelineStageFlags.None;
|
||||
bool hasBufferWrite = _incoherentBufferWriteStages != PipelineStageFlags.None;
|
||||
bool hasBufferBarrier = _queuedIncoherentBarrier > IncoherentBarrierType.Texture;
|
||||
|
||||
if (hasTextureWrite || (hasBufferBarrier && hasBufferWrite))
|
||||
{
|
||||
AccessFlags access = BaseAccess;
|
||||
|
||||
PipelineStageFlags stages = inRenderPass ? PipelineStageFlags.AllGraphicsBit : PipelineStageFlags.AllCommandsBit;
|
||||
|
||||
if (hasBufferBarrier && hasBufferWrite)
|
||||
{
|
||||
access |= BufferAccess;
|
||||
|
||||
if (_gd.TransformFeedbackApi != null)
|
||||
{
|
||||
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
}
|
||||
|
||||
if (_queuedIncoherentBarrier == IncoherentBarrierType.CommandBuffer)
|
||||
{
|
||||
access |= CommandBufferAccess;
|
||||
stages |= PipelineStageFlags.DrawIndirectBit;
|
||||
}
|
||||
|
||||
MemoryBarrier barrier = new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = access,
|
||||
DstAccessMask = access
|
||||
};
|
||||
|
||||
QueueBarrier(barrier, stages, stages);
|
||||
|
||||
_incoherentTextureWriteStages = program?.IncoherentTextureWriteStages ?? PipelineStageFlags.None;
|
||||
|
||||
if (_queuedIncoherentBarrier > IncoherentBarrierType.Texture)
|
||||
{
|
||||
if (program != null)
|
||||
{
|
||||
_incoherentBufferWriteStages = program.IncoherentBufferWriteStages | _extraStages;
|
||||
}
|
||||
else
|
||||
{
|
||||
_incoherentBufferWriteStages = PipelineStageFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
_queuedIncoherentBarrier = IncoherentBarrierType.None;
|
||||
_queuedFeedbackLoopBarrier = false;
|
||||
}
|
||||
else if (_feedbackLoopActive && _queuedFeedbackLoopBarrier)
|
||||
{
|
||||
// Feedback loop barrier.
|
||||
|
||||
MemoryBarrier barrier = new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.ShaderWriteBit,
|
||||
DstAccessMask = AccessFlags.ShaderReadBit
|
||||
};
|
||||
|
||||
QueueBarrier(barrier, PipelineStageFlags.FragmentShaderBit, PipelineStageFlags.AllGraphicsBit);
|
||||
|
||||
_queuedFeedbackLoopBarrier = false;
|
||||
}
|
||||
|
||||
_feedbackLoopActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||
{
|
||||
Flush(cbs, null, false, inRenderPass, rpHolder, endRenderPass);
|
||||
}
|
||||
|
||||
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool feedbackLoopActive, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||
{
|
||||
if (program != null)
|
||||
{
|
||||
_incoherentBufferWriteStages |= program.IncoherentBufferWriteStages | _extraStages;
|
||||
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
|
||||
}
|
||||
|
||||
_feedbackLoopActive |= feedbackLoopActive;
|
||||
|
||||
FlushMemoryBarrier(program, inRenderPass);
|
||||
|
||||
if (!inRenderPass && rpHolder != null)
|
||||
{
|
||||
// Render pass is about to begin. Queue any fences that normally interrupt the pass.
|
||||
rpHolder.InsertForcedFences(cbs);
|
||||
}
|
||||
|
||||
while (_queuedBarrierCount > 0)
|
||||
{
|
||||
int memoryCount = 0;
|
||||
int bufferCount = 0;
|
||||
int imageCount = 0;
|
||||
|
||||
bool hasBarrier = false;
|
||||
StageFlags flags = default;
|
||||
|
||||
static void AddBarriers<T, T2>(
|
||||
Span<T> target,
|
||||
ref int queuedBarrierCount,
|
||||
ref bool hasBarrier,
|
||||
ref StageFlags flags,
|
||||
ref int count,
|
||||
List<BarrierWithStageFlags<T, T2>> list) where T : unmanaged
|
||||
{
|
||||
int firstMatch = -1;
|
||||
int end = list.Count;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
BarrierWithStageFlags<T, T2> barrier = list[i];
|
||||
|
||||
if (!hasBarrier)
|
||||
{
|
||||
flags = barrier.Flags;
|
||||
hasBarrier = true;
|
||||
|
||||
target[count++] = barrier.Barrier;
|
||||
queuedBarrierCount--;
|
||||
firstMatch = i;
|
||||
|
||||
if (count >= target.Length)
|
||||
{
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flags.Equals(barrier.Flags))
|
||||
{
|
||||
target[count++] = barrier.Barrier;
|
||||
queuedBarrierCount--;
|
||||
|
||||
if (firstMatch == -1)
|
||||
{
|
||||
firstMatch = i;
|
||||
}
|
||||
|
||||
if (count >= target.Length)
|
||||
{
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete consumed barriers from the first match to the current non-match.
|
||||
if (firstMatch != -1)
|
||||
{
|
||||
int deleteCount = i - firstMatch;
|
||||
list.RemoveRange(firstMatch, deleteCount);
|
||||
i -= deleteCount;
|
||||
|
||||
firstMatch = -1;
|
||||
end = list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (firstMatch == 0 && end == list.Count)
|
||||
{
|
||||
list.Clear();
|
||||
}
|
||||
else if (firstMatch != -1)
|
||||
{
|
||||
int deleteCount = end - firstMatch;
|
||||
|
||||
list.RemoveRange(firstMatch, deleteCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (inRenderPass && _imageBarriers.Count > 0)
|
||||
{
|
||||
// Image barriers queued in the batch are meant to be globally scoped,
|
||||
// but inside a render pass they're scoped to just the range of the render pass.
|
||||
|
||||
// On MoltenVK, we just break the rules and always use image barrier.
|
||||
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
||||
// Generally, we want to avoid this from happening in the future, so flag the texture to immediately
|
||||
// emit a barrier whenever the current render pass is bound again.
|
||||
|
||||
bool anyIsNonAttachment = false;
|
||||
|
||||
foreach (BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage> barrier in _imageBarriers)
|
||||
{
|
||||
// If the binding is an attachment, don't add it as a forced fence.
|
||||
bool isAttachment = rpHolder.ContainsAttachment(barrier.Resource);
|
||||
|
||||
if (!isAttachment)
|
||||
{
|
||||
rpHolder.AddForcedFence(barrier.Resource, barrier.Flags.Dest);
|
||||
anyIsNonAttachment = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_gd.IsTBDR)
|
||||
{
|
||||
if (!_gd.IsMoltenVk)
|
||||
{
|
||||
if (!anyIsNonAttachment)
|
||||
{
|
||||
// This case is a feedback loop. To prevent this from causing an absolute performance disaster,
|
||||
// remove the barriers entirely.
|
||||
// If this is not here, there will be a lot of single draw render passes.
|
||||
// TODO: explicit handling for feedback loops, likely outside this class.
|
||||
|
||||
_queuedBarrierCount -= _imageBarriers.Count;
|
||||
_imageBarriers.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TBDR GPUs are sensitive to barriers, so we need to end the pass to ensure the data is available.
|
||||
// Metal already has hazard tracking so MVK doesn't need this.
|
||||
endRenderPass();
|
||||
inRenderPass = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generic pipeline memory barriers will work for desktop GPUs.
|
||||
// They do require a few more access flags on the subpass dependency, though.
|
||||
foreach (var barrier in _imageBarriers)
|
||||
{
|
||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier, int>(
|
||||
barrier.Flags,
|
||||
new MemoryBarrier()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = barrier.Barrier.SrcAccessMask,
|
||||
DstAccessMask = barrier.Barrier.DstAccessMask
|
||||
}));
|
||||
}
|
||||
|
||||
_imageBarriers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (inRenderPass && _memoryBarriers.Count > 0)
|
||||
{
|
||||
PipelineStageFlags allFlags = PipelineStageFlags.None;
|
||||
|
||||
foreach (var barrier in _memoryBarriers)
|
||||
{
|
||||
allFlags |= barrier.Flags.Dest;
|
||||
}
|
||||
|
||||
if (allFlags.HasFlag(PipelineStageFlags.DrawIndirectBit) || !_gd.SupportsRenderPassBarrier(allFlags))
|
||||
{
|
||||
endRenderPass();
|
||||
inRenderPass = false;
|
||||
}
|
||||
}
|
||||
|
||||
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
|
||||
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
||||
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
||||
|
||||
if (hasBarrier)
|
||||
{
|
||||
PipelineStageFlags srcStageFlags = flags.Source;
|
||||
|
||||
if (inRenderPass)
|
||||
{
|
||||
// Inside a render pass, barrier stages can only be from rasterization.
|
||||
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
cbs.CommandBuffer,
|
||||
srcStageFlags,
|
||||
flags.Dest,
|
||||
0,
|
||||
(uint)memoryCount,
|
||||
_memoryBarrierBatch.Pointer,
|
||||
(uint)bufferCount,
|
||||
_bufferBarrierBatch.Pointer,
|
||||
(uint)imageCount,
|
||||
_imageBarrierBatch.Pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueIncoherentBarrier(IncoherentBarrierType type)
|
||||
{
|
||||
if (type > _queuedIncoherentBarrier)
|
||||
{
|
||||
_queuedIncoherentBarrier = type;
|
||||
}
|
||||
|
||||
_queuedFeedbackLoopBarrier = true;
|
||||
}
|
||||
|
||||
public void QueueTextureBarrier()
|
||||
{
|
||||
QueueIncoherentBarrier(IncoherentBarrierType.Texture);
|
||||
}
|
||||
|
||||
public void QueueMemoryBarrier()
|
||||
{
|
||||
QueueIncoherentBarrier(IncoherentBarrierType.All);
|
||||
}
|
||||
|
||||
public void QueueCommandBufferBarrier()
|
||||
{
|
||||
QueueIncoherentBarrier(IncoherentBarrierType.CommandBuffer);
|
||||
}
|
||||
|
||||
public void EnableTfbBarriers(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
_extraStages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
else
|
||||
{
|
||||
_extraStages &= ~PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_memoryBarrierBatch.Dispose();
|
||||
_bufferBarrierBatch.Dispose();
|
||||
_imageBarrierBatch.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
157
src/Ryujinx.Graphics.Rdna3Vulkan/BitMap.cs
Normal file
157
src/Ryujinx.Graphics.Rdna3Vulkan/BitMap.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
263
src/Ryujinx.Graphics.Rdna3Vulkan/BitMapStruct.cs
Normal file
263
src/Ryujinx.Graphics.Rdna3Vulkan/BitMapStruct.cs
Normal file
|
@ -0,0 +1,263 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
interface IBitMapListener
|
||||
{
|
||||
void BitMapSignal(int index, int count);
|
||||
}
|
||||
|
||||
struct BitMapStruct<T> where T : IArray<long>
|
||||
{
|
||||
public const int IntSize = 64;
|
||||
|
||||
private const int IntShift = 6;
|
||||
private const int IntMask = IntSize - 1;
|
||||
|
||||
private T _masks;
|
||||
|
||||
public BitMapStruct()
|
||||
{
|
||||
_masks = default;
|
||||
}
|
||||
|
||||
public bool BecomesUnsetFrom(in BitMapStruct<T> from, ref BitMapStruct<T> into)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
int masks = _masks.Length;
|
||||
for (int i = 0; i < masks; i++)
|
||||
{
|
||||
long fromMask = from._masks[i];
|
||||
long unsetMask = (~fromMask) & (fromMask ^ _masks[i]);
|
||||
into._masks[i] = unsetMask;
|
||||
|
||||
result |= unsetMask != 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SetAndSignalUnset<T2>(in BitMapStruct<T> from, ref T2 listener) where T2 : struct, IBitMapListener
|
||||
{
|
||||
BitMapStruct<T> result = new();
|
||||
|
||||
if (BecomesUnsetFrom(from, ref result))
|
||||
{
|
||||
// Iterate the set bits in the result, and signal them.
|
||||
|
||||
int offset = 0;
|
||||
int masks = _masks.Length;
|
||||
ref T resultMasks = ref result._masks;
|
||||
for (int i = 0; i < masks; i++)
|
||||
{
|
||||
long value = resultMasks[i];
|
||||
while (value != 0)
|
||||
{
|
||||
int bit = BitOperations.TrailingZeroCount((ulong)value);
|
||||
|
||||
listener.BitMapSignal(offset + bit, 1);
|
||||
|
||||
value &= ~(1L << bit);
|
||||
}
|
||||
|
||||
offset += IntSize;
|
||||
}
|
||||
}
|
||||
|
||||
_masks = from._masks;
|
||||
}
|
||||
|
||||
public void SignalSet(Action<int, int> action)
|
||||
{
|
||||
// Iterate the set bits in the result, and signal them.
|
||||
|
||||
int offset = 0;
|
||||
int masks = _masks.Length;
|
||||
for (int i = 0; i < masks; i++)
|
||||
{
|
||||
long value = _masks[i];
|
||||
while (value != 0)
|
||||
{
|
||||
int bit = BitOperations.TrailingZeroCount((ulong)value);
|
||||
|
||||
action(offset + bit, 1);
|
||||
|
||||
value &= ~(1L << bit);
|
||||
}
|
||||
|
||||
offset += 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 Set(int bit, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Set(bit);
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear(bit);
|
||||
}
|
||||
}
|
||||
|
||||
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] |= -1L;
|
||||
}
|
||||
|
||||
_masks[endIndex] |= endMask;
|
||||
}
|
||||
}
|
||||
|
||||
public BitMapStruct<T> Union(BitMapStruct<T> other)
|
||||
{
|
||||
var result = new BitMapStruct<T>();
|
||||
|
||||
ref var masks = ref _masks;
|
||||
ref var otherMasks = ref other._masks;
|
||||
ref var newMasks = ref result._masks;
|
||||
|
||||
for (int i = 0; i < masks.Length; i++)
|
||||
{
|
||||
newMasks[i] = masks[i] | otherMasks[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
src/Ryujinx.Graphics.Rdna3Vulkan/BufferAllocationType.cs
Normal file
13
src/Ryujinx.Graphics.Rdna3Vulkan/BufferAllocationType.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
internal enum BufferAllocationType
|
||||
{
|
||||
Auto = 0,
|
||||
|
||||
HostMappedNoCache,
|
||||
HostMapped,
|
||||
DeviceLocal,
|
||||
DeviceLocalMapped,
|
||||
Sparse,
|
||||
}
|
||||
}
|
922
src/Ryujinx.Graphics.Rdna3Vulkan/BufferHolder.cs
Normal file
922
src/Ryujinx.Graphics.Rdna3Vulkan/BufferHolder.cs
Normal file
|
@ -0,0 +1,922 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class BufferHolder : IDisposable, IMirrorable<DisposableBuffer>, IMirrorable<DisposableBufferView>
|
||||
{
|
||||
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 readonly MemoryAllocation _allocation;
|
||||
private readonly Auto<DisposableBuffer> _buffer;
|
||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
||||
private readonly bool _allocationImported;
|
||||
private readonly ulong _bufferHandle;
|
||||
|
||||
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
||||
|
||||
public int Size { get; }
|
||||
|
||||
private readonly nint _map;
|
||||
|
||||
private readonly MultiFenceHolder _waitable;
|
||||
|
||||
private bool _lastAccessIsWrite;
|
||||
|
||||
private readonly BufferAllocationType _baseType;
|
||||
private readonly BufferAllocationType _activeType;
|
||||
|
||||
private readonly ReaderWriterLockSlim _flushLock;
|
||||
private FenceHolder _flushFence;
|
||||
private int _flushWaiting;
|
||||
|
||||
private byte[] _pendingData;
|
||||
private BufferMirrorRangeList _pendingDataRanges;
|
||||
private Dictionary<ulong, StagingBufferReserved> _mirrors;
|
||||
private bool _useMirrors;
|
||||
|
||||
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), this, _waitable, _allocationAuto);
|
||||
_bufferHandle = buffer.Handle;
|
||||
Size = size;
|
||||
_map = allocation.HostPointer;
|
||||
|
||||
_baseType = type;
|
||||
_activeType = currentType;
|
||||
|
||||
_flushLock = new ReaderWriterLockSlim();
|
||||
_useMirrors = gd.IsTBDR;
|
||||
}
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, Auto<MemoryAllocation> allocation, int size, BufferAllocationType type, BufferAllocationType currentType, int offset)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
_allocation = allocation.GetUnsafe();
|
||||
_allocationAuto = allocation;
|
||||
_allocationImported = true;
|
||||
_waitable = new MultiFenceHolder(size);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), this, _waitable, _allocationAuto);
|
||||
_bufferHandle = buffer.Handle;
|
||||
Size = size;
|
||||
_map = _allocation.HostPointer + offset;
|
||||
|
||||
_baseType = type;
|
||||
_activeType = currentType;
|
||||
|
||||
_flushLock = new ReaderWriterLockSlim();
|
||||
}
|
||||
|
||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, int size, Auto<MemoryAllocation>[] storageAllocations)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
_waitable = new MultiFenceHolder(size);
|
||||
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, storageAllocations);
|
||||
_bufferHandle = buffer.Handle;
|
||||
Size = size;
|
||||
|
||||
_baseType = BufferAllocationType.Sparse;
|
||||
_activeType = BufferAllocationType.Sparse;
|
||||
|
||||
_flushLock = new ReaderWriterLockSlim();
|
||||
}
|
||||
|
||||
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, in bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
|
||||
|
||||
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer);
|
||||
}
|
||||
|
||||
public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
|
||||
{
|
||||
// If the last access is write, we always need a barrier to be sure we will read or modify
|
||||
// the correct data.
|
||||
// If the last access is read, and current one is a write, we need to wait until the
|
||||
// read finishes to avoid overwriting data still in use.
|
||||
// Otherwise, if the last access is a read and the current one too, we don't need barriers.
|
||||
bool needsBarrier = isWrite || _lastAccessIsWrite;
|
||||
|
||||
_lastAccessIsWrite = isWrite;
|
||||
|
||||
if (needsBarrier)
|
||||
{
|
||||
MemoryBarrier memoryBarrier = new()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = DefaultAccessFlags,
|
||||
DstAccessMask = DefaultAccessFlags,
|
||||
};
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
DependencyFlags.DeviceGroupBit,
|
||||
1,
|
||||
in memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong ToMirrorKey(int offset, int size)
|
||||
{
|
||||
return ((ulong)offset << 32) | (uint)size;
|
||||
}
|
||||
|
||||
private static (int offset, int size) FromMirrorKey(ulong key)
|
||||
{
|
||||
return ((int)(key >> 32), (int)key);
|
||||
}
|
||||
|
||||
private unsafe bool TryGetMirror(CommandBufferScoped cbs, ref int offset, int size, out Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
size = Math.Min(size, Size - offset);
|
||||
|
||||
// Does this binding need to be mirrored?
|
||||
|
||||
if (!_pendingDataRanges.OverlapsWith(offset, size))
|
||||
{
|
||||
buffer = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = ToMirrorKey(offset, size);
|
||||
|
||||
if (_mirrors.TryGetValue(key, out StagingBufferReserved reserved))
|
||||
{
|
||||
buffer = reserved.Buffer.GetBuffer();
|
||||
offset = reserved.Offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is this mirror allowed to exist? Can't be used for write in any in-flight write.
|
||||
if (_waitable.IsBufferRangeInUse(offset, size, true))
|
||||
{
|
||||
// Some of the data is not mirrorable, so upload the whole range.
|
||||
ClearMirrors(cbs, offset, size);
|
||||
|
||||
buffer = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build data for the new mirror.
|
||||
|
||||
var baseData = new Span<byte>((void*)(_map + offset), size);
|
||||
var modData = _pendingData.AsSpan(offset, size);
|
||||
|
||||
StagingBufferReserved? newMirror = _gd.BufferManager.StagingBuffer.TryReserveData(cbs, size);
|
||||
|
||||
if (newMirror != null)
|
||||
{
|
||||
var mirror = newMirror.Value;
|
||||
_pendingDataRanges.FillData(baseData, modData, offset, new Span<byte>((void*)(mirror.Buffer._map + mirror.Offset), size));
|
||||
|
||||
if (_mirrors.Count == 0)
|
||||
{
|
||||
_gd.PipelineInternal.RegisterActiveMirror(this);
|
||||
}
|
||||
|
||||
_mirrors.Add(key, mirror);
|
||||
|
||||
buffer = mirror.Buffer.GetBuffer();
|
||||
offset = mirror.Offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Data could not be placed on the mirror, likely out of space. Force the data to flush.
|
||||
ClearMirrors(cbs, offset, size);
|
||||
|
||||
buffer = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer()
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
SignalWrite(0, Size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, int offset, int size, bool isWrite = false)
|
||||
{
|
||||
if (isWrite)
|
||||
{
|
||||
SignalWrite(offset, size);
|
||||
}
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
public Auto<DisposableBuffer> GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
|
||||
{
|
||||
if (_pendingData != null && TryGetMirror(cbs, ref offset, size, out Auto<DisposableBuffer> result))
|
||||
{
|
||||
mirrored = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
mirrored = false;
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
Auto<DisposableBufferView> IMirrorable<DisposableBufferView>.GetMirrorable(CommandBufferScoped cbs, ref int offset, int size, out bool mirrored)
|
||||
{
|
||||
// Cannot mirror buffer views right now.
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ClearMirrors()
|
||||
{
|
||||
// Clear mirrors without forcing a flush. This happens when the command buffer is switched,
|
||||
// as all reserved areas on the staging buffer are released.
|
||||
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_mirrors.Clear();
|
||||
};
|
||||
}
|
||||
|
||||
public void ClearMirrors(CommandBufferScoped cbs, int offset, int size)
|
||||
{
|
||||
// Clear mirrors in the given range, and submit overlapping pending data.
|
||||
|
||||
if (_pendingData != null)
|
||||
{
|
||||
bool hadMirrors = _mirrors.Count > 0 && RemoveOverlappingMirrors(offset, size);
|
||||
|
||||
if (_pendingDataRanges.Count() != 0)
|
||||
{
|
||||
UploadPendingData(cbs, offset, size);
|
||||
}
|
||||
|
||||
if (hadMirrors)
|
||||
{
|
||||
_gd.PipelineInternal.Rebind(_buffer, offset, size);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void UseMirrors()
|
||||
{
|
||||
_useMirrors = true;
|
||||
}
|
||||
|
||||
private void UploadPendingData(CommandBufferScoped cbs, int offset, int size)
|
||||
{
|
||||
var ranges = _pendingDataRanges.FindOverlaps(offset, size);
|
||||
|
||||
if (ranges != null)
|
||||
{
|
||||
_pendingDataRanges.Remove(offset, size);
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
int rangeOffset = Math.Max(offset, range.Offset);
|
||||
int rangeSize = Math.Min(offset + size, range.End) - rangeOffset;
|
||||
|
||||
if (_gd.PipelineInternal.CurrentCommandBuffer.CommandBuffer.Handle == cbs.CommandBuffer.Handle)
|
||||
{
|
||||
SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, _gd.PipelineInternal.EndRenderPassDelegate, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetData(rangeOffset, _pendingData.AsSpan(rangeOffset, rangeSize), cbs, null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Auto<MemoryAllocation> GetAllocation()
|
||||
{
|
||||
return _allocationAuto;
|
||||
}
|
||||
|
||||
public (DeviceMemory, ulong) GetDeviceMemoryAndOffset()
|
||||
{
|
||||
return (_allocation.Memory, _allocation.Offset);
|
||||
}
|
||||
|
||||
public void SignalWrite(int offset, int size)
|
||||
{
|
||||
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 nint Map(int offset, int mappingSize)
|
||||
{
|
||||
return _map;
|
||||
}
|
||||
|
||||
private void ClearFlushFence()
|
||||
{
|
||||
// Assumes _flushLock is held as writer.
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
if (_flushWaiting == 0)
|
||||
{
|
||||
_flushFence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForFlushFence()
|
||||
{
|
||||
if (_flushFence == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
||||
_flushLock.ExitReadLock();
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
if (_flushFence != null)
|
||||
{
|
||||
var fence = _flushFence;
|
||||
Interlocked.Increment(ref _flushWaiting);
|
||||
|
||||
// Don't wait in the lock.
|
||||
|
||||
_flushLock.ExitWriteLock();
|
||||
|
||||
fence.Wait();
|
||||
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
||||
{
|
||||
fence.Put();
|
||||
}
|
||||
|
||||
_flushFence = null;
|
||||
}
|
||||
|
||||
// Assumes the _flushLock is held as reader, returns in same state.
|
||||
_flushLock.ExitWriteLock();
|
||||
_flushLock.EnterReadLock();
|
||||
}
|
||||
|
||||
public PinnedSpan<byte> GetData(int offset, int size)
|
||||
{
|
||||
_flushLock.EnterReadLock();
|
||||
|
||||
WaitForFlushFence();
|
||||
|
||||
Span<byte> result;
|
||||
|
||||
if (_map != nint.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.ExitReadLock();
|
||||
|
||||
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
||||
}
|
||||
|
||||
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.ExitReadLock();
|
||||
|
||||
// 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 != nint.Zero)
|
||||
{
|
||||
return new Span<byte>((void*)(_map + offset), mappingSize);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("The buffer is not host mapped.");
|
||||
}
|
||||
|
||||
public bool RemoveOverlappingMirrors(int offset, int size)
|
||||
{
|
||||
List<ulong> toRemove = null;
|
||||
foreach (var key in _mirrors.Keys)
|
||||
{
|
||||
(int keyOffset, int keySize) = FromMirrorKey(key);
|
||||
if (!(offset + size <= keyOffset || offset >= keyOffset + keySize))
|
||||
{
|
||||
toRemove ??= new List<ulong>();
|
||||
|
||||
toRemove.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
_mirrors.Remove(key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null, bool allowCbsWait = true)
|
||||
{
|
||||
int dataSize = Math.Min(data.Length, Size - offset);
|
||||
if (dataSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool allowMirror = _useMirrors && allowCbsWait && cbs != null && _activeType <= BufferAllocationType.HostMapped;
|
||||
|
||||
if (_map != nint.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, false);
|
||||
|
||||
if (!needsFlush)
|
||||
{
|
||||
WaitForFences(offset, dataSize);
|
||||
|
||||
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
|
||||
if (_pendingData != null)
|
||||
{
|
||||
bool removed = _pendingDataRanges.Remove(offset, dataSize);
|
||||
if (RemoveOverlappingMirrors(offset, dataSize) || removed)
|
||||
{
|
||||
// If any mirrors were removed, rebind the buffer range.
|
||||
_gd.PipelineInternal.Rebind(_buffer, offset, dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
SignalWrite(offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the buffer does not have an in-flight write (including an inline update), then upload data to a pendingCopy.
|
||||
if (allowMirror && !_waitable.IsBufferRangeInUse(offset, dataSize, true))
|
||||
{
|
||||
if (_pendingData == null)
|
||||
{
|
||||
_pendingData = new byte[Size];
|
||||
_mirrors = new Dictionary<ulong, StagingBufferReserved>();
|
||||
}
|
||||
|
||||
data[..dataSize].CopyTo(_pendingData.AsSpan(offset, dataSize));
|
||||
_pendingDataRanges.Add(offset, dataSize);
|
||||
|
||||
// Remove any overlapping mirrors.
|
||||
RemoveOverlappingMirrors(offset, dataSize);
|
||||
|
||||
// Tell the graphics device to rebind any constant buffer that overlaps the newly modified range, as it should access a mirror.
|
||||
_gd.PipelineInternal.Rebind(_buffer, offset, dataSize);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingDataRanges.Remove(offset, dataSize);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
if (allowCbsWait)
|
||||
{
|
||||
_gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool rentCbs = cbs == null;
|
||||
if (rentCbs)
|
||||
{
|
||||
cbs = _gd.CommandBufferPool.Rent();
|
||||
}
|
||||
|
||||
if (!_gd.BufferManager.StagingBuffer.TryPushData(cbs.Value, endRenderPass, this, offset, data))
|
||||
{
|
||||
// Need to do a slow upload.
|
||||
BufferHolder srcHolder = _gd.BufferManager.Create(_gd, dataSize, baseType: BufferAllocationType.HostMapped);
|
||||
srcHolder.SetDataUnchecked(0, data);
|
||||
|
||||
var srcBuffer = srcHolder.GetBuffer();
|
||||
var dstBuffer = this.GetBuffer(cbs.Value.CommandBuffer, true);
|
||||
|
||||
Copy(_gd, cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize);
|
||||
|
||||
srcHolder.Dispose();
|
||||
}
|
||||
|
||||
if (rentCbs)
|
||||
{
|
||||
cbs.Value.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetDataUnchecked(int offset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
int dataSize = Math.Min(data.Length, Size - offset);
|
||||
if (dataSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map != nint.Zero)
|
||||
{
|
||||
data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
_gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, null, null, this, offset, data);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetDataUnchecked<T>(int offset, ReadOnlySpan<T> data) where T : unmanaged
|
||||
{
|
||||
SetDataUnchecked(offset, MemoryMarshal.AsBytes(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, true).Value;
|
||||
|
||||
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,
|
||||
bool registerSrcUsage = true)
|
||||
{
|
||||
var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
|
||||
var dstBuffer = dst.Get(cbs, dstOffset, size, true).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()
|
||||
{
|
||||
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,
|
||||
in 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, baseType: BufferAllocationType.DeviceLocal);
|
||||
|
||||
_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, baseType: BufferAllocationType.DeviceLocal);
|
||||
|
||||
_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, baseType: BufferAllocationType.DeviceLocal);
|
||||
|
||||
_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()
|
||||
{
|
||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||
|
||||
_buffer.Dispose();
|
||||
_cachedConvertedBuffers.Dispose();
|
||||
if (_allocationImported)
|
||||
{
|
||||
_allocationAuto.DecrementReferenceCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
_allocationAuto?.Dispose();
|
||||
}
|
||||
|
||||
_flushLock.EnterWriteLock();
|
||||
|
||||
ClearFlushFence();
|
||||
|
||||
_flushLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
679
src/Ryujinx.Graphics.Rdna3Vulkan/BufferManager.cs
Normal file
679
src/Ryujinx.Graphics.Rdna3Vulkan/BufferManager.cs
Normal file
|
@ -0,0 +1,679 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
readonly struct ScopedTemporaryBuffer : IDisposable
|
||||
{
|
||||
private readonly BufferManager _bufferManager;
|
||||
private readonly bool _isReserved;
|
||||
|
||||
public readonly BufferRange Range;
|
||||
public readonly BufferHolder Holder;
|
||||
|
||||
public BufferHandle Handle => Range.Handle;
|
||||
public int Offset => Range.Offset;
|
||||
|
||||
public ScopedTemporaryBuffer(BufferManager bufferManager, BufferHolder holder, BufferHandle handle, int offset, int size, bool isReserved)
|
||||
{
|
||||
_bufferManager = bufferManager;
|
||||
|
||||
Range = new BufferRange(handle, offset, size);
|
||||
Holder = holder;
|
||||
|
||||
_isReserved = isReserved;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isReserved)
|
||||
{
|
||||
_bufferManager.Delete(Range.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BufferManager : IDisposable
|
||||
{
|
||||
public 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 const BufferUsageFlags HostImportedBufferUsageFlags =
|
||||
BufferUsageFlags.TransferSrcBit |
|
||||
BufferUsageFlags.TransferDstBit;
|
||||
|
||||
private readonly Device _device;
|
||||
|
||||
private readonly IdList<BufferHolder> _buffers;
|
||||
|
||||
public int BufferCount { get; private set; }
|
||||
|
||||
public StagingBuffer StagingBuffer { get; }
|
||||
|
||||
public MemoryRequirements HostImportedBufferMemoryRequirements { get; }
|
||||
|
||||
public BufferManager(VulkanRenderer gd, Device device)
|
||||
{
|
||||
_device = device;
|
||||
_buffers = new IdList<BufferHolder>();
|
||||
StagingBuffer = new StagingBuffer(gd, this);
|
||||
|
||||
HostImportedBufferMemoryRequirements = GetHostImportedUsageRequirements(gd);
|
||||
}
|
||||
|
||||
public unsafe BufferHandle CreateHostImported(VulkanRenderer gd, nint pointer, int size)
|
||||
{
|
||||
var usage = HostImportedBufferUsageFlags;
|
||||
|
||||
if (gd.Capabilities.SupportsIndirectParameters)
|
||||
{
|
||||
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||
}
|
||||
|
||||
var externalMemoryBuffer = new ExternalMemoryBufferCreateInfo
|
||||
{
|
||||
SType = StructureType.ExternalMemoryBufferCreateInfo,
|
||||
HandleTypes = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
|
||||
};
|
||||
|
||||
var bufferCreateInfo = new BufferCreateInfo
|
||||
{
|
||||
SType = StructureType.BufferCreateInfo,
|
||||
Size = (ulong)size,
|
||||
Usage = usage,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
PNext = &externalMemoryBuffer,
|
||||
};
|
||||
|
||||
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
||||
|
||||
(Auto<MemoryAllocation> allocation, ulong offset) = gd.HostMemoryAllocator.GetExistingAllocation(pointer, (ulong)size);
|
||||
|
||||
gd.Api.BindBufferMemory(_device, buffer, allocation.GetUnsafe().Memory, allocation.GetUnsafe().Offset + offset);
|
||||
|
||||
var holder = new BufferHolder(gd, _device, buffer, allocation, size, BufferAllocationType.HostMapped, BufferAllocationType.HostMapped, (int)offset);
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public unsafe BufferHandle CreateSparse(VulkanRenderer gd, ReadOnlySpan<BufferRange> storageBuffers)
|
||||
{
|
||||
var usage = DefaultBufferUsageFlags;
|
||||
|
||||
if (gd.Capabilities.SupportsIndirectParameters)
|
||||
{
|
||||
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||
}
|
||||
|
||||
ulong size = 0;
|
||||
|
||||
foreach (BufferRange range in storageBuffers)
|
||||
{
|
||||
size += (ulong)range.Size;
|
||||
}
|
||||
|
||||
var bufferCreateInfo = new BufferCreateInfo()
|
||||
{
|
||||
SType = StructureType.BufferCreateInfo,
|
||||
Size = size,
|
||||
Usage = usage,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
Flags = BufferCreateFlags.SparseBindingBit | BufferCreateFlags.SparseAliasedBit
|
||||
};
|
||||
|
||||
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
||||
|
||||
var memoryBinds = new SparseMemoryBind[storageBuffers.Length];
|
||||
var storageAllocations = new Auto<MemoryAllocation>[storageBuffers.Length];
|
||||
int storageAllocationsCount = 0;
|
||||
|
||||
ulong dstOffset = 0;
|
||||
|
||||
for (int index = 0; index < storageBuffers.Length; index++)
|
||||
{
|
||||
BufferRange range = storageBuffers[index];
|
||||
|
||||
if (TryGetBuffer(range.Handle, out var existingHolder))
|
||||
{
|
||||
(var memory, var offset) = existingHolder.GetDeviceMemoryAndOffset();
|
||||
|
||||
memoryBinds[index] = new SparseMemoryBind()
|
||||
{
|
||||
ResourceOffset = dstOffset,
|
||||
Size = (ulong)range.Size,
|
||||
Memory = memory,
|
||||
MemoryOffset = offset + (ulong)range.Offset,
|
||||
Flags = SparseMemoryBindFlags.None
|
||||
};
|
||||
|
||||
storageAllocations[storageAllocationsCount++] = existingHolder.GetAllocation();
|
||||
}
|
||||
else
|
||||
{
|
||||
memoryBinds[index] = new SparseMemoryBind()
|
||||
{
|
||||
ResourceOffset = dstOffset,
|
||||
Size = (ulong)range.Size,
|
||||
Memory = default,
|
||||
MemoryOffset = 0UL,
|
||||
Flags = SparseMemoryBindFlags.None
|
||||
};
|
||||
}
|
||||
|
||||
dstOffset += (ulong)range.Size;
|
||||
}
|
||||
|
||||
if (storageAllocations.Length != storageAllocationsCount)
|
||||
{
|
||||
Array.Resize(ref storageAllocations, storageAllocationsCount);
|
||||
}
|
||||
|
||||
fixed (SparseMemoryBind* pMemoryBinds = memoryBinds)
|
||||
{
|
||||
SparseBufferMemoryBindInfo bufferBind = new SparseBufferMemoryBindInfo()
|
||||
{
|
||||
Buffer = buffer,
|
||||
BindCount = (uint)memoryBinds.Length,
|
||||
PBinds = pMemoryBinds
|
||||
};
|
||||
|
||||
BindSparseInfo bindSparseInfo = new BindSparseInfo()
|
||||
{
|
||||
SType = StructureType.BindSparseInfo,
|
||||
BufferBindCount = 1,
|
||||
PBufferBinds = &bufferBind
|
||||
};
|
||||
|
||||
gd.Api.QueueBindSparse(gd.Queue, 1, in bindSparseInfo, default).ThrowOnError();
|
||||
}
|
||||
|
||||
var holder = new BufferHolder(gd, _device, buffer, (int)size, storageAllocations);
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
bool sparseCompatible = false,
|
||||
BufferAllocationType baseType = BufferAllocationType.HostMapped,
|
||||
bool forceMirrors = false)
|
||||
{
|
||||
return CreateWithHandle(gd, size, out _, sparseCompatible, baseType, forceMirrors);
|
||||
}
|
||||
|
||||
public BufferHandle CreateWithHandle(
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
out BufferHolder holder,
|
||||
bool sparseCompatible = false,
|
||||
BufferAllocationType baseType = BufferAllocationType.HostMapped,
|
||||
bool forceMirrors = false)
|
||||
{
|
||||
holder = Create(gd, size, forConditionalRendering: false, sparseCompatible, baseType);
|
||||
if (holder == null)
|
||||
{
|
||||
return BufferHandle.Null;
|
||||
}
|
||||
|
||||
if (forceMirrors)
|
||||
{
|
||||
holder.UseMirrors();
|
||||
}
|
||||
|
||||
BufferCount++;
|
||||
|
||||
ulong handle64 = (uint)_buffers.Add(holder);
|
||||
|
||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||
}
|
||||
|
||||
public ScopedTemporaryBuffer ReserveOrCreate(VulkanRenderer gd, CommandBufferScoped cbs, int size)
|
||||
{
|
||||
StagingBufferReserved? result = StagingBuffer.TryReserveData(cbs, size);
|
||||
|
||||
if (result.HasValue)
|
||||
{
|
||||
return new ScopedTemporaryBuffer(this, result.Value.Buffer, StagingBuffer.Handle, result.Value.Offset, result.Value.Size, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a temporary buffer.
|
||||
BufferHandle handle = CreateWithHandle(gd, size, out BufferHolder holder);
|
||||
|
||||
return new ScopedTemporaryBuffer(this, holder, handle, 0, size, false);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe MemoryRequirements GetHostImportedUsageRequirements(VulkanRenderer gd)
|
||||
{
|
||||
var usage = HostImportedBufferUsageFlags;
|
||||
|
||||
if (gd.Capabilities.SupportsIndirectParameters)
|
||||
{
|
||||
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||
}
|
||||
|
||||
var bufferCreateInfo = new BufferCreateInfo
|
||||
{
|
||||
SType = StructureType.BufferCreateInfo,
|
||||
Size = (ulong)Environment.SystemPageSize,
|
||||
Usage = usage,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
};
|
||||
|
||||
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
||||
|
||||
gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
|
||||
|
||||
gd.Api.DestroyBuffer(_device, buffer, null);
|
||||
|
||||
return requirements;
|
||||
}
|
||||
|
||||
public unsafe (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) CreateBacking(
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
BufferAllocationType type,
|
||||
bool forConditionalRendering = false,
|
||||
bool sparseCompatible = 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);
|
||||
|
||||
if (sparseCompatible)
|
||||
{
|
||||
requirements.Alignment = Math.Max(requirements.Alignment, Constants.SparseBufferAlignment);
|
||||
}
|
||||
|
||||
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 BufferHolder Create(
|
||||
VulkanRenderer gd,
|
||||
int size,
|
||||
bool forConditionalRendering = false,
|
||||
bool sparseCompatible = false,
|
||||
BufferAllocationType baseType = BufferAllocationType.HostMapped)
|
||||
{
|
||||
BufferAllocationType type = baseType;
|
||||
|
||||
if (baseType == BufferAllocationType.Auto)
|
||||
{
|
||||
type = BufferAllocationType.HostMapped;
|
||||
}
|
||||
|
||||
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) =
|
||||
CreateBacking(gd, size, type, forConditionalRendering, sparseCompatible);
|
||||
|
||||
if (buffer.Handle != 0)
|
||||
{
|
||||
var holder = new BufferHolder(gd, _device, buffer, allocation, size, baseType, resultType);
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X} and type \"{baseType}\".");
|
||||
|
||||
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)
|
||||
{
|
||||
StagingBuffer.Dispose();
|
||||
|
||||
foreach (BufferHolder buffer in _buffers)
|
||||
{
|
||||
buffer.Dispose();
|
||||
}
|
||||
|
||||
_buffers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
305
src/Ryujinx.Graphics.Rdna3Vulkan/BufferMirrorRangeList.cs
Normal file
305
src/Ryujinx.Graphics.Rdna3Vulkan/BufferMirrorRangeList.cs
Normal file
|
@ -0,0 +1,305 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure tracking pending upload ranges for buffers.
|
||||
/// Where a range is present, pending data exists that can either be used to build mirrors
|
||||
/// or upload directly to the buffer.
|
||||
/// </summary>
|
||||
struct BufferMirrorRangeList
|
||||
{
|
||||
internal readonly struct Range
|
||||
{
|
||||
public int Offset { get; }
|
||||
public int Size { get; }
|
||||
|
||||
public int End => Offset + Size;
|
||||
|
||||
public Range(int offset, int size)
|
||||
{
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public bool OverlapsWith(int offset, int size)
|
||||
{
|
||||
return Offset < offset + size && offset < Offset + Size;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Range> _ranges;
|
||||
|
||||
public readonly IEnumerable<Range> All()
|
||||
{
|
||||
return _ranges;
|
||||
}
|
||||
|
||||
public readonly bool Remove(int offset, int size)
|
||||
{
|
||||
var list = _ranges;
|
||||
bool removedAny = false;
|
||||
if (list != null)
|
||||
{
|
||||
int overlapIndex = BinarySearch(list, offset, size);
|
||||
|
||||
if (overlapIndex >= 0)
|
||||
{
|
||||
// Overlaps with a range. Search back to find the first one it doesn't overlap with.
|
||||
|
||||
while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size))
|
||||
{
|
||||
overlapIndex--;
|
||||
}
|
||||
|
||||
int endOffset = offset + size;
|
||||
int startIndex = overlapIndex;
|
||||
|
||||
var currentOverlap = list[overlapIndex];
|
||||
|
||||
// Orphan the start of the overlap.
|
||||
if (currentOverlap.Offset < offset)
|
||||
{
|
||||
list[overlapIndex] = new Range(currentOverlap.Offset, offset - currentOverlap.Offset);
|
||||
currentOverlap = new Range(offset, currentOverlap.End - offset);
|
||||
list.Insert(++overlapIndex, currentOverlap);
|
||||
startIndex++;
|
||||
|
||||
removedAny = true;
|
||||
}
|
||||
|
||||
// Remove any middle overlaps.
|
||||
while (currentOverlap.Offset < endOffset)
|
||||
{
|
||||
if (currentOverlap.End > endOffset)
|
||||
{
|
||||
// Update the end overlap instead of removing it, if it spans beyond the removed range.
|
||||
list[overlapIndex] = new Range(endOffset, currentOverlap.End - endOffset);
|
||||
|
||||
removedAny = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (++overlapIndex >= list.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currentOverlap = list[overlapIndex];
|
||||
}
|
||||
|
||||
int count = overlapIndex - startIndex;
|
||||
|
||||
list.RemoveRange(startIndex, count);
|
||||
|
||||
removedAny |= count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return removedAny;
|
||||
}
|
||||
|
||||
public void Add(int offset, int size)
|
||||
{
|
||||
var list = _ranges;
|
||||
if (list != null)
|
||||
{
|
||||
int overlapIndex = BinarySearch(list, offset, size);
|
||||
if (overlapIndex >= 0)
|
||||
{
|
||||
while (overlapIndex > 0 && list[overlapIndex - 1].OverlapsWith(offset, size))
|
||||
{
|
||||
overlapIndex--;
|
||||
}
|
||||
|
||||
int endOffset = offset + size;
|
||||
int startIndex = overlapIndex;
|
||||
|
||||
while (overlapIndex < list.Count && list[overlapIndex].OverlapsWith(offset, size))
|
||||
{
|
||||
var currentOverlap = list[overlapIndex];
|
||||
var currentOverlapEndOffset = currentOverlap.Offset + currentOverlap.Size;
|
||||
|
||||
if (offset > currentOverlap.Offset)
|
||||
{
|
||||
offset = currentOverlap.Offset;
|
||||
}
|
||||
|
||||
if (endOffset < currentOverlapEndOffset)
|
||||
{
|
||||
endOffset = currentOverlapEndOffset;
|
||||
}
|
||||
|
||||
overlapIndex++;
|
||||
size = endOffset - offset;
|
||||
}
|
||||
|
||||
int count = overlapIndex - startIndex;
|
||||
|
||||
list.RemoveRange(startIndex, count);
|
||||
|
||||
overlapIndex = startIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
overlapIndex = ~overlapIndex;
|
||||
}
|
||||
|
||||
list.Insert(overlapIndex, new Range(offset, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
_ranges = new List<Range>
|
||||
{
|
||||
new Range(offset, size)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool OverlapsWith(int offset, int size)
|
||||
{
|
||||
var list = _ranges;
|
||||
if (list == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return BinarySearch(list, offset, size) >= 0;
|
||||
}
|
||||
|
||||
public readonly List<Range> FindOverlaps(int offset, int size)
|
||||
{
|
||||
var list = _ranges;
|
||||
if (list == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Range> result = null;
|
||||
|
||||
int index = BinarySearch(list, offset, size);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
while (index > 0 && list[index - 1].OverlapsWith(offset, size))
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
(result ??= []).Add(list[index++]);
|
||||
}
|
||||
while (index < list.Count && list[index].OverlapsWith(offset, size));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int BinarySearch(List<Range> list, int offset, int size)
|
||||
{
|
||||
int left = 0;
|
||||
int right = list.Count - 1;
|
||||
|
||||
while (left <= right)
|
||||
{
|
||||
int range = right - left;
|
||||
|
||||
int middle = left + (range >> 1);
|
||||
|
||||
var item = list[middle];
|
||||
|
||||
if (item.OverlapsWith(offset, size))
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
|
||||
if (offset < item.Offset)
|
||||
{
|
||||
right = middle - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~left;
|
||||
}
|
||||
|
||||
public readonly void FillData(Span<byte> baseData, Span<byte> modData, int offset, Span<byte> result)
|
||||
{
|
||||
int size = baseData.Length;
|
||||
int endOffset = offset + size;
|
||||
|
||||
var list = _ranges;
|
||||
if (list == null)
|
||||
{
|
||||
baseData.CopyTo(result);
|
||||
}
|
||||
|
||||
int srcOffset = offset;
|
||||
int dstOffset = 0;
|
||||
bool activeRange = false;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
var range = list[i];
|
||||
|
||||
int rangeEnd = range.Offset + range.Size;
|
||||
|
||||
if (activeRange)
|
||||
{
|
||||
if (range.Offset >= endOffset)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rangeEnd <= offset)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
activeRange = true;
|
||||
}
|
||||
|
||||
int baseSize = range.Offset - srcOffset;
|
||||
|
||||
if (baseSize > 0)
|
||||
{
|
||||
baseData.Slice(dstOffset, baseSize).CopyTo(result.Slice(dstOffset, baseSize));
|
||||
srcOffset += baseSize;
|
||||
dstOffset += baseSize;
|
||||
}
|
||||
|
||||
int modSize = Math.Min(rangeEnd - srcOffset, endOffset - srcOffset);
|
||||
if (modSize != 0)
|
||||
{
|
||||
modData.Slice(dstOffset, modSize).CopyTo(result.Slice(dstOffset, modSize));
|
||||
srcOffset += modSize;
|
||||
dstOffset += modSize;
|
||||
}
|
||||
}
|
||||
|
||||
int baseSizeEnd = endOffset - srcOffset;
|
||||
|
||||
if (baseSizeEnd > 0)
|
||||
{
|
||||
baseData.Slice(dstOffset, baseSizeEnd).CopyTo(result.Slice(dstOffset, baseSizeEnd));
|
||||
}
|
||||
}
|
||||
|
||||
public readonly int Count()
|
||||
{
|
||||
return _ranges?.Count ?? 0;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_ranges = null;
|
||||
}
|
||||
}
|
||||
}
|
56
src/Ryujinx.Graphics.Rdna3Vulkan/BufferState.cs
Normal file
56
src/Ryujinx.Graphics.Rdna3Vulkan/BufferState.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
struct BufferState : IDisposable
|
||||
{
|
||||
public static BufferState Null => new(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 readonly void BindTransformFeedbackBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding)
|
||||
{
|
||||
if (_buffer != null)
|
||||
{
|
||||
var buffer = _buffer.Get(cbs, _offset, _size, true).Value;
|
||||
|
||||
ulong offset = (ulong)_offset;
|
||||
ulong size = (ulong)_size;
|
||||
|
||||
gd.TransformFeedbackApi.CmdBindTransformFeedbackBuffers(cbs.CommandBuffer, binding, 1, in buffer, in offset, in size);
|
||||
}
|
||||
}
|
||||
|
||||
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
if (_buffer == from)
|
||||
{
|
||||
_buffer.DecrementReferenceCount();
|
||||
to.IncrementReferenceCount();
|
||||
|
||||
_buffer = to;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool Overlaps(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
return buffer == _buffer && offset < _offset + _size && offset + size > _offset;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
_buffer?.DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
82
src/Ryujinx.Graphics.Rdna3Vulkan/BufferUsageBitmap.cs
Normal file
82
src/Ryujinx.Graphics.Rdna3Vulkan/BufferUsageBitmap.cs
Normal file
|
@ -0,0 +1,82 @@
|
|||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
internal class BufferUsageBitmap
|
||||
{
|
||||
private readonly BitMap _bitmap;
|
||||
private readonly int _size;
|
||||
private readonly int _granularity;
|
||||
private readonly int _bits;
|
||||
private readonly int _writeBitOffset;
|
||||
|
||||
private readonly int _intsPerCb;
|
||||
private readonly int _bitsPerCb;
|
||||
|
||||
public BufferUsageBitmap(int size, int granularity)
|
||||
{
|
||||
_size = size;
|
||||
_granularity = granularity;
|
||||
|
||||
// There are two sets of bits - one for read tracking, and the other for write.
|
||||
int bits = (size + (granularity - 1)) / granularity;
|
||||
_writeBitOffset = bits;
|
||||
_bits = bits << 1;
|
||||
|
||||
_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, bool write)
|
||||
{
|
||||
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 + (write ? _writeBitOffset : 0);
|
||||
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, bool write = false)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int cbBase = cbIndex * _bitsPerCb + (write ? _writeBitOffset : 0);
|
||||
int start = cbBase + offset / _granularity;
|
||||
int end = cbBase + (offset + size - 1) / _granularity;
|
||||
|
||||
return _bitmap.IsSet(start, end);
|
||||
}
|
||||
|
||||
public bool OverlapsWith(int offset, int size, bool write)
|
||||
{
|
||||
for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
|
||||
{
|
||||
if (OverlapsWith(i, offset, size, write))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear(int cbIndex)
|
||||
{
|
||||
_bitmap.ClearInt(cbIndex * _intsPerCb, (cbIndex + 1) * _intsPerCb - 1);
|
||||
}
|
||||
}
|
||||
}
|
394
src/Ryujinx.Graphics.Rdna3Vulkan/CacheByRange.cs
Normal file
394
src/Ryujinx.Graphics.Rdna3Vulkan/CacheByRange.cs
Normal file
|
@ -0,0 +1,394 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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 readonly bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is I8ToI16CacheKey;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public readonly 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 readonly bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is AlignedVertexBufferCacheKey entry &&
|
||||
entry._stride == _stride &&
|
||||
entry._alignment == _alignment;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
_gd.PipelineInternal.DirtyVertexBuffer(_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
struct TopologyConversionCacheKey : ICacheKey
|
||||
{
|
||||
private readonly IndexBufferPattern _pattern;
|
||||
private readonly 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 readonly bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is TopologyConversionCacheKey entry &&
|
||||
entry._pattern == _pattern &&
|
||||
entry._indexSize == _indexSize;
|
||||
}
|
||||
|
||||
public void SetBuffer(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
}
|
||||
|
||||
public readonly 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();
|
||||
}
|
||||
}
|
||||
|
||||
readonly struct IndirectDataCacheKey : ICacheKey
|
||||
{
|
||||
private readonly 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 readonly bool KeyEqual(ICacheKey other)
|
||||
{
|
||||
return other is DrawCountCacheKey;
|
||||
}
|
||||
|
||||
public readonly 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 readonly 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 readonly 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)
|
||||
{
|
||||
_ranges ??= new Dictionary<ulong, List<Entry>>();
|
||||
|
||||
ulong key = PackRange(offset, size);
|
||||
|
||||
if (!_ranges.TryGetValue(key, out List<Entry> 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();
|
||||
}
|
||||
}
|
||||
}
|
370
src/Ryujinx.Graphics.Rdna3Vulkan/CommandBufferPool.cs
Normal file
370
src/Ryujinx.Graphics.Rdna3Vulkan/CommandBufferPool.cs
Normal file
|
@ -0,0 +1,370 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Semaphore = Silk.NET.Vulkan.Semaphore;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class CommandBufferPool : IDisposable
|
||||
{
|
||||
public const int MaxCommandBuffers = 16;
|
||||
|
||||
private readonly int _totalCommandBuffers;
|
||||
private readonly int _totalCommandBuffersMask;
|
||||
|
||||
private readonly Vk _api;
|
||||
private readonly Device _device;
|
||||
private readonly Queue _queue;
|
||||
private readonly Lock _queueLock;
|
||||
private readonly bool _concurrentFenceWaitUnsupported;
|
||||
private readonly CommandPool _pool;
|
||||
private readonly Thread _owner;
|
||||
|
||||
public bool OwnedByCurrentThread => _owner == Thread.CurrentThread;
|
||||
|
||||
private struct ReservedCommandBuffer
|
||||
{
|
||||
public bool InUse;
|
||||
public bool InConsumption;
|
||||
public int SubmissionCount;
|
||||
public CommandBuffer CommandBuffer;
|
||||
public FenceHolder Fence;
|
||||
|
||||
public List<IAuto> Dependants;
|
||||
public List<MultiFenceHolder> Waitables;
|
||||
|
||||
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, in allocateInfo, out CommandBuffer);
|
||||
|
||||
Dependants = new List<IAuto>();
|
||||
Waitables = new List<MultiFenceHolder>();
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
Lock queueLock,
|
||||
uint queueFamilyIndex,
|
||||
bool concurrentFenceWaitUnsupported,
|
||||
bool isLight = false)
|
||||
{
|
||||
_api = api;
|
||||
_device = device;
|
||||
_queue = queue;
|
||||
_queueLock = queueLock;
|
||||
_concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
|
||||
_owner = Thread.CurrentThread;
|
||||
|
||||
var commandPoolCreateInfo = new CommandPoolCreateInfo
|
||||
{
|
||||
SType = StructureType.CommandPoolCreateInfo,
|
||||
QueueFamilyIndex = queueFamilyIndex,
|
||||
Flags = CommandPoolCreateFlags.TransientBit |
|
||||
CommandPoolCreateFlags.ResetCommandBufferBit,
|
||||
};
|
||||
|
||||
api.CreateCommandPool(device, in 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 AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[cbIndex];
|
||||
if (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 &&
|
||||
waitable.HasFence(i) &&
|
||||
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;
|
||||
}
|
||||
|
||||
public int GetSubmissionCount(int cbIndex)
|
||||
{
|
||||
return _commandBuffers[cbIndex].SubmissionCount;
|
||||
}
|
||||
|
||||
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, in 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;
|
||||
entry.SubmissionCount++;
|
||||
_inUseCount--;
|
||||
|
||||
var commandBuffer = entry.CommandBuffer;
|
||||
|
||||
_api.EndCommandBuffer(commandBuffer).ThrowOnError();
|
||||
|
||||
fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
|
||||
{
|
||||
fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask)
|
||||
{
|
||||
SubmitInfo sInfo = new()
|
||||
{
|
||||
SType = StructureType.SubmitInfo,
|
||||
WaitSemaphoreCount = !waitSemaphores.IsEmpty ? (uint)waitSemaphores.Length : 0,
|
||||
PWaitSemaphores = pWaitSemaphores,
|
||||
PWaitDstStageMask = pWaitDstStageMask,
|
||||
CommandBufferCount = 1,
|
||||
PCommandBuffers = &commandBuffer,
|
||||
SignalSemaphoreCount = !signalSemaphores.IsEmpty ? (uint)signalSemaphores.Length : 0,
|
||||
PSignalSemaphores = pSignalSemaphores,
|
||||
};
|
||||
|
||||
lock (_queueLock)
|
||||
{
|
||||
_api.QueueSubmit(_queue, 1, in 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);
|
||||
waitable.RemoveBufferUses(cbIndex);
|
||||
}
|
||||
|
||||
entry.Dependants.Clear();
|
||||
entry.Waitables.Clear();
|
||||
entry.Fence?.Dispose();
|
||||
|
||||
if (refreshFence)
|
||||
{
|
||||
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Fence = null;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _totalCommandBuffers; i++)
|
||||
{
|
||||
WaitAndDecrementRef(i, refreshFence: false);
|
||||
}
|
||||
|
||||
_api.DestroyCommandPool(_device, _pool, null);
|
||||
}
|
||||
}
|
||||
}
|
39
src/Ryujinx.Graphics.Rdna3Vulkan/CommandBufferScoped.cs
Normal file
39
src/Ryujinx.Graphics.Rdna3Vulkan/CommandBufferScoped.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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 FenceHolder GetFence()
|
||||
{
|
||||
return _pool.GetFence(CommandBufferIndex);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pool?.Return(this);
|
||||
}
|
||||
}
|
||||
}
|
23
src/Ryujinx.Graphics.Rdna3Vulkan/Constants.cs
Normal file
23
src/Ryujinx.Graphics.Rdna3Vulkan/Constants.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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;
|
||||
public const int MaxPushDescriptorBinding = 64;
|
||||
|
||||
public const ulong SparseBufferAlignment = 0x10000;
|
||||
}
|
||||
}
|
222
src/Ryujinx.Graphics.Rdna3Vulkan/DescriptorSetCollection.cs
Normal file
222
src/Ryujinx.Graphics.Rdna3Vulkan/DescriptorSetCollection.cs
Normal file
|
@ -0,0 +1,222 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
struct DescriptorSetCollection : IDisposable
|
||||
{
|
||||
private DescriptorSetManager.DescriptorPoolHolder _holder;
|
||||
private readonly DescriptorSet[] _descriptorSets;
|
||||
public readonly int SetsCount => _descriptorSets.Length;
|
||||
|
||||
public DescriptorSetCollection(DescriptorSetManager.DescriptorPoolHolder holder, DescriptorSet[] descriptorSets)
|
||||
{
|
||||
_holder = holder;
|
||||
_descriptorSets = descriptorSets;
|
||||
}
|
||||
|
||||
public void InitializeBuffers(int setIndex, int baseBinding, int count, DescriptorType type, VkBuffer dummyBuffer)
|
||||
{
|
||||
Span<DescriptorBufferInfo> infos = stackalloc DescriptorBufferInfo[count];
|
||||
|
||||
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, in 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, in 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, in 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, in 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, in 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, in 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, in writeDescriptorSet, 0, null);
|
||||
}
|
||||
|
||||
i += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly DescriptorSet[] GetSets()
|
||||
{
|
||||
return _descriptorSets;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_holder?.FreeDescriptorSets(this);
|
||||
_holder = null;
|
||||
}
|
||||
}
|
||||
}
|
231
src/Ryujinx.Graphics.Rdna3Vulkan/DescriptorSetManager.cs
Normal file
231
src/Ryujinx.Graphics.Rdna3Vulkan/DescriptorSetManager.cs
Normal file
|
@ -0,0 +1,231 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class DescriptorSetManager : IDisposable
|
||||
{
|
||||
public const uint MaxSets = 8;
|
||||
|
||||
public class DescriptorPoolHolder : IDisposable
|
||||
{
|
||||
public Vk Api { get; }
|
||||
public Device Device { get; }
|
||||
|
||||
private readonly DescriptorPool _pool;
|
||||
private int _freeDescriptors;
|
||||
private int _totalSets;
|
||||
private int _setsInUse;
|
||||
private bool _done;
|
||||
|
||||
public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan<DescriptorPoolSize> poolSizes, bool updateAfterBind)
|
||||
{
|
||||
Api = api;
|
||||
Device = device;
|
||||
|
||||
foreach (var poolSize in poolSizes)
|
||||
{
|
||||
_freeDescriptors += (int)poolSize.DescriptorCount;
|
||||
}
|
||||
|
||||
fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
|
||||
{
|
||||
var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorPoolCreateInfo,
|
||||
Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None,
|
||||
MaxSets = MaxSets,
|
||||
PoolSizeCount = (uint)poolSizes.Length,
|
||||
PPoolSizes = pPoolsSize,
|
||||
};
|
||||
|
||||
Api.CreateDescriptorPool(device, in descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors)
|
||||
{
|
||||
TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc);
|
||||
return dsc;
|
||||
}
|
||||
|
||||
public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors, out DescriptorSetCollection dsc)
|
||||
{
|
||||
return TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: true, out dsc);
|
||||
}
|
||||
|
||||
private unsafe bool TryAllocateDescriptorSets(
|
||||
ReadOnlySpan<DescriptorSetLayout> layouts,
|
||||
int consumedDescriptors,
|
||||
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)MaxSets;
|
||||
_done = true;
|
||||
DestroyIfDone();
|
||||
dsc = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
result.ThrowOnError();
|
||||
}
|
||||
}
|
||||
|
||||
_freeDescriptors -= consumedDescriptors;
|
||||
_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 setsCount, int descriptorsCount)
|
||||
{
|
||||
// Try to determine if an allocation with the given parameters will succeed.
|
||||
// An allocation may fail if the sets count or descriptors count exceeds the available counts
|
||||
// of the pool.
|
||||
// Not getting that right is not fatal, it will just create a new pool and try again,
|
||||
// but it is less efficient.
|
||||
|
||||
if (_totalSets + setsCount <= MaxSets && _freeDescriptors >= descriptorsCount)
|
||||
{
|
||||
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()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Device _device;
|
||||
private readonly DescriptorPoolHolder[] _currentPools;
|
||||
|
||||
public DescriptorSetManager(Device device, int poolCount)
|
||||
{
|
||||
_device = device;
|
||||
_currentPools = new DescriptorPoolHolder[poolCount];
|
||||
}
|
||||
|
||||
public Auto<DescriptorSetCollection> AllocateDescriptorSet(
|
||||
Vk api,
|
||||
DescriptorSetLayout layout,
|
||||
ReadOnlySpan<DescriptorPoolSize> poolSizes,
|
||||
int poolIndex,
|
||||
int consumedDescriptors,
|
||||
bool updateAfterBind)
|
||||
{
|
||||
Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
|
||||
layouts[0] = layout;
|
||||
return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind);
|
||||
}
|
||||
|
||||
public Auto<DescriptorSetCollection> AllocateDescriptorSets(
|
||||
Vk api,
|
||||
ReadOnlySpan<DescriptorSetLayout> layouts,
|
||||
ReadOnlySpan<DescriptorPoolSize> poolSizes,
|
||||
int poolIndex,
|
||||
int consumedDescriptors,
|
||||
bool updateAfterBind)
|
||||
{
|
||||
// If we fail the first time, just create a new pool and try again.
|
||||
|
||||
var pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
|
||||
if (!pool.TryAllocateDescriptorSets(layouts, consumedDescriptors, out var dsc))
|
||||
{
|
||||
pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
|
||||
dsc = pool.AllocateDescriptorSets(layouts, consumedDescriptors);
|
||||
}
|
||||
|
||||
return new Auto<DescriptorSetCollection>(dsc);
|
||||
}
|
||||
|
||||
private DescriptorPoolHolder GetPool(
|
||||
Vk api,
|
||||
ReadOnlySpan<DescriptorPoolSize> poolSizes,
|
||||
int poolIndex,
|
||||
int setsCount,
|
||||
int descriptorsCount,
|
||||
bool updateAfterBind)
|
||||
{
|
||||
ref DescriptorPoolHolder currentPool = ref _currentPools[poolIndex];
|
||||
|
||||
if (currentPool == null || !currentPool.CanFit(setsCount, descriptorsCount))
|
||||
{
|
||||
currentPool = new DescriptorPoolHolder(api, _device, poolSizes, updateAfterBind);
|
||||
}
|
||||
|
||||
return currentPool;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
for (int index = 0; index < _currentPools.Length; index++)
|
||||
{
|
||||
_currentPools[index]?.Dispose();
|
||||
_currentPools[index] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
210
src/Ryujinx.Graphics.Rdna3Vulkan/DescriptorSetTemplate.cs
Normal file
210
src/Ryujinx.Graphics.Rdna3Vulkan/DescriptorSetTemplate.cs
Normal file
|
@ -0,0 +1,210 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class DescriptorSetTemplate : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Renderdoc seems to crash when doing a templated uniform update with count > 1 on a push descriptor.
|
||||
/// When this is true, consecutive buffers are always updated individually.
|
||||
/// </summary>
|
||||
private const bool RenderdocPushCountBug = true;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
|
||||
public readonly DescriptorUpdateTemplate Template;
|
||||
public readonly int Size;
|
||||
|
||||
public unsafe DescriptorSetTemplate(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ResourceBindingSegment[] segments,
|
||||
PipelineLayoutCacheEntry plce,
|
||||
PipelineBindPoint pbp,
|
||||
int setIndex)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
|
||||
// Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order.
|
||||
|
||||
DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segments.Length];
|
||||
nuint structureOffset = 0;
|
||||
|
||||
for (int seg = 0; seg < segments.Length; seg++)
|
||||
{
|
||||
ResourceBindingSegment segment = segments[seg];
|
||||
|
||||
int binding = segment.Binding;
|
||||
int count = segment.Count;
|
||||
|
||||
if (IsBufferType(segment.Type))
|
||||
{
|
||||
entries[seg] = new DescriptorUpdateTemplateEntry()
|
||||
{
|
||||
DescriptorType = segment.Type.Convert(),
|
||||
DstBinding = (uint)binding,
|
||||
DescriptorCount = (uint)count,
|
||||
Offset = structureOffset,
|
||||
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
|
||||
};
|
||||
|
||||
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
|
||||
}
|
||||
else if (IsBufferTextureType(segment.Type))
|
||||
{
|
||||
entries[seg] = new DescriptorUpdateTemplateEntry()
|
||||
{
|
||||
DescriptorType = segment.Type.Convert(),
|
||||
DstBinding = (uint)binding,
|
||||
DescriptorCount = (uint)count,
|
||||
Offset = structureOffset,
|
||||
Stride = (nuint)Unsafe.SizeOf<BufferView>()
|
||||
};
|
||||
|
||||
structureOffset += (nuint)(Unsafe.SizeOf<BufferView>() * count);
|
||||
}
|
||||
else
|
||||
{
|
||||
entries[seg] = new DescriptorUpdateTemplateEntry()
|
||||
{
|
||||
DescriptorType = segment.Type.Convert(),
|
||||
DstBinding = (uint)binding,
|
||||
DescriptorCount = (uint)count,
|
||||
Offset = structureOffset,
|
||||
Stride = (nuint)Unsafe.SizeOf<DescriptorImageInfo>()
|
||||
};
|
||||
|
||||
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorImageInfo>() * count);
|
||||
}
|
||||
}
|
||||
|
||||
Size = (int)structureOffset;
|
||||
|
||||
var info = new DescriptorUpdateTemplateCreateInfo()
|
||||
{
|
||||
SType = StructureType.DescriptorUpdateTemplateCreateInfo,
|
||||
DescriptorUpdateEntryCount = (uint)segments.Length,
|
||||
PDescriptorUpdateEntries = entries,
|
||||
|
||||
TemplateType = DescriptorUpdateTemplateType.DescriptorSet,
|
||||
DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex],
|
||||
PipelineBindPoint = pbp,
|
||||
PipelineLayout = plce.PipelineLayout,
|
||||
Set = (uint)setIndex,
|
||||
};
|
||||
|
||||
DescriptorUpdateTemplate result;
|
||||
gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError();
|
||||
|
||||
Template = result;
|
||||
}
|
||||
|
||||
public unsafe DescriptorSetTemplate(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ResourceDescriptorCollection descriptors,
|
||||
long updateMask,
|
||||
PipelineLayoutCacheEntry plce,
|
||||
PipelineBindPoint pbp,
|
||||
int setIndex)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
|
||||
// Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order.
|
||||
int segmentCount = BitOperations.PopCount((ulong)updateMask);
|
||||
|
||||
DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segmentCount];
|
||||
int entry = 0;
|
||||
nuint structureOffset = 0;
|
||||
|
||||
void AddBinding(int binding, int count)
|
||||
{
|
||||
entries[entry++] = new DescriptorUpdateTemplateEntry()
|
||||
{
|
||||
DescriptorType = DescriptorType.UniformBuffer,
|
||||
DstBinding = (uint)binding,
|
||||
DescriptorCount = (uint)count,
|
||||
Offset = structureOffset,
|
||||
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
|
||||
};
|
||||
|
||||
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
|
||||
}
|
||||
|
||||
int startBinding = 0;
|
||||
int bindingCount = 0;
|
||||
|
||||
foreach (ResourceDescriptor descriptor in descriptors.Descriptors)
|
||||
{
|
||||
for (int i = 0; i < descriptor.Count; i++)
|
||||
{
|
||||
int binding = descriptor.Binding + i;
|
||||
|
||||
if ((updateMask & (1L << binding)) != 0)
|
||||
{
|
||||
if (bindingCount > 0 && (RenderdocPushCountBug || startBinding + bindingCount != binding))
|
||||
{
|
||||
AddBinding(startBinding, bindingCount);
|
||||
|
||||
bindingCount = 0;
|
||||
}
|
||||
|
||||
if (bindingCount == 0)
|
||||
{
|
||||
startBinding = binding;
|
||||
}
|
||||
|
||||
bindingCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bindingCount > 0)
|
||||
{
|
||||
AddBinding(startBinding, bindingCount);
|
||||
}
|
||||
|
||||
Size = (int)structureOffset;
|
||||
|
||||
var info = new DescriptorUpdateTemplateCreateInfo()
|
||||
{
|
||||
SType = StructureType.DescriptorUpdateTemplateCreateInfo,
|
||||
DescriptorUpdateEntryCount = (uint)entry,
|
||||
PDescriptorUpdateEntries = entries,
|
||||
|
||||
TemplateType = DescriptorUpdateTemplateType.PushDescriptorsKhr,
|
||||
DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex],
|
||||
PipelineBindPoint = pbp,
|
||||
PipelineLayout = plce.PipelineLayout,
|
||||
Set = (uint)setIndex,
|
||||
};
|
||||
|
||||
DescriptorUpdateTemplate result;
|
||||
gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError();
|
||||
|
||||
Template = result;
|
||||
}
|
||||
|
||||
private static bool IsBufferType(ResourceType type)
|
||||
{
|
||||
return type == ResourceType.UniformBuffer || type == ResourceType.StorageBuffer;
|
||||
}
|
||||
|
||||
private static bool IsBufferTextureType(ResourceType type)
|
||||
{
|
||||
return type == ResourceType.BufferTexture || type == ResourceType.BufferImage;
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
using Ryujinx.Common;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
ref struct DescriptorSetTemplateWriter
|
||||
{
|
||||
private Span<byte> _data;
|
||||
|
||||
public DescriptorSetTemplateWriter(Span<byte> data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public void Push<T>(ReadOnlySpan<T> values) where T : unmanaged
|
||||
{
|
||||
Span<T> target = MemoryMarshal.Cast<byte, T>(_data);
|
||||
|
||||
values.CopyTo(target);
|
||||
|
||||
_data = _data[(Unsafe.SizeOf<T>() * values.Length)..];
|
||||
}
|
||||
}
|
||||
|
||||
unsafe class DescriptorSetTemplateUpdater : IDisposable
|
||||
{
|
||||
private const int SizeGranularity = 512;
|
||||
|
||||
private DescriptorSetTemplate _activeTemplate;
|
||||
private NativeArray<byte> _data;
|
||||
|
||||
private void EnsureSize(int size)
|
||||
{
|
||||
if (_data == null || _data.Length < size)
|
||||
{
|
||||
_data?.Dispose();
|
||||
|
||||
int dataSize = BitUtils.AlignUp(size, SizeGranularity);
|
||||
_data = new NativeArray<byte>(dataSize);
|
||||
}
|
||||
}
|
||||
|
||||
public DescriptorSetTemplateWriter Begin(DescriptorSetTemplate template)
|
||||
{
|
||||
_activeTemplate = template;
|
||||
|
||||
EnsureSize(template.Size);
|
||||
|
||||
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, template.Size));
|
||||
}
|
||||
|
||||
public DescriptorSetTemplateWriter Begin(int maxSize)
|
||||
{
|
||||
EnsureSize(maxSize);
|
||||
|
||||
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, maxSize));
|
||||
}
|
||||
|
||||
public void Commit(VulkanRenderer gd, Device device, DescriptorSet set)
|
||||
{
|
||||
gd.Api.UpdateDescriptorSetWithTemplate(device, set, _activeTemplate.Template, _data.Pointer);
|
||||
}
|
||||
|
||||
public void CommitPushDescriptor(VulkanRenderer gd, CommandBufferScoped cbs, DescriptorSetTemplate template, PipelineLayout layout)
|
||||
{
|
||||
gd.PushDescriptorApi.CmdPushDescriptorSetWithTemplate(cbs.CommandBuffer, template.Template, layout, 0, _data.Pointer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_data?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
1190
src/Ryujinx.Graphics.Rdna3Vulkan/DescriptorSetUpdater.cs
Normal file
1190
src/Ryujinx.Graphics.Rdna3Vulkan/DescriptorSetUpdater.cs
Normal file
File diff suppressed because it is too large
Load diff
26
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableBuffer.cs
Normal file
26
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableBuffer.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Buffer = Silk.NET.Vulkan.Buffer;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
readonly struct DisposableBuffer : IDisposable
|
||||
{
|
||||
private readonly Vk _api;
|
||||
private readonly Device _device;
|
||||
|
||||
public Buffer Value { get; }
|
||||
|
||||
public DisposableBuffer(Vk api, Device device, Buffer buffer)
|
||||
{
|
||||
_api = api;
|
||||
_device = device;
|
||||
Value = buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_api.DestroyBuffer(_device, Value, Span<AllocationCallbacks>.Empty);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableBufferView.cs
Normal file
25
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableBufferView.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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.Rdna3Vulkan/DisposableFramebuffer.cs
Normal file
25
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableFramebuffer.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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.Rdna3Vulkan/DisposableImage.cs
Normal file
25
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableImage.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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.Rdna3Vulkan/DisposableImageView.cs
Normal file
25
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableImageView.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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.Rdna3Vulkan/DisposableMemory.cs
Normal file
24
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableMemory.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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.Rdna3Vulkan/DisposablePipeline.cs
Normal file
25
src/Ryujinx.Graphics.Rdna3Vulkan/DisposablePipeline.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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.Rdna3Vulkan/DisposableRenderPass.cs
Normal file
25
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableRenderPass.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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.Rdna3Vulkan/DisposableSampler.cs
Normal file
25
src/Ryujinx.Graphics.Rdna3Vulkan/DisposableSampler.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
101
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/AreaScalingFilter.cs
Normal file
101
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/AreaScalingFilter.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
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;
|
||||
using Format = Silk.NET.Vulkan.Format;
|
||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.Effects
|
||||
{
|
||||
internal class AreaScalingFilter : IScalingFilter
|
||||
{
|
||||
private readonly VulkanRenderer _renderer;
|
||||
private PipelineHelperShader _pipeline;
|
||||
private ISampler _sampler;
|
||||
private ShaderCollection _scalingProgram;
|
||||
private Device _device;
|
||||
|
||||
public float Level { get; set; }
|
||||
|
||||
public AreaScalingFilter(VulkanRenderer renderer, Device device)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pipeline.Dispose();
|
||||
_scalingProgram.Dispose();
|
||||
_sampler.Dispose();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_pipeline = new PipelineHelperShader(_renderer, _device);
|
||||
|
||||
_pipeline.Initialize();
|
||||
|
||||
var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/AreaScaling.spv");
|
||||
|
||||
var scalingResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
}, scalingResourceLayout);
|
||||
}
|
||||
|
||||
public void Run(
|
||||
TextureView view,
|
||||
CommandBufferScoped cbs,
|
||||
Auto<DisposableImageView> destinationTexture,
|
||||
Format format,
|
||||
int width,
|
||||
int height,
|
||||
Extent2D source,
|
||||
Extent2D destination)
|
||||
{
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
_pipeline.SetProgram(_scalingProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
|
||||
|
||||
ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
|
||||
{
|
||||
source.X1,
|
||||
source.X2,
|
||||
source.Y1,
|
||||
source.Y2,
|
||||
destination.X1,
|
||||
destination.X2,
|
||||
destination.Y1,
|
||||
destination.Y2,
|
||||
};
|
||||
|
||||
int rangeSize = dimensionsBuffer.Length * sizeof(float);
|
||||
using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
|
||||
buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
|
||||
|
||||
int threadGroupWorkRegionDim = 16;
|
||||
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
|
||||
_pipeline.SetImage(0, destinationTexture);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
}
|
||||
}
|
||||
}
|
172
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/FsrScalingFilter.cs
Normal file
172
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/FsrScalingFilter.cs
Normal file
|
@ -0,0 +1,172 @@
|
|||
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;
|
||||
using Format = Silk.NET.Vulkan.Format;
|
||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.Effects
|
||||
{
|
||||
internal 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.Rdna3Vulkan/Effects/Shaders/FsrScaling.spv");
|
||||
var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/FsrSharpening.spv");
|
||||
|
||||
var scalingResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
var sharpeningResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
}, scalingResourceLayout);
|
||||
|
||||
_sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(sharpeningShader, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
}, sharpeningResourceLayout);
|
||||
}
|
||||
|
||||
public void Run(
|
||||
TextureView view,
|
||||
CommandBufferScoped cbs,
|
||||
Auto<DisposableImageView> destinationTexture,
|
||||
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 info = new TextureCreateInfo(
|
||||
width,
|
||||
height,
|
||||
originalInfo.Depth,
|
||||
originalInfo.Levels,
|
||||
originalInfo.Samples,
|
||||
originalInfo.BlockWidth,
|
||||
originalInfo.BlockHeight,
|
||||
originalInfo.BytesPerPixel,
|
||||
originalInfo.Format,
|
||||
originalInfo.DepthStencilMode,
|
||||
originalInfo.Target,
|
||||
originalInfo.SwizzleR,
|
||||
originalInfo.SwizzleG,
|
||||
originalInfo.SwizzleB,
|
||||
originalInfo.SwizzleA);
|
||||
_intermediaryTexture?.Dispose();
|
||||
_intermediaryTexture = _renderer.CreateTexture(info) 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);
|
||||
using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
|
||||
buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
|
||||
|
||||
ReadOnlySpan<float> sharpeningBufferData = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f) };
|
||||
using var sharpeningBuffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, sizeof(float));
|
||||
sharpeningBuffer.Holder.SetDataUnchecked(sharpeningBuffer.Offset, sharpeningBufferData);
|
||||
|
||||
int threadGroupWorkRegionDim = 16;
|
||||
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
// Sharpening pass
|
||||
_pipeline.SetProgram(_sharpeningProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningBuffer.Range) });
|
||||
_pipeline.SetImage(0, destinationTexture);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.Effects
|
||||
{
|
||||
internal class FxaaPostProcessingEffect : IPostProcessingEffect
|
||||
{
|
||||
private readonly VulkanRenderer _renderer;
|
||||
private ISampler _samplerLinear;
|
||||
private ShaderCollection _shaderProgram;
|
||||
|
||||
private readonly 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.Rdna3Vulkan/Effects/Shaders/Fxaa.spv");
|
||||
|
||||
var resourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(shader, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
}, resourceLayout);
|
||||
}
|
||||
|
||||
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
|
||||
{
|
||||
if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height)
|
||||
{
|
||||
_texture?.Dispose();
|
||||
_texture = _renderer.CreateTexture(view.Info) 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);
|
||||
using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
|
||||
|
||||
buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer);
|
||||
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
|
||||
|
||||
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
||||
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
||||
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, _texture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
|
||||
return _texture;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.Effects
|
||||
{
|
||||
internal interface IPostProcessingEffect : IDisposable
|
||||
{
|
||||
const int LocalGroupSize = 64;
|
||||
TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height);
|
||||
}
|
||||
}
|
20
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/IScalingFilter.cs
Normal file
20
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/IScalingFilter.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// Scaling
|
||||
|
||||
#version 430 core
|
||||
layout (local_size_x = 16, local_size_y = 16) in;
|
||||
layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput;
|
||||
layout( binding = 1, set = 2) uniform sampler2D Source;
|
||||
layout( binding = 2 ) uniform dimensions{
|
||||
float srcX0;
|
||||
float srcX1;
|
||||
float srcY0;
|
||||
float srcY1;
|
||||
float dstX0;
|
||||
float dstX1;
|
||||
float dstY0;
|
||||
float dstY1;
|
||||
};
|
||||
|
||||
/***** Area Sampling *****/
|
||||
|
||||
// By Sam Belliveau and Filippo Tarpini. Public Domain license.
|
||||
// Effectively a more accurate sharp bilinear filter when upscaling,
|
||||
// that also works as a mathematically perfect downscale filter.
|
||||
// https://entropymine.com/imageworsener/pixelmixing/
|
||||
// https://github.com/obsproject/obs-studio/pull/1715
|
||||
// https://legacy.imagemagick.org/Usage/filter/
|
||||
vec4 AreaSampling(vec2 xy)
|
||||
{
|
||||
// Determine the sizes of the source and target images.
|
||||
vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
|
||||
vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
|
||||
vec2 inverted_target_size = vec2(1.0) / target_size;
|
||||
|
||||
// Compute the top-left and bottom-right corners of the target pixel box.
|
||||
vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
|
||||
vec2 t_end = t_beg + vec2(1.0, 1.0);
|
||||
|
||||
// Convert the target pixel box to source pixel box.
|
||||
vec2 beg = t_beg * inverted_target_size * source_size;
|
||||
vec2 end = t_end * inverted_target_size * source_size;
|
||||
|
||||
// Compute the top-left and bottom-right corners of the pixel box.
|
||||
ivec2 f_beg = ivec2(beg);
|
||||
ivec2 f_end = ivec2(end);
|
||||
|
||||
// Compute how much of the start and end pixels are covered horizontally & vertically.
|
||||
float area_w = 1.0 - fract(beg.x);
|
||||
float area_n = 1.0 - fract(beg.y);
|
||||
float area_e = fract(end.x);
|
||||
float area_s = fract(end.y);
|
||||
|
||||
// Compute the areas of the corner pixels in the pixel box.
|
||||
float area_nw = area_n * area_w;
|
||||
float area_ne = area_n * area_e;
|
||||
float area_sw = area_s * area_w;
|
||||
float area_se = area_s * area_e;
|
||||
|
||||
// Initialize the color accumulator.
|
||||
vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
// Accumulate corner pixels.
|
||||
avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
|
||||
avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
|
||||
avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
|
||||
avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);
|
||||
|
||||
// Determine the size of the pixel box.
|
||||
int x_range = int(f_end.x - f_beg.x - 0.5);
|
||||
int y_range = int(f_end.y - f_beg.y - 0.5);
|
||||
|
||||
// Accumulate top and bottom edge pixels.
|
||||
for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
|
||||
{
|
||||
avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
|
||||
avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
|
||||
}
|
||||
|
||||
// Accumulate left and right edge pixels and all the pixels in between.
|
||||
for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
|
||||
{
|
||||
avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
|
||||
avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);
|
||||
|
||||
for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
|
||||
{
|
||||
avg_color += texelFetch(Source, ivec2(x, y), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the area of the pixel box that was sampled.
|
||||
float area_corners = area_nw + area_ne + area_sw + area_se;
|
||||
float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
|
||||
float area_center = float(x_range) * float(y_range);
|
||||
|
||||
// Return the normalized average color.
|
||||
return avg_color / (area_corners + area_edges + area_center);
|
||||
}
|
||||
|
||||
float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
|
||||
vec2 s = step(bLeft, v) - step(tRight, v);
|
||||
return s.x * s.y;
|
||||
}
|
||||
|
||||
vec2 translateDest(vec2 pos) {
|
||||
vec2 translatedPos = vec2(pos.x, pos.y);
|
||||
translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
|
||||
translatedPos.y = dstY0 < dstY1 ? dstY1 + dstY0 - translatedPos.y - 1 : translatedPos.y;
|
||||
return translatedPos;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
|
||||
vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
|
||||
ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
|
||||
if (insideBox(loc, bLeft, tRight) == 0) {
|
||||
imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
|
||||
return;
|
||||
}
|
||||
|
||||
vec4 outColor = AreaSampling(loc);
|
||||
imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
|
||||
}
|
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/AreaScaling.spv
Normal file
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/AreaScaling.spv
Normal file
Binary file not shown.
3945
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/FsrScaling.glsl
Normal file
3945
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/FsrScaling.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/FsrScaling.spv
Normal file
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/FsrScaling.spv
Normal file
Binary file not shown.
3904
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/FsrSharpening.glsl
Normal file
3904
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/FsrSharpening.glsl
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
1177
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/Fxaa.glsl
Normal file
1177
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/Fxaa.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/Fxaa.spv
Normal file
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/Fxaa.spv
Normal file
Binary file not shown.
1404
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaBlend.glsl
Normal file
1404
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaBlend.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaBlend.spv
Normal file
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaBlend.spv
Normal file
Binary file not shown.
1402
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaEdge.glsl
Normal file
1402
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaEdge.glsl
Normal file
File diff suppressed because it is too large
Load diff
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaEdge.spv
Normal file
BIN
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaEdge.spv
Normal file
Binary file not shown.
1403
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaNeighbour.glsl
Normal file
1403
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaNeighbour.glsl
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
15
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/SmaaConstants.cs
Normal file
15
src/Ryujinx.Graphics.Rdna3Vulkan/Effects/SmaaConstants.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,266 @@
|
|||
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;
|
||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.Effects
|
||||
{
|
||||
internal 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 void RecreateShaders(int width, int height)
|
||||
{
|
||||
_recreatePipelines = false;
|
||||
|
||||
DeletePipelines();
|
||||
_pipeline = new PipelineHelperShader(_renderer, _device);
|
||||
|
||||
_pipeline.Initialize();
|
||||
|
||||
var edgeShader = EmbeddedResources.Read("Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaEdge.spv");
|
||||
var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaBlend.spv");
|
||||
var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Rdna3Vulkan/Effects/Shaders/SmaaNeighbour.spv");
|
||||
|
||||
var edgeResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
var blendResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
var neighbourResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(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, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
}, edgeResourceLayout, new[] { specInfo });
|
||||
|
||||
_blendProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(blendShader, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
}, blendResourceLayout, new[] { specInfo });
|
||||
|
||||
_neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(neighbourShader, ShaderStage.Compute, TargetLanguage.Spirv),
|
||||
}, neighbourResourceLayout, 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.ReadFileToRentedMemory("Ryujinx.Graphics.Rdna3Vulkan/Effects/Textures/SmaaAreaTexture.bin");
|
||||
var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Rdna3Vulkan/Effects/Textures/SmaaSearchTexture.bin");
|
||||
|
||||
_areaTexture = _renderer.CreateTexture(areaInfo) as TextureView;
|
||||
_searchTexture = _renderer.CreateTexture(searchInfo) 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();
|
||||
|
||||
_outputTexture = _renderer.CreateTexture(view.Info) as TextureView;
|
||||
_edgeOutputTexture = _renderer.CreateTexture(view.Info) as TextureView;
|
||||
_blendOutputTexture = _renderer.CreateTexture(view.Info) 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);
|
||||
using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
|
||||
|
||||
buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
|
||||
_pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
|
||||
_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(ShaderStage.Compute, 0, _blendOutputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
|
||||
_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(ShaderStage.Compute, 0, _outputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
|
||||
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, (uint)texture.Width, (uint)texture.Height);
|
||||
_pipeline.SetRenderTargetColorMasks(colorMasks);
|
||||
_pipeline.SetScissors(scissors);
|
||||
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
452
src/Ryujinx.Graphics.Rdna3Vulkan/EnumConversion.cs
Normal file
452
src/Ryujinx.Graphics.Rdna3Vulkan/EnumConversion.cs
Normal file
|
@ -0,0 +1,452 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using BlendFactor = Silk.NET.Vulkan.BlendFactor;
|
||||
using BlendOp = Silk.NET.Vulkan.BlendOp;
|
||||
using CompareOp = Silk.NET.Vulkan.CompareOp;
|
||||
using Format = Ryujinx.Graphics.GAL.Format;
|
||||
using FrontFace = Silk.NET.Vulkan.FrontFace;
|
||||
using IndexType = Silk.NET.Vulkan.IndexType;
|
||||
using PrimitiveTopology = Silk.NET.Vulkan.PrimitiveTopology;
|
||||
using StencilOp = Silk.NET.Vulkan.StencilOp;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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 ShaderStageFlags Convert(this ResourceStages stages)
|
||||
{
|
||||
ShaderStageFlags stageFlags = stages.HasFlag(ResourceStages.Compute)
|
||||
? ShaderStageFlags.ComputeBit
|
||||
: ShaderStageFlags.None;
|
||||
|
||||
if (stages.HasFlag(ResourceStages.Vertex))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.VertexBit;
|
||||
}
|
||||
|
||||
if (stages.HasFlag(ResourceStages.TessellationControl))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.TessellationControlBit;
|
||||
}
|
||||
|
||||
if (stages.HasFlag(ResourceStages.TessellationEvaluation))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.TessellationEvaluationBit;
|
||||
}
|
||||
|
||||
if (stages.HasFlag(ResourceStages.Geometry))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.GeometryBit;
|
||||
}
|
||||
|
||||
if (stages.HasFlag(ResourceStages.Fragment))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.FragmentBit;
|
||||
}
|
||||
|
||||
return stageFlags;
|
||||
}
|
||||
|
||||
public static DescriptorType Convert(this ResourceType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ResourceType.UniformBuffer => DescriptorType.UniformBuffer,
|
||||
ResourceType.StorageBuffer => DescriptorType.StorageBuffer,
|
||||
ResourceType.Texture => DescriptorType.SampledImage,
|
||||
ResourceType.Sampler => DescriptorType.Sampler,
|
||||
ResourceType.TextureAndSampler => DescriptorType.CombinedImageSampler,
|
||||
ResourceType.Image => DescriptorType.StorageImage,
|
||||
ResourceType.BufferTexture => DescriptorType.UniformTexelBuffer,
|
||||
ResourceType.BufferImage => DescriptorType.StorageTexelBuffer,
|
||||
_ => throw new ArgumentException($"Invalid resource type \"{type}\"."),
|
||||
};
|
||||
}
|
||||
|
||||
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 BlendFactor Convert(this GAL.BlendFactor factor)
|
||||
{
|
||||
return factor switch
|
||||
{
|
||||
GAL.BlendFactor.Zero or GAL.BlendFactor.ZeroGl => BlendFactor.Zero,
|
||||
GAL.BlendFactor.One or GAL.BlendFactor.OneGl => BlendFactor.One,
|
||||
GAL.BlendFactor.SrcColor or GAL.BlendFactor.SrcColorGl => BlendFactor.SrcColor,
|
||||
GAL.BlendFactor.OneMinusSrcColor or GAL.BlendFactor.OneMinusSrcColorGl => BlendFactor.OneMinusSrcColor,
|
||||
GAL.BlendFactor.SrcAlpha or GAL.BlendFactor.SrcAlphaGl => BlendFactor.SrcAlpha,
|
||||
GAL.BlendFactor.OneMinusSrcAlpha or GAL.BlendFactor.OneMinusSrcAlphaGl => BlendFactor.OneMinusSrcAlpha,
|
||||
GAL.BlendFactor.DstAlpha or GAL.BlendFactor.DstAlphaGl => BlendFactor.DstAlpha,
|
||||
GAL.BlendFactor.OneMinusDstAlpha or GAL.BlendFactor.OneMinusDstAlphaGl => BlendFactor.OneMinusDstAlpha,
|
||||
GAL.BlendFactor.DstColor or GAL.BlendFactor.DstColorGl => BlendFactor.DstColor,
|
||||
GAL.BlendFactor.OneMinusDstColor or GAL.BlendFactor.OneMinusDstColorGl => BlendFactor.OneMinusDstColor,
|
||||
GAL.BlendFactor.SrcAlphaSaturate or GAL.BlendFactor.SrcAlphaSaturateGl => BlendFactor.SrcAlphaSaturate,
|
||||
GAL.BlendFactor.Src1Color or GAL.BlendFactor.Src1ColorGl => BlendFactor.Src1Color,
|
||||
GAL.BlendFactor.OneMinusSrc1Color or GAL.BlendFactor.OneMinusSrc1ColorGl => BlendFactor.OneMinusSrc1Color,
|
||||
GAL.BlendFactor.Src1Alpha or GAL.BlendFactor.Src1AlphaGl => BlendFactor.Src1Alpha,
|
||||
GAL.BlendFactor.OneMinusSrc1Alpha or GAL.BlendFactor.OneMinusSrc1AlphaGl => BlendFactor.OneMinusSrc1Alpha,
|
||||
GAL.BlendFactor.ConstantColor => BlendFactor.ConstantColor,
|
||||
GAL.BlendFactor.OneMinusConstantColor => BlendFactor.OneMinusConstantColor,
|
||||
GAL.BlendFactor.ConstantAlpha => BlendFactor.ConstantAlpha,
|
||||
GAL.BlendFactor.OneMinusConstantAlpha => BlendFactor.OneMinusConstantAlpha,
|
||||
_ => LogInvalidAndReturn(factor, nameof(GAL.BlendFactor), BlendFactor.Zero),
|
||||
};
|
||||
}
|
||||
|
||||
public static BlendOp Convert(this AdvancedBlendOp op)
|
||||
{
|
||||
return op switch
|
||||
{
|
||||
AdvancedBlendOp.Zero => BlendOp.ZeroExt,
|
||||
AdvancedBlendOp.Src => BlendOp.SrcExt,
|
||||
AdvancedBlendOp.Dst => BlendOp.DstExt,
|
||||
AdvancedBlendOp.SrcOver => BlendOp.SrcOverExt,
|
||||
AdvancedBlendOp.DstOver => BlendOp.DstOverExt,
|
||||
AdvancedBlendOp.SrcIn => BlendOp.SrcInExt,
|
||||
AdvancedBlendOp.DstIn => BlendOp.DstInExt,
|
||||
AdvancedBlendOp.SrcOut => BlendOp.SrcOutExt,
|
||||
AdvancedBlendOp.DstOut => BlendOp.DstOutExt,
|
||||
AdvancedBlendOp.SrcAtop => BlendOp.SrcAtopExt,
|
||||
AdvancedBlendOp.DstAtop => BlendOp.DstAtopExt,
|
||||
AdvancedBlendOp.Xor => BlendOp.XorExt,
|
||||
AdvancedBlendOp.Plus => BlendOp.PlusExt,
|
||||
AdvancedBlendOp.PlusClamped => BlendOp.PlusClampedExt,
|
||||
AdvancedBlendOp.PlusClampedAlpha => BlendOp.PlusClampedAlphaExt,
|
||||
AdvancedBlendOp.PlusDarker => BlendOp.PlusDarkerExt,
|
||||
AdvancedBlendOp.Multiply => BlendOp.MultiplyExt,
|
||||
AdvancedBlendOp.Screen => BlendOp.ScreenExt,
|
||||
AdvancedBlendOp.Overlay => BlendOp.OverlayExt,
|
||||
AdvancedBlendOp.Darken => BlendOp.DarkenExt,
|
||||
AdvancedBlendOp.Lighten => BlendOp.LightenExt,
|
||||
AdvancedBlendOp.ColorDodge => BlendOp.ColordodgeExt,
|
||||
AdvancedBlendOp.ColorBurn => BlendOp.ColorburnExt,
|
||||
AdvancedBlendOp.HardLight => BlendOp.HardlightExt,
|
||||
AdvancedBlendOp.SoftLight => BlendOp.SoftlightExt,
|
||||
AdvancedBlendOp.Difference => BlendOp.DifferenceExt,
|
||||
AdvancedBlendOp.Minus => BlendOp.MinusExt,
|
||||
AdvancedBlendOp.MinusClamped => BlendOp.MinusClampedExt,
|
||||
AdvancedBlendOp.Exclusion => BlendOp.ExclusionExt,
|
||||
AdvancedBlendOp.Contrast => BlendOp.ContrastExt,
|
||||
AdvancedBlendOp.Invert => BlendOp.InvertExt,
|
||||
AdvancedBlendOp.InvertRGB => BlendOp.InvertRgbExt,
|
||||
AdvancedBlendOp.InvertOvg => BlendOp.InvertOvgExt,
|
||||
AdvancedBlendOp.LinearDodge => BlendOp.LineardodgeExt,
|
||||
AdvancedBlendOp.LinearBurn => BlendOp.LinearburnExt,
|
||||
AdvancedBlendOp.VividLight => BlendOp.VividlightExt,
|
||||
AdvancedBlendOp.LinearLight => BlendOp.LinearlightExt,
|
||||
AdvancedBlendOp.PinLight => BlendOp.PinlightExt,
|
||||
AdvancedBlendOp.HardMix => BlendOp.HardmixExt,
|
||||
AdvancedBlendOp.Red => BlendOp.RedExt,
|
||||
AdvancedBlendOp.Green => BlendOp.GreenExt,
|
||||
AdvancedBlendOp.Blue => BlendOp.BlueExt,
|
||||
AdvancedBlendOp.HslHue => BlendOp.HslHueExt,
|
||||
AdvancedBlendOp.HslSaturation => BlendOp.HslSaturationExt,
|
||||
AdvancedBlendOp.HslColor => BlendOp.HslColorExt,
|
||||
AdvancedBlendOp.HslLuminosity => BlendOp.HslLuminosityExt,
|
||||
_ => LogInvalidAndReturn(op, nameof(AdvancedBlendOp), BlendOp.Add),
|
||||
};
|
||||
}
|
||||
|
||||
public static BlendOp Convert(this GAL.BlendOp op)
|
||||
{
|
||||
return op switch
|
||||
{
|
||||
GAL.BlendOp.Add or GAL.BlendOp.AddGl => BlendOp.Add,
|
||||
GAL.BlendOp.Subtract or GAL.BlendOp.SubtractGl => BlendOp.Subtract,
|
||||
GAL.BlendOp.ReverseSubtract or GAL.BlendOp.ReverseSubtractGl => BlendOp.ReverseSubtract,
|
||||
GAL.BlendOp.Minimum or GAL.BlendOp.MinimumGl => BlendOp.Min,
|
||||
GAL.BlendOp.Maximum or GAL.BlendOp.MaximumGl => BlendOp.Max,
|
||||
_ => LogInvalidAndReturn(op, nameof(GAL.BlendOp), BlendOp.Add),
|
||||
};
|
||||
}
|
||||
|
||||
public static BlendOverlapEXT Convert(this AdvancedBlendOverlap overlap)
|
||||
{
|
||||
return overlap switch
|
||||
{
|
||||
AdvancedBlendOverlap.Uncorrelated => BlendOverlapEXT.UncorrelatedExt,
|
||||
AdvancedBlendOverlap.Disjoint => BlendOverlapEXT.DisjointExt,
|
||||
AdvancedBlendOverlap.Conjoint => BlendOverlapEXT.ConjointExt,
|
||||
_ => LogInvalidAndReturn(overlap, nameof(AdvancedBlendOverlap), BlendOverlapEXT.UncorrelatedExt),
|
||||
};
|
||||
}
|
||||
|
||||
public static CompareOp Convert(this GAL.CompareOp op)
|
||||
{
|
||||
return op switch
|
||||
{
|
||||
GAL.CompareOp.Never or GAL.CompareOp.NeverGl => CompareOp.Never,
|
||||
GAL.CompareOp.Less or GAL.CompareOp.LessGl => CompareOp.Less,
|
||||
GAL.CompareOp.Equal or GAL.CompareOp.EqualGl => CompareOp.Equal,
|
||||
GAL.CompareOp.LessOrEqual or GAL.CompareOp.LessOrEqualGl => CompareOp.LessOrEqual,
|
||||
GAL.CompareOp.Greater or GAL.CompareOp.GreaterGl => CompareOp.Greater,
|
||||
GAL.CompareOp.NotEqual or GAL.CompareOp.NotEqualGl => CompareOp.NotEqual,
|
||||
GAL.CompareOp.GreaterOrEqual or GAL.CompareOp.GreaterOrEqualGl => CompareOp.GreaterOrEqual,
|
||||
GAL.CompareOp.Always or GAL.CompareOp.AlwaysGl => CompareOp.Always,
|
||||
_ => LogInvalidAndReturn(op, nameof(GAL.CompareOp), 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 FrontFace Convert(this GAL.FrontFace frontFace)
|
||||
{
|
||||
// Flipped to account for origin differences.
|
||||
return frontFace switch
|
||||
{
|
||||
GAL.FrontFace.Clockwise => FrontFace.CounterClockwise,
|
||||
GAL.FrontFace.CounterClockwise => FrontFace.Clockwise,
|
||||
_ => LogInvalidAndReturn(frontFace, nameof(GAL.FrontFace), FrontFace.Clockwise),
|
||||
};
|
||||
}
|
||||
|
||||
public static IndexType Convert(this GAL.IndexType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
GAL.IndexType.UByte => IndexType.Uint8Ext,
|
||||
GAL.IndexType.UShort => IndexType.Uint16,
|
||||
GAL.IndexType.UInt => IndexType.Uint32,
|
||||
_ => LogInvalidAndReturn(type, nameof(GAL.IndexType), 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 PrimitiveTopology Convert(this GAL.PrimitiveTopology topology)
|
||||
{
|
||||
return topology switch
|
||||
{
|
||||
GAL.PrimitiveTopology.Points => PrimitiveTopology.PointList,
|
||||
GAL.PrimitiveTopology.Lines => PrimitiveTopology.LineList,
|
||||
GAL.PrimitiveTopology.LineStrip => PrimitiveTopology.LineStrip,
|
||||
GAL.PrimitiveTopology.Triangles => PrimitiveTopology.TriangleList,
|
||||
GAL.PrimitiveTopology.TriangleStrip => PrimitiveTopology.TriangleStrip,
|
||||
GAL.PrimitiveTopology.TriangleFan => PrimitiveTopology.TriangleFan,
|
||||
GAL.PrimitiveTopology.LinesAdjacency => PrimitiveTopology.LineListWithAdjacency,
|
||||
GAL.PrimitiveTopology.LineStripAdjacency => PrimitiveTopology.LineStripWithAdjacency,
|
||||
GAL.PrimitiveTopology.TrianglesAdjacency => PrimitiveTopology.TriangleListWithAdjacency,
|
||||
GAL.PrimitiveTopology.TriangleStripAdjacency => PrimitiveTopology.TriangleStripWithAdjacency,
|
||||
GAL.PrimitiveTopology.Patches => PrimitiveTopology.PatchList,
|
||||
GAL.PrimitiveTopology.Polygon => 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), PrimitiveTopology.TriangleList),
|
||||
};
|
||||
}
|
||||
|
||||
public static StencilOp Convert(this GAL.StencilOp op)
|
||||
{
|
||||
return op switch
|
||||
{
|
||||
GAL.StencilOp.Keep or GAL.StencilOp.KeepGl => StencilOp.Keep,
|
||||
GAL.StencilOp.Zero or GAL.StencilOp.ZeroGl => StencilOp.Zero,
|
||||
GAL.StencilOp.Replace or GAL.StencilOp.ReplaceGl => StencilOp.Replace,
|
||||
GAL.StencilOp.IncrementAndClamp or GAL.StencilOp.IncrementAndClampGl => StencilOp.IncrementAndClamp,
|
||||
GAL.StencilOp.DecrementAndClamp or GAL.StencilOp.DecrementAndClampGl => StencilOp.DecrementAndClamp,
|
||||
GAL.StencilOp.Invert or GAL.StencilOp.InvertGl => StencilOp.Invert,
|
||||
GAL.StencilOp.IncrementAndWrap or GAL.StencilOp.IncrementAndWrapGl => StencilOp.IncrementAndWrap,
|
||||
GAL.StencilOp.DecrementAndWrap or GAL.StencilOp.DecrementAndWrapGl => StencilOp.DecrementAndWrap,
|
||||
_ => LogInvalidAndReturn(op, nameof(GAL.StencilOp), 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 Format format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit,
|
||||
Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
Format.D24UnormS8Uint or
|
||||
Format.D32FloatS8Uint or
|
||||
Format.S8UintD24Unorm => ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit,
|
||||
_ => ImageAspectFlags.ColorBit,
|
||||
};
|
||||
}
|
||||
|
||||
public static ImageAspectFlags ConvertAspectFlags(this Format format, DepthStencilMode depthStencilMode)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
Format.D16Unorm or Format.D32Float or Format.X8UintD24Unorm => ImageAspectFlags.DepthBit,
|
||||
Format.S8Uint => ImageAspectFlags.StencilBit,
|
||||
Format.D24UnormS8Uint or
|
||||
Format.D32FloatS8Uint or
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
public static BufferAllocationType Convert(this BufferAccess access)
|
||||
{
|
||||
BufferAccess memType = access & BufferAccess.MemoryTypeMask;
|
||||
|
||||
if (memType == BufferAccess.HostMemory || access.HasFlag(BufferAccess.Stream))
|
||||
{
|
||||
return BufferAllocationType.HostMapped;
|
||||
}
|
||||
else if (memType == BufferAccess.DeviceMemory)
|
||||
{
|
||||
return BufferAllocationType.DeviceLocal;
|
||||
}
|
||||
else if (memType == BufferAccess.DeviceMemoryMapped)
|
||||
{
|
||||
return BufferAllocationType.DeviceLocalMapped;
|
||||
}
|
||||
|
||||
return BufferAllocationType.Auto;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Graphics.Rdna3Vulkan/FeedbackLoopAspects.cs
Normal file
12
src/Ryujinx.Graphics.Rdna3Vulkan/FeedbackLoopAspects.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
[Flags]
|
||||
internal enum FeedbackLoopAspects
|
||||
{
|
||||
None = 0,
|
||||
Color = 1 << 0,
|
||||
Depth = 1 << 1,
|
||||
}
|
||||
}
|
30
src/Ryujinx.Graphics.Rdna3Vulkan/FenceHelper.cs
Normal file
30
src/Ryujinx.Graphics.Rdna3Vulkan/FenceHelper.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
159
src/Ryujinx.Graphics.Rdna3Vulkan/FenceHolder.cs
Normal file
159
src/Ryujinx.Graphics.Rdna3Vulkan/FenceHolder.cs
Normal file
|
@ -0,0 +1,159 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class FenceHolder : IDisposable
|
||||
{
|
||||
private readonly Vk _api;
|
||||
private readonly Device _device;
|
||||
private Fence _fence;
|
||||
private int _referenceCount;
|
||||
private int _lock;
|
||||
private readonly bool _concurrentWaitUnsupported;
|
||||
private bool _disposed;
|
||||
|
||||
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported)
|
||||
{
|
||||
_api = api;
|
||||
_device = device;
|
||||
_concurrentWaitUnsupported = concurrentWaitUnsupported;
|
||||
|
||||
var fenceCreateInfo = new FenceCreateInfo
|
||||
{
|
||||
SType = StructureType.FenceCreateInfo,
|
||||
};
|
||||
|
||||
api.CreateFence(device, in fenceCreateInfo, null, out _fence).ThrowOnError();
|
||||
|
||||
_referenceCount = 1;
|
||||
}
|
||||
|
||||
public Fence GetUnsafe()
|
||||
{
|
||||
return _fence;
|
||||
}
|
||||
|
||||
public bool TryGet(out Fence fence)
|
||||
{
|
||||
int lastValue;
|
||||
do
|
||||
{
|
||||
lastValue = _referenceCount;
|
||||
|
||||
if (lastValue == 0)
|
||||
{
|
||||
fence = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||
|
||||
if (_concurrentWaitUnsupported)
|
||||
{
|
||||
AcquireLock();
|
||||
}
|
||||
|
||||
fence = _fence;
|
||||
return true;
|
||||
}
|
||||
|
||||
public Fence Get()
|
||||
{
|
||||
Interlocked.Increment(ref _referenceCount);
|
||||
return _fence;
|
||||
}
|
||||
|
||||
public void PutLock()
|
||||
{
|
||||
Put();
|
||||
|
||||
if (_concurrentWaitUnsupported)
|
||||
{
|
||||
ReleaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Put()
|
||||
{
|
||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
||||
{
|
||||
_api.DestroyFence(_device, _fence, Span<AllocationCallbacks>.Empty);
|
||||
_fence = default;
|
||||
}
|
||||
}
|
||||
|
||||
private void AcquireLock()
|
||||
{
|
||||
while (!TryAcquireLock())
|
||||
{
|
||||
Thread.SpinWait(32);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryAcquireLock()
|
||||
{
|
||||
return Interlocked.Exchange(ref _lock, 1) == 0;
|
||||
}
|
||||
|
||||
private void ReleaseLock()
|
||||
{
|
||||
Interlocked.Exchange(ref _lock, 0);
|
||||
}
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
if (_concurrentWaitUnsupported)
|
||||
{
|
||||
AcquireLock();
|
||||
|
||||
try
|
||||
{
|
||||
FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence });
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence });
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSignaled()
|
||||
{
|
||||
if (_concurrentWaitUnsupported)
|
||||
{
|
||||
if (!TryAcquireLock())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence });
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReleaseLock();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence });
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Put();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
233
src/Ryujinx.Graphics.Rdna3Vulkan/FormatCapabilities.cs
Normal file
233
src/Ryujinx.Graphics.Rdna3Vulkan/FormatCapabilities.cs
Normal file
|
@ -0,0 +1,233 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Format = Ryujinx.Graphics.GAL.Format;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class FormatCapabilities
|
||||
{
|
||||
private static readonly GAL.Format[] _scaledFormats = {
|
||||
GAL.Format.R8Uscaled,
|
||||
GAL.Format.R8Sscaled,
|
||||
GAL.Format.R16Uscaled,
|
||||
GAL.Format.R16Sscaled,
|
||||
GAL.Format.R8G8Uscaled,
|
||||
GAL.Format.R8G8Sscaled,
|
||||
GAL.Format.R16G16Uscaled,
|
||||
GAL.Format.R16G16Sscaled,
|
||||
GAL.Format.R8G8B8Uscaled,
|
||||
GAL.Format.R8G8B8Sscaled,
|
||||
GAL.Format.R16G16B16Uscaled,
|
||||
GAL.Format.R16G16B16Sscaled,
|
||||
GAL.Format.R8G8B8A8Uscaled,
|
||||
GAL.Format.R8G8B8A8Sscaled,
|
||||
GAL.Format.R16G16B16A16Uscaled,
|
||||
GAL.Format.R16G16B16A16Sscaled,
|
||||
GAL.Format.R10G10B10A2Uscaled,
|
||||
GAL.Format.R10G10B10A2Sscaled,
|
||||
};
|
||||
|
||||
private static readonly GAL.Format[] _intFormats = {
|
||||
GAL.Format.R8Uint,
|
||||
GAL.Format.R8Sint,
|
||||
GAL.Format.R16Uint,
|
||||
GAL.Format.R16Sint,
|
||||
GAL.Format.R8G8Uint,
|
||||
GAL.Format.R8G8Sint,
|
||||
GAL.Format.R16G16Uint,
|
||||
GAL.Format.R16G16Sint,
|
||||
GAL.Format.R8G8B8Uint,
|
||||
GAL.Format.R8G8B8Sint,
|
||||
GAL.Format.R16G16B16Uint,
|
||||
GAL.Format.R16G16B16Sint,
|
||||
GAL.Format.R8G8B8A8Uint,
|
||||
GAL.Format.R8G8B8A8Sint,
|
||||
GAL.Format.R16G16B16A16Uint,
|
||||
GAL.Format.R16G16B16A16Sint,
|
||||
GAL.Format.R10G10B10A2Uint,
|
||||
GAL.Format.R10G10B10A2Sint,
|
||||
};
|
||||
|
||||
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<Format>().Length;
|
||||
|
||||
_bufferTable = new FormatFeatureFlags[totalFormats];
|
||||
_optimalTable = new FormatFeatureFlags[totalFormats];
|
||||
}
|
||||
|
||||
public bool BufferFormatsSupport(FormatFeatureFlags flags, params ReadOnlySpan<Format> formats)
|
||||
{
|
||||
foreach (Format format in formats)
|
||||
{
|
||||
if (!BufferFormatSupports(flags, format))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OptimalFormatsSupport(FormatFeatureFlags flags, params ReadOnlySpan<Format> formats)
|
||||
{
|
||||
foreach (Format format in formats)
|
||||
{
|
||||
if (!OptimalFormatSupports(flags, format))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool BufferFormatSupports(FormatFeatureFlags flags, 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 SupportsScaledVertexFormats()
|
||||
{
|
||||
// We want to check is all scaled formats are supported,
|
||||
// but if the integer variant is not supported either,
|
||||
// then the format is likely not supported at all,
|
||||
// we ignore formats that are entirely unsupported here.
|
||||
|
||||
for (int i = 0; i < _scaledFormats.Length; i++)
|
||||
{
|
||||
if (!BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, _scaledFormats[i]) &&
|
||||
BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, _intFormats[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool BufferFormatSupports(FormatFeatureFlags flags, VkFormat format)
|
||||
{
|
||||
_api.GetPhysicalDeviceFormatProperties(_physicalDevice, format, out var fp);
|
||||
|
||||
return (fp.BufferFeatures & flags) == flags;
|
||||
}
|
||||
|
||||
public bool OptimalFormatSupports(FormatFeatureFlags flags, 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(Format srcFormat, bool storageFeatureFlagRequired)
|
||||
{
|
||||
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() && storageFeatureFlagRequired)
|
||||
{
|
||||
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 == Format.R4G4B4A4Unorm)
|
||||
{
|
||||
format = VkFormat.R4G4B4A4UnormPack16;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Format {srcFormat} is not supported by the host.");
|
||||
}
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
public VkFormat ConvertToVertexVkFormat(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 Format.R16G16B16Float:
|
||||
format = VkFormat.R16G16B16A16Sfloat;
|
||||
break;
|
||||
case Format.R16G16B16Sint:
|
||||
format = VkFormat.R16G16B16A16Sint;
|
||||
break;
|
||||
case 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(Format format)
|
||||
{
|
||||
return format == Format.D24UnormS8Uint || format == Format.S8UintD24Unorm || format == Format.X8UintD24Unorm;
|
||||
}
|
||||
|
||||
private static bool IsRGB16IntFloat(Format format)
|
||||
{
|
||||
return format == Format.R16G16B16Float ||
|
||||
format == Format.R16G16B16Sint ||
|
||||
format == Format.R16G16B16Uint;
|
||||
}
|
||||
}
|
||||
}
|
49
src/Ryujinx.Graphics.Rdna3Vulkan/FormatConverter.cs
Normal file
49
src/Ryujinx.Graphics.Rdna3Vulkan/FormatConverter.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
358
src/Ryujinx.Graphics.Rdna3Vulkan/FormatTable.cs
Normal file
358
src/Ryujinx.Graphics.Rdna3Vulkan/FormatTable.cs
Normal file
|
@ -0,0 +1,358 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
static class FormatTable
|
||||
{
|
||||
private static readonly VkFormat[] _table;
|
||||
private static readonly Dictionary<VkFormat, Format> _reverseMap;
|
||||
|
||||
static FormatTable()
|
||||
{
|
||||
_table = new VkFormat[Enum.GetNames<Format>().Length];
|
||||
_reverseMap = new Dictionary<VkFormat, Format>();
|
||||
|
||||
#pragma warning disable IDE0055 // Disable formatting
|
||||
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.X8UintD24Unorm, VkFormat.X8D24UnormPack32);
|
||||
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);
|
||||
Add(Format.B10G10R10A2Unorm, VkFormat.A2R10G10B10UnormPack32);
|
||||
#pragma warning restore IDE0055
|
||||
}
|
||||
|
||||
private static void Add(Format format, VkFormat vkFormat)
|
||||
{
|
||||
_table[(int)format] = vkFormat;
|
||||
_reverseMap[vkFormat] = format;
|
||||
}
|
||||
|
||||
public static VkFormat GetFormat(Format format)
|
||||
{
|
||||
return _table[(int)format];
|
||||
}
|
||||
|
||||
public static Format GetFormat(VkFormat format)
|
||||
{
|
||||
if (!_reverseMap.TryGetValue(format, out Format result))
|
||||
{
|
||||
return Format.B8G8R8A8Unorm;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Format ConvertRgba8SrgbToUnorm(Format format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
Format.R8G8B8A8Srgb => Format.R8G8B8A8Unorm,
|
||||
Format.B8G8R8A8Srgb => Format.B8G8R8A8Unorm,
|
||||
_ => format,
|
||||
};
|
||||
}
|
||||
|
||||
public static int GetAttributeFormatSize(VkFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VkFormat.R8Unorm:
|
||||
case VkFormat.R8SNorm:
|
||||
case VkFormat.R8Uint:
|
||||
case VkFormat.R8Sint:
|
||||
case VkFormat.R8Uscaled:
|
||||
case VkFormat.R8Sscaled:
|
||||
return 1;
|
||||
|
||||
case VkFormat.R8G8Unorm:
|
||||
case VkFormat.R8G8SNorm:
|
||||
case VkFormat.R8G8Uint:
|
||||
case VkFormat.R8G8Sint:
|
||||
case VkFormat.R8G8Uscaled:
|
||||
case VkFormat.R8G8Sscaled:
|
||||
case VkFormat.R16Sfloat:
|
||||
case VkFormat.R16Unorm:
|
||||
case VkFormat.R16SNorm:
|
||||
case VkFormat.R16Uint:
|
||||
case VkFormat.R16Sint:
|
||||
case VkFormat.R16Uscaled:
|
||||
case VkFormat.R16Sscaled:
|
||||
return 2;
|
||||
|
||||
case VkFormat.R8G8B8Unorm:
|
||||
case VkFormat.R8G8B8SNorm:
|
||||
case VkFormat.R8G8B8Uint:
|
||||
case VkFormat.R8G8B8Sint:
|
||||
case VkFormat.R8G8B8Uscaled:
|
||||
case VkFormat.R8G8B8Sscaled:
|
||||
return 3;
|
||||
|
||||
case VkFormat.R8G8B8A8Unorm:
|
||||
case VkFormat.R8G8B8A8SNorm:
|
||||
case VkFormat.R8G8B8A8Uint:
|
||||
case VkFormat.R8G8B8A8Sint:
|
||||
case VkFormat.R8G8B8A8Srgb:
|
||||
case VkFormat.R8G8B8A8Uscaled:
|
||||
case VkFormat.R8G8B8A8Sscaled:
|
||||
case VkFormat.B8G8R8A8Unorm:
|
||||
case VkFormat.B8G8R8A8Srgb:
|
||||
case VkFormat.R16G16Sfloat:
|
||||
case VkFormat.R16G16Unorm:
|
||||
case VkFormat.R16G16SNorm:
|
||||
case VkFormat.R16G16Uint:
|
||||
case VkFormat.R16G16Sint:
|
||||
case VkFormat.R16G16Uscaled:
|
||||
case VkFormat.R16G16Sscaled:
|
||||
case VkFormat.R32Sfloat:
|
||||
case VkFormat.R32Uint:
|
||||
case VkFormat.R32Sint:
|
||||
case VkFormat.A2B10G10R10UnormPack32:
|
||||
case VkFormat.A2B10G10R10UintPack32:
|
||||
case VkFormat.B10G11R11UfloatPack32:
|
||||
case VkFormat.E5B9G9R9UfloatPack32:
|
||||
case VkFormat.A2B10G10R10SNormPack32:
|
||||
case VkFormat.A2B10G10R10SintPack32:
|
||||
case VkFormat.A2B10G10R10UscaledPack32:
|
||||
case VkFormat.A2B10G10R10SscaledPack32:
|
||||
return 4;
|
||||
|
||||
case VkFormat.R16G16B16Sfloat:
|
||||
case VkFormat.R16G16B16Unorm:
|
||||
case VkFormat.R16G16B16SNorm:
|
||||
case VkFormat.R16G16B16Uint:
|
||||
case VkFormat.R16G16B16Sint:
|
||||
case VkFormat.R16G16B16Uscaled:
|
||||
case VkFormat.R16G16B16Sscaled:
|
||||
return 6;
|
||||
|
||||
case VkFormat.R16G16B16A16Sfloat:
|
||||
case VkFormat.R16G16B16A16Unorm:
|
||||
case VkFormat.R16G16B16A16SNorm:
|
||||
case VkFormat.R16G16B16A16Uint:
|
||||
case VkFormat.R16G16B16A16Sint:
|
||||
case VkFormat.R16G16B16A16Uscaled:
|
||||
case VkFormat.R16G16B16A16Sscaled:
|
||||
case VkFormat.R32G32Sfloat:
|
||||
case VkFormat.R32G32Uint:
|
||||
case VkFormat.R32G32Sint:
|
||||
return 8;
|
||||
|
||||
case VkFormat.R32G32B32Sfloat:
|
||||
case VkFormat.R32G32B32Uint:
|
||||
case VkFormat.R32G32B32Sint:
|
||||
return 12;
|
||||
|
||||
case VkFormat.R32G32B32A32Sfloat:
|
||||
case VkFormat.R32G32B32A32Uint:
|
||||
case VkFormat.R32G32B32A32Sint:
|
||||
return 16;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static VkFormat DropLastComponent(VkFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
VkFormat.R8G8Unorm => VkFormat.R8Unorm,
|
||||
VkFormat.R8G8SNorm => VkFormat.R8SNorm,
|
||||
VkFormat.R8G8Uint => VkFormat.R8Uint,
|
||||
VkFormat.R8G8Sint => VkFormat.R8Sint,
|
||||
VkFormat.R8G8Uscaled => VkFormat.R8Uscaled,
|
||||
VkFormat.R8G8Sscaled => VkFormat.R8Sscaled,
|
||||
VkFormat.R8G8B8Unorm => VkFormat.R8G8Unorm,
|
||||
VkFormat.R8G8B8SNorm => VkFormat.R8G8SNorm,
|
||||
VkFormat.R8G8B8Uint => VkFormat.R8G8Uint,
|
||||
VkFormat.R8G8B8Sint => VkFormat.R8G8Sint,
|
||||
VkFormat.R8G8B8Uscaled => VkFormat.R8G8Uscaled,
|
||||
VkFormat.R8G8B8Sscaled => VkFormat.R8G8Sscaled,
|
||||
VkFormat.R8G8B8A8Unorm => VkFormat.R8G8B8Unorm,
|
||||
VkFormat.R8G8B8A8SNorm => VkFormat.R8G8B8SNorm,
|
||||
VkFormat.R8G8B8A8Uint => VkFormat.R8G8B8Uint,
|
||||
VkFormat.R8G8B8A8Sint => VkFormat.R8G8B8Sint,
|
||||
VkFormat.R8G8B8A8Srgb => VkFormat.R8G8B8Srgb,
|
||||
VkFormat.R8G8B8A8Uscaled => VkFormat.R8G8B8Uscaled,
|
||||
VkFormat.R8G8B8A8Sscaled => VkFormat.R8G8B8Sscaled,
|
||||
VkFormat.B8G8R8A8Unorm => VkFormat.B8G8R8Unorm,
|
||||
VkFormat.B8G8R8A8Srgb => VkFormat.B8G8R8Srgb,
|
||||
VkFormat.R16G16Sfloat => VkFormat.R16Sfloat,
|
||||
VkFormat.R16G16Unorm => VkFormat.R16Unorm,
|
||||
VkFormat.R16G16SNorm => VkFormat.R16SNorm,
|
||||
VkFormat.R16G16Uint => VkFormat.R16Uint,
|
||||
VkFormat.R16G16Sint => VkFormat.R16Sint,
|
||||
VkFormat.R16G16Uscaled => VkFormat.R16Uscaled,
|
||||
VkFormat.R16G16Sscaled => VkFormat.R16Sscaled,
|
||||
VkFormat.R16G16B16Sfloat => VkFormat.R16G16Sfloat,
|
||||
VkFormat.R16G16B16Unorm => VkFormat.R16G16Unorm,
|
||||
VkFormat.R16G16B16SNorm => VkFormat.R16G16SNorm,
|
||||
VkFormat.R16G16B16Uint => VkFormat.R16G16Uint,
|
||||
VkFormat.R16G16B16Sint => VkFormat.R16G16Sint,
|
||||
VkFormat.R16G16B16Uscaled => VkFormat.R16G16Uscaled,
|
||||
VkFormat.R16G16B16Sscaled => VkFormat.R16G16Sscaled,
|
||||
VkFormat.R16G16B16A16Sfloat => VkFormat.R16G16B16Sfloat,
|
||||
VkFormat.R16G16B16A16Unorm => VkFormat.R16G16B16Unorm,
|
||||
VkFormat.R16G16B16A16SNorm => VkFormat.R16G16B16SNorm,
|
||||
VkFormat.R16G16B16A16Uint => VkFormat.R16G16B16Uint,
|
||||
VkFormat.R16G16B16A16Sint => VkFormat.R16G16B16Sint,
|
||||
VkFormat.R16G16B16A16Uscaled => VkFormat.R16G16B16Uscaled,
|
||||
VkFormat.R16G16B16A16Sscaled => VkFormat.R16G16B16Sscaled,
|
||||
VkFormat.R32G32Sfloat => VkFormat.R32Sfloat,
|
||||
VkFormat.R32G32Uint => VkFormat.R32Uint,
|
||||
VkFormat.R32G32Sint => VkFormat.R32Sint,
|
||||
VkFormat.R32G32B32Sfloat => VkFormat.R32G32Sfloat,
|
||||
VkFormat.R32G32B32Uint => VkFormat.R32G32Uint,
|
||||
VkFormat.R32G32B32Sint => VkFormat.R32G32Sint,
|
||||
VkFormat.R32G32B32A32Sfloat => VkFormat.R32G32B32Sfloat,
|
||||
VkFormat.R32G32B32A32Uint => VkFormat.R32G32B32Uint,
|
||||
VkFormat.R32G32B32A32Sint => VkFormat.R32G32B32Sint,
|
||||
_ => format,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
344
src/Ryujinx.Graphics.Rdna3Vulkan/FramebufferParams.cs
Normal file
344
src/Ryujinx.Graphics.Rdna3Vulkan/FramebufferParams.cs
Normal file
|
@ -0,0 +1,344 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class FramebufferParams
|
||||
{
|
||||
private readonly Device _device;
|
||||
private readonly Auto<DisposableImageView>[] _attachments;
|
||||
private readonly TextureView[] _colors;
|
||||
private readonly TextureView _depthStencil;
|
||||
private readonly TextureView[] _colorsCanonical;
|
||||
private readonly TextureView _baseAttachment;
|
||||
private readonly 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 uint AttachmentIntegerFormatMask { get; }
|
||||
public bool LogicOpsAllowed { get; }
|
||||
|
||||
public int AttachmentsCount { get; }
|
||||
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[^1] : -1;
|
||||
public bool HasDepthStencil { get; }
|
||||
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
|
||||
|
||||
public FramebufferParams(Device device, TextureView view, uint width, uint height)
|
||||
{
|
||||
var format = view.Info.Format;
|
||||
|
||||
bool isDepthStencil = format.IsDepthOrStencil();
|
||||
|
||||
_device = device;
|
||||
_attachments = new[] { view.GetImageViewForAttachment() };
|
||||
_validColorAttachments = isDepthStencil ? 0u : 1u;
|
||||
_baseAttachment = view;
|
||||
|
||||
if (isDepthStencil)
|
||||
{
|
||||
_depthStencil = view;
|
||||
}
|
||||
else
|
||||
{
|
||||
_colors = new TextureView[] { view };
|
||||
_colorsCanonical = _colors;
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Layers = 1;
|
||||
|
||||
AttachmentSamples = new[] { (uint)view.Info.Samples };
|
||||
AttachmentFormats = new[] { view.VkFormat };
|
||||
AttachmentIndices = isDepthStencil ? Array.Empty<int>() : new[] { 0 };
|
||||
AttachmentIntegerFormatMask = format.IsInteger() ? 1u : 0u;
|
||||
LogicOpsAllowed = !format.IsFloatOrSrgb();
|
||||
|
||||
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];
|
||||
_colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
|
||||
|
||||
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;
|
||||
uint attachmentIntegerFormatMask = 0;
|
||||
bool allFormatsFloatOrSrgb = colorsCount != 0;
|
||||
|
||||
foreach (ITexture color in colors)
|
||||
{
|
||||
if (IsValidTextureView(color))
|
||||
{
|
||||
var texture = (TextureView)color;
|
||||
|
||||
_attachments[index] = texture.GetImageViewForAttachment();
|
||||
_colors[index] = texture;
|
||||
_validColorAttachments |= 1u << bindIndex;
|
||||
_baseAttachment = texture;
|
||||
|
||||
AttachmentSamples[index] = (uint)texture.Info.Samples;
|
||||
AttachmentFormats[index] = texture.VkFormat;
|
||||
AttachmentIndices[index] = bindIndex;
|
||||
|
||||
var format = texture.Info.Format;
|
||||
|
||||
if (format.IsInteger())
|
||||
{
|
||||
attachmentIntegerFormatMask |= 1u << bindIndex;
|
||||
}
|
||||
|
||||
allFormatsFloatOrSrgb &= format.IsFloatOrSrgb();
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
|
||||
LogicOpsAllowed = !allFormatsFloatOrSrgb;
|
||||
|
||||
if (depthStencil is TextureView dsTexture && dsTexture.Valid)
|
||||
{
|
||||
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
|
||||
_depthStencil = dsTexture;
|
||||
_baseAttachment ??= 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 Auto<DisposableImageView> GetDepthStencilAttachment()
|
||||
{
|
||||
if (!HasDepthStencil)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _attachments[AttachmentsCount - 1];
|
||||
}
|
||||
|
||||
public ComponentType GetAttachmentComponentType(int index)
|
||||
{
|
||||
if (_colors != null && (uint)index < _colors.Length)
|
||||
{
|
||||
var format = _colors[index].Info.Format;
|
||||
|
||||
if (format.IsSint())
|
||||
{
|
||||
return ComponentType.SignedInteger;
|
||||
}
|
||||
|
||||
if (format.IsUint())
|
||||
{
|
||||
return ComponentType.UnsignedInteger;
|
||||
}
|
||||
}
|
||||
|
||||
return ComponentType.Float;
|
||||
}
|
||||
|
||||
public ImageAspectFlags GetDepthStencilAspectFlags()
|
||||
{
|
||||
if (_depthStencil == null)
|
||||
{
|
||||
return ImageAspectFlags.None;
|
||||
}
|
||||
|
||||
return _depthStencil.Info.Format.ConvertAspectFlags();
|
||||
}
|
||||
|
||||
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, in framebufferCreateInfo, null, out var framebuffer).ThrowOnError();
|
||||
return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
|
||||
}
|
||||
|
||||
public TextureView[] GetAttachmentViews()
|
||||
{
|
||||
var result = new TextureView[_attachments.Length];
|
||||
|
||||
_colors?.CopyTo(result, 0);
|
||||
|
||||
if (_depthStencil != null)
|
||||
{
|
||||
result[^1] = _depthStencil;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public RenderPassCacheKey GetRenderPassCacheKey()
|
||||
{
|
||||
return new RenderPassCacheKey(_depthStencil, _colorsCanonical);
|
||||
}
|
||||
|
||||
public void InsertLoadOpBarriers(VulkanRenderer gd, CommandBufferScoped cbs)
|
||||
{
|
||||
if (_colors != null)
|
||||
{
|
||||
foreach (var color in _colors)
|
||||
{
|
||||
// If Clear or DontCare were used, this would need to be write bit.
|
||||
color.Storage?.QueueLoadOpBarrier(cbs, false);
|
||||
}
|
||||
}
|
||||
|
||||
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
|
||||
|
||||
gd.Barriers.Flush(cbs, false, null, null);
|
||||
}
|
||||
|
||||
public void AddStoreOpUsage()
|
||||
{
|
||||
if (_colors != null)
|
||||
{
|
||||
foreach (var color in _colors)
|
||||
{
|
||||
color.Storage?.AddStoreOpUsage(false);
|
||||
}
|
||||
}
|
||||
|
||||
_depthStencil?.Storage?.AddStoreOpUsage(true);
|
||||
}
|
||||
|
||||
public void ClearBindings()
|
||||
{
|
||||
_depthStencil?.Storage.ClearBindings();
|
||||
|
||||
for (int i = 0; i < _colorsCanonical.Length; i++)
|
||||
{
|
||||
_colorsCanonical[i]?.Storage.ClearBindings();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBindings()
|
||||
{
|
||||
_depthStencil?.Storage.AddBinding(_depthStencil);
|
||||
|
||||
for (int i = 0; i < _colorsCanonical.Length; i++)
|
||||
{
|
||||
TextureView color = _colorsCanonical[i];
|
||||
color?.Storage.AddBinding(color);
|
||||
}
|
||||
}
|
||||
|
||||
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
CommandBufferScoped cbs)
|
||||
{
|
||||
return _baseAttachment.GetPassAndFramebuffer(gd, device, cbs, this);
|
||||
}
|
||||
|
||||
public TextureView GetColorView(int index)
|
||||
{
|
||||
return _colorsCanonical[index];
|
||||
}
|
||||
|
||||
public TextureView GetDepthStencilView()
|
||||
{
|
||||
return _depthStencil;
|
||||
}
|
||||
}
|
||||
}
|
138
src/Ryujinx.Graphics.Rdna3Vulkan/HardwareCapabilities.cs
Normal file
138
src/Ryujinx.Graphics.Rdna3Vulkan/HardwareCapabilities.cs
Normal file
|
@ -0,0 +1,138 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
[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 SupportsShaderFloat64;
|
||||
public readonly bool SupportsShaderInt8;
|
||||
public readonly bool SupportsShaderStencilExport;
|
||||
public readonly bool SupportsShaderStorageImageMultisample;
|
||||
public readonly bool SupportsConditionalRendering;
|
||||
public readonly bool SupportsExtendedDynamicState;
|
||||
public readonly bool SupportsMultiView;
|
||||
public readonly bool SupportsNullDescriptors;
|
||||
public readonly bool SupportsPushDescriptors;
|
||||
public readonly uint MaxPushDescriptors;
|
||||
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 SupportsTessellationShader;
|
||||
public readonly bool SupportsViewportArray2;
|
||||
public readonly bool SupportsHostImportedMemory;
|
||||
public readonly bool SupportsDepthClipControl;
|
||||
public readonly bool SupportsAttachmentFeedbackLoop;
|
||||
public readonly bool SupportsDynamicAttachmentFeedbackLoop;
|
||||
public readonly uint SubgroupSize;
|
||||
public readonly SampleCountFlags SupportedSampleCounts;
|
||||
public readonly PortabilitySubsetFlags PortabilitySubset;
|
||||
public readonly uint VertexBufferAlignment;
|
||||
public readonly uint SubTexelPrecisionBits;
|
||||
public readonly ulong MinResourceAlignment;
|
||||
|
||||
public HardwareCapabilities(
|
||||
bool supportsIndexTypeUint8,
|
||||
bool supportsCustomBorderColor,
|
||||
bool supportsBlendEquationAdvanced,
|
||||
bool supportsBlendEquationAdvancedCorrelatedOverlap,
|
||||
bool supportsBlendEquationAdvancedNonPreMultipliedSrcColor,
|
||||
bool supportsBlendEquationAdvancedNonPreMultipliedDstColor,
|
||||
bool supportsIndirectParameters,
|
||||
bool supportsFragmentShaderInterlock,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
bool supportsShaderFloat64,
|
||||
bool supportsShaderInt8,
|
||||
bool supportsShaderStencilExport,
|
||||
bool supportsShaderStorageImageMultisample,
|
||||
bool supportsConditionalRendering,
|
||||
bool supportsExtendedDynamicState,
|
||||
bool supportsMultiView,
|
||||
bool supportsNullDescriptors,
|
||||
bool supportsPushDescriptors,
|
||||
uint maxPushDescriptors,
|
||||
bool supportsPrimitiveTopologyListRestart,
|
||||
bool supportsPrimitiveTopologyPatchListRestart,
|
||||
bool supportsTransformFeedback,
|
||||
bool supportsTransformFeedbackQueries,
|
||||
bool supportsPreciseOcclusionQueries,
|
||||
bool supportsPipelineStatisticsQuery,
|
||||
bool supportsGeometryShader,
|
||||
bool supportsTessellationShader,
|
||||
bool supportsViewportArray2,
|
||||
bool supportsHostImportedMemory,
|
||||
bool supportsDepthClipControl,
|
||||
bool supportsAttachmentFeedbackLoop,
|
||||
bool supportsDynamicAttachmentFeedbackLoop,
|
||||
uint subgroupSize,
|
||||
SampleCountFlags supportedSampleCounts,
|
||||
PortabilitySubsetFlags portabilitySubset,
|
||||
uint vertexBufferAlignment,
|
||||
uint subTexelPrecisionBits,
|
||||
ulong minResourceAlignment)
|
||||
{
|
||||
SupportsIndexTypeUint8 = supportsIndexTypeUint8;
|
||||
SupportsCustomBorderColor = supportsCustomBorderColor;
|
||||
SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced;
|
||||
SupportsBlendEquationAdvancedCorrelatedOverlap = supportsBlendEquationAdvancedCorrelatedOverlap;
|
||||
SupportsBlendEquationAdvancedNonPreMultipliedSrcColor = supportsBlendEquationAdvancedNonPreMultipliedSrcColor;
|
||||
SupportsBlendEquationAdvancedNonPreMultipliedDstColor = supportsBlendEquationAdvancedNonPreMultipliedDstColor;
|
||||
SupportsIndirectParameters = supportsIndirectParameters;
|
||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||
SupportsShaderInt8 = supportsShaderInt8;
|
||||
SupportsShaderStencilExport = supportsShaderStencilExport;
|
||||
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
|
||||
SupportsConditionalRendering = supportsConditionalRendering;
|
||||
SupportsExtendedDynamicState = supportsExtendedDynamicState;
|
||||
SupportsMultiView = supportsMultiView;
|
||||
SupportsNullDescriptors = supportsNullDescriptors;
|
||||
SupportsPushDescriptors = supportsPushDescriptors;
|
||||
MaxPushDescriptors = maxPushDescriptors;
|
||||
SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart;
|
||||
SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart;
|
||||
SupportsTransformFeedback = supportsTransformFeedback;
|
||||
SupportsTransformFeedbackQueries = supportsTransformFeedbackQueries;
|
||||
SupportsPreciseOcclusionQueries = supportsPreciseOcclusionQueries;
|
||||
SupportsPipelineStatisticsQuery = supportsPipelineStatisticsQuery;
|
||||
SupportsGeometryShader = supportsGeometryShader;
|
||||
SupportsTessellationShader = supportsTessellationShader;
|
||||
SupportsViewportArray2 = supportsViewportArray2;
|
||||
SupportsHostImportedMemory = supportsHostImportedMemory;
|
||||
SupportsDepthClipControl = supportsDepthClipControl;
|
||||
SupportsAttachmentFeedbackLoop = supportsAttachmentFeedbackLoop;
|
||||
SupportsDynamicAttachmentFeedbackLoop = supportsDynamicAttachmentFeedbackLoop;
|
||||
SubgroupSize = subgroupSize;
|
||||
SupportedSampleCounts = supportedSampleCounts;
|
||||
PortabilitySubset = portabilitySubset;
|
||||
VertexBufferAlignment = vertexBufferAlignment;
|
||||
SubTexelPrecisionBits = subTexelPrecisionBits;
|
||||
MinResourceAlignment = minResourceAlignment;
|
||||
}
|
||||
}
|
||||
}
|
143
src/Ryujinx.Graphics.Rdna3Vulkan/HashTableSlim.cs
Normal file
143
src/Ryujinx.Graphics.Rdna3Vulkan/HashTableSlim.cs
Normal file
|
@ -0,0 +1,143 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
interface IRefEquatable<T>
|
||||
{
|
||||
bool Equals(ref T other);
|
||||
}
|
||||
|
||||
class HashTableSlim<TKey, TValue> where TKey : IRefEquatable<TKey>
|
||||
{
|
||||
private const int TotalBuckets = 16; // Must be power of 2
|
||||
private const int TotalBucketsMask = TotalBuckets - 1;
|
||||
|
||||
private struct Entry
|
||||
{
|
||||
public int Hash;
|
||||
public TKey Key;
|
||||
public TValue Value;
|
||||
}
|
||||
|
||||
private struct Bucket
|
||||
{
|
||||
public int Length;
|
||||
public Entry[] Entries;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Span<Entry> AsSpan()
|
||||
{
|
||||
return Entries == null ? Span<Entry>.Empty : Entries.AsSpan(0, Length);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Bucket[] _hashTable = new Bucket[TotalBuckets];
|
||||
|
||||
public IEnumerable<TKey> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (Bucket bucket in _hashTable)
|
||||
{
|
||||
for (int i = 0; i < bucket.Length; i++)
|
||||
{
|
||||
yield return bucket.Entries[i].Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TValue> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (Bucket bucket in _hashTable)
|
||||
{
|
||||
for (int i = 0; i < bucket.Length; i++)
|
||||
{
|
||||
yield return bucket.Entries[i].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(ref TKey key, TValue value)
|
||||
{
|
||||
var entry = new Entry
|
||||
{
|
||||
Hash = key.GetHashCode(),
|
||||
Key = key,
|
||||
Value = value,
|
||||
};
|
||||
|
||||
int hashCode = key.GetHashCode();
|
||||
int bucketIndex = hashCode & TotalBucketsMask;
|
||||
|
||||
ref var bucket = ref _hashTable[bucketIndex];
|
||||
if (bucket.Entries != null)
|
||||
{
|
||||
int index = bucket.Length;
|
||||
|
||||
if (index >= bucket.Entries.Length)
|
||||
{
|
||||
Array.Resize(ref bucket.Entries, index + 1);
|
||||
}
|
||||
|
||||
bucket.Entries[index] = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
bucket.Entries = new[]
|
||||
{
|
||||
entry,
|
||||
};
|
||||
}
|
||||
|
||||
bucket.Length++;
|
||||
}
|
||||
|
||||
public bool Remove(ref TKey key)
|
||||
{
|
||||
int hashCode = key.GetHashCode();
|
||||
|
||||
ref var bucket = ref _hashTable[hashCode & TotalBucketsMask];
|
||||
var entries = bucket.AsSpan();
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
ref var entry = ref entries[i];
|
||||
|
||||
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
|
||||
{
|
||||
entries[(i + 1)..].CopyTo(entries[i..]);
|
||||
bucket.Length--;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetValue(ref TKey key, out TValue value)
|
||||
{
|
||||
int hashCode = key.GetHashCode();
|
||||
|
||||
var entries = _hashTable[hashCode & TotalBucketsMask].AsSpan();
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
ref var entry = ref entries[i];
|
||||
|
||||
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
|
||||
{
|
||||
value = entry.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
1740
src/Ryujinx.Graphics.Rdna3Vulkan/HelperShader.cs
Normal file
1740
src/Ryujinx.Graphics.Rdna3Vulkan/HelperShader.cs
Normal file
File diff suppressed because it is too large
Load diff
189
src/Ryujinx.Graphics.Rdna3Vulkan/HostMemoryAllocator.cs
Normal file
189
src/Ryujinx.Graphics.Rdna3Vulkan/HostMemoryAllocator.cs
Normal file
|
@ -0,0 +1,189 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Collections;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
internal class HostMemoryAllocator
|
||||
{
|
||||
private readonly struct HostMemoryAllocation
|
||||
{
|
||||
public readonly Auto<MemoryAllocation> Allocation;
|
||||
public readonly nint Pointer;
|
||||
public readonly ulong Size;
|
||||
|
||||
public ulong Start => (ulong)Pointer;
|
||||
public ulong End => (ulong)Pointer + Size;
|
||||
|
||||
public HostMemoryAllocation(Auto<MemoryAllocation> allocation, nint pointer, ulong size)
|
||||
{
|
||||
Allocation = allocation;
|
||||
Pointer = pointer;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MemoryAllocator _allocator;
|
||||
private readonly Vk _api;
|
||||
private readonly ExtExternalMemoryHost _hostMemoryApi;
|
||||
private readonly Device _device;
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private readonly List<HostMemoryAllocation> _allocations;
|
||||
private readonly IntervalTree<ulong, HostMemoryAllocation> _allocationTree;
|
||||
|
||||
public HostMemoryAllocator(MemoryAllocator allocator, Vk api, ExtExternalMemoryHost hostMemoryApi, Device device)
|
||||
{
|
||||
_allocator = allocator;
|
||||
_api = api;
|
||||
_hostMemoryApi = hostMemoryApi;
|
||||
_device = device;
|
||||
|
||||
_allocations = new List<HostMemoryAllocation>();
|
||||
_allocationTree = new IntervalTree<ulong, HostMemoryAllocation>();
|
||||
}
|
||||
|
||||
public unsafe bool TryImport(
|
||||
MemoryRequirements requirements,
|
||||
MemoryPropertyFlags flags,
|
||||
nint pointer,
|
||||
ulong size)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Does a compatible allocation exist in the tree?
|
||||
var allocations = new HostMemoryAllocation[10];
|
||||
|
||||
ulong start = (ulong)pointer;
|
||||
ulong end = start + size;
|
||||
|
||||
int count = _allocationTree.Get(start, end, ref allocations);
|
||||
|
||||
// A compatible range is one that where the start and end completely cover the requested range.
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
HostMemoryAllocation existing = allocations[i];
|
||||
|
||||
if (start >= existing.Start && end <= existing.End)
|
||||
{
|
||||
try
|
||||
{
|
||||
existing.Allocation.IncrementReferenceCount();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Can throw if the allocation has been disposed.
|
||||
// Just continue the search if this happens.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nint pageAlignedPointer = BitUtils.AlignDown(pointer, Environment.SystemPageSize);
|
||||
nint pageAlignedEnd = BitUtils.AlignUp((nint)((ulong)pointer + size), Environment.SystemPageSize);
|
||||
ulong pageAlignedSize = (ulong)(pageAlignedEnd - pageAlignedPointer);
|
||||
|
||||
Result getResult = _hostMemoryApi.GetMemoryHostPointerProperties(_device, ExternalMemoryHandleTypeFlags.HostAllocationBitExt, (void*)pageAlignedPointer, out MemoryHostPointerPropertiesEXT properties);
|
||||
if (getResult < Result.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int memoryTypeIndex = _allocator.FindSuitableMemoryTypeIndex(properties.MemoryTypeBits & requirements.MemoryTypeBits, flags);
|
||||
if (memoryTypeIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ImportMemoryHostPointerInfoEXT importInfo = new()
|
||||
{
|
||||
SType = StructureType.ImportMemoryHostPointerInfoExt,
|
||||
HandleType = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
|
||||
PHostPointer = (void*)pageAlignedPointer,
|
||||
};
|
||||
|
||||
var memoryAllocateInfo = new MemoryAllocateInfo
|
||||
{
|
||||
SType = StructureType.MemoryAllocateInfo,
|
||||
AllocationSize = pageAlignedSize,
|
||||
MemoryTypeIndex = (uint)memoryTypeIndex,
|
||||
PNext = &importInfo,
|
||||
};
|
||||
|
||||
Result result = _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory);
|
||||
|
||||
if (result < Result.Success)
|
||||
{
|
||||
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Host mapping import 0x{pageAlignedPointer:x16} 0x{pageAlignedSize:x8} failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var allocation = new MemoryAllocation(this, deviceMemory, pageAlignedPointer, 0, pageAlignedSize);
|
||||
var allocAuto = new Auto<MemoryAllocation>(allocation);
|
||||
var hostAlloc = new HostMemoryAllocation(allocAuto, pageAlignedPointer, pageAlignedSize);
|
||||
|
||||
allocAuto.IncrementReferenceCount();
|
||||
allocAuto.Dispose(); // Kept alive by ref count only.
|
||||
|
||||
// Register this mapping for future use.
|
||||
|
||||
_allocationTree.Add(hostAlloc.Start, hostAlloc.End, hostAlloc);
|
||||
_allocations.Add(hostAlloc);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public (Auto<MemoryAllocation>, ulong) GetExistingAllocation(nint pointer, ulong size)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// Does a compatible allocation exist in the tree?
|
||||
var allocations = new HostMemoryAllocation[10];
|
||||
|
||||
ulong start = (ulong)pointer;
|
||||
ulong end = start + size;
|
||||
|
||||
int count = _allocationTree.Get(start, end, ref allocations);
|
||||
|
||||
// A compatible range is one that where the start and end completely cover the requested range.
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
HostMemoryAllocation existing = allocations[i];
|
||||
|
||||
if (start >= existing.Start && end <= existing.End)
|
||||
{
|
||||
return (existing.Allocation, start - existing.Start);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"No host allocation was prepared for requested range 0x{pointer:x16}:0x{size:x16}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(DeviceMemory memory, ulong offset, ulong size)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_allocations.RemoveAll(allocation =>
|
||||
{
|
||||
if (allocation.Allocation.GetUnsafe().Memory.Handle == memory.Handle)
|
||||
{
|
||||
_allocationTree.Remove(allocation.Start, allocation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
_api.FreeMemory(_device, memory, ReadOnlySpan<AllocationCallbacks>.Empty);
|
||||
}
|
||||
}
|
||||
}
|
121
src/Ryujinx.Graphics.Rdna3Vulkan/IdList.cs
Normal file
121
src/Ryujinx.Graphics.Rdna3Vulkan/IdList.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
207
src/Ryujinx.Graphics.Rdna3Vulkan/ImageArray.cs
Normal file
207
src/Ryujinx.Graphics.Rdna3Vulkan/ImageArray.cs
Normal file
|
@ -0,0 +1,207 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class ImageArray : ResourceArray, IImageArray
|
||||
{
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
private record struct TextureRef
|
||||
{
|
||||
public TextureStorage Storage;
|
||||
public TextureView View;
|
||||
}
|
||||
|
||||
private readonly TextureRef[] _textureRefs;
|
||||
private readonly TextureBuffer[] _bufferTextureRefs;
|
||||
|
||||
private readonly DescriptorImageInfo[] _textures;
|
||||
private readonly BufferView[] _bufferTextures;
|
||||
|
||||
private HashSet<TextureStorage> _storages;
|
||||
|
||||
private int _cachedCommandBufferIndex;
|
||||
private int _cachedSubmissionCount;
|
||||
|
||||
private readonly bool _isBuffer;
|
||||
|
||||
public ImageArray(VulkanRenderer gd, int size, bool isBuffer)
|
||||
{
|
||||
_gd = gd;
|
||||
|
||||
if (isBuffer)
|
||||
{
|
||||
_bufferTextureRefs = new TextureBuffer[size];
|
||||
_bufferTextures = new BufferView[size];
|
||||
}
|
||||
else
|
||||
{
|
||||
_textureRefs = new TextureRef[size];
|
||||
_textures = new DescriptorImageInfo[size];
|
||||
}
|
||||
|
||||
_storages = null;
|
||||
|
||||
_cachedCommandBufferIndex = -1;
|
||||
_cachedSubmissionCount = 0;
|
||||
|
||||
_isBuffer = isBuffer;
|
||||
}
|
||||
|
||||
public void SetImages(int index, ITexture[] images)
|
||||
{
|
||||
for (int i = 0; i < images.Length; i++)
|
||||
{
|
||||
ITexture image = images[i];
|
||||
|
||||
if (image is TextureBuffer textureBuffer)
|
||||
{
|
||||
_bufferTextureRefs[index + i] = textureBuffer;
|
||||
}
|
||||
else if (image is TextureView view)
|
||||
{
|
||||
_textureRefs[index + i].Storage = view.Storage;
|
||||
_textureRefs[index + i].View = view;
|
||||
}
|
||||
else if (!_isBuffer)
|
||||
{
|
||||
_textureRefs[index + i].Storage = null;
|
||||
_textureRefs[index + i].View = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
_bufferTextureRefs[index + i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
SetDirty();
|
||||
}
|
||||
|
||||
private void SetDirty()
|
||||
{
|
||||
_cachedCommandBufferIndex = -1;
|
||||
_storages = null;
|
||||
SetDirty(_gd, isImage: true);
|
||||
}
|
||||
|
||||
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
||||
{
|
||||
HashSet<TextureStorage> storages = _storages;
|
||||
|
||||
if (storages == null)
|
||||
{
|
||||
storages = new HashSet<TextureStorage>();
|
||||
|
||||
for (int index = 0; index < _textureRefs.Length; index++)
|
||||
{
|
||||
if (_textureRefs[index].Storage != null)
|
||||
{
|
||||
storages.Add(_textureRefs[index].Storage);
|
||||
}
|
||||
}
|
||||
|
||||
_storages = storages;
|
||||
}
|
||||
|
||||
foreach (TextureStorage storage in storages)
|
||||
{
|
||||
storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stageFlags);
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlySpan<DescriptorImageInfo> GetImageInfos(VulkanRenderer gd, CommandBufferScoped cbs, TextureView dummyTexture)
|
||||
{
|
||||
int submissionCount = gd.CommandBufferPool.GetSubmissionCount(cbs.CommandBufferIndex);
|
||||
|
||||
Span<DescriptorImageInfo> textures = _textures;
|
||||
|
||||
if (cbs.CommandBufferIndex == _cachedCommandBufferIndex && submissionCount == _cachedSubmissionCount)
|
||||
{
|
||||
return textures;
|
||||
}
|
||||
|
||||
_cachedCommandBufferIndex = cbs.CommandBufferIndex;
|
||||
_cachedSubmissionCount = submissionCount;
|
||||
|
||||
for (int i = 0; i < textures.Length; i++)
|
||||
{
|
||||
ref var texture = ref textures[i];
|
||||
ref var refs = ref _textureRefs[i];
|
||||
|
||||
if (i > 0 && _textureRefs[i - 1].View == refs.View)
|
||||
{
|
||||
texture = textures[i - 1];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
texture.ImageLayout = ImageLayout.General;
|
||||
texture.ImageView = refs.View?.GetIdentityImageView().Get(cbs).Value ?? default;
|
||||
|
||||
if (texture.ImageView.Handle == 0)
|
||||
{
|
||||
texture.ImageView = dummyTexture.GetImageView().Get(cbs).Value;
|
||||
}
|
||||
}
|
||||
|
||||
return textures;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<BufferView> GetBufferViews(CommandBufferScoped cbs)
|
||||
{
|
||||
Span<BufferView> bufferTextures = _bufferTextures;
|
||||
|
||||
for (int i = 0; i < bufferTextures.Length; i++)
|
||||
{
|
||||
bufferTextures[i] = _bufferTextureRefs[i]?.GetBufferView(cbs, true) ?? default;
|
||||
}
|
||||
|
||||
return bufferTextures;
|
||||
}
|
||||
|
||||
public DescriptorSet[] GetDescriptorSets(
|
||||
Device device,
|
||||
CommandBufferScoped cbs,
|
||||
DescriptorSetTemplateUpdater templateUpdater,
|
||||
ShaderCollection program,
|
||||
int setIndex,
|
||||
TextureView dummyTexture)
|
||||
{
|
||||
if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets))
|
||||
{
|
||||
// We still need to ensure the current command buffer holds a reference to all used textures.
|
||||
|
||||
if (!_isBuffer)
|
||||
{
|
||||
GetImageInfos(_gd, cbs, dummyTexture);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetBufferViews(cbs);
|
||||
}
|
||||
|
||||
return sets;
|
||||
}
|
||||
|
||||
DescriptorSetTemplate template = program.Templates[setIndex];
|
||||
|
||||
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
|
||||
|
||||
if (!_isBuffer)
|
||||
{
|
||||
tu.Push(GetImageInfos(_gd, cbs, dummyTexture));
|
||||
}
|
||||
else
|
||||
{
|
||||
tu.Push(GetBufferViews(cbs));
|
||||
}
|
||||
|
||||
templateUpdater.Commit(_gd, device, sets[0]);
|
||||
|
||||
return sets;
|
||||
}
|
||||
}
|
||||
}
|
139
src/Ryujinx.Graphics.Rdna3Vulkan/IndexBufferPattern.cs
Normal file
139
src/Ryujinx.Graphics.Rdna3Vulkan/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.Rdna3Vulkan
|
||||
{
|
||||
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 readonly 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
171
src/Ryujinx.Graphics.Rdna3Vulkan/IndexBufferState.cs
Normal file
171
src/Ryujinx.Graphics.Rdna3Vulkan/IndexBufferState.cs
Normal file
|
@ -0,0 +1,171 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using IndexType = Silk.NET.Vulkan.IndexType;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
internal struct IndexBufferState
|
||||
{
|
||||
private const int IndexBufferMaxMirrorable = 0x20000;
|
||||
|
||||
public static IndexBufferState Null => new(BufferHandle.Null, 0, 0);
|
||||
|
||||
private readonly int _offset;
|
||||
private readonly int _size;
|
||||
private readonly IndexType _type;
|
||||
|
||||
private readonly BufferHandle _handle;
|
||||
private Auto<DisposableBuffer> _buffer;
|
||||
|
||||
public IndexBufferState(BufferHandle handle, int offset, int size, IndexType type)
|
||||
{
|
||||
_handle = handle;
|
||||
_offset = offset;
|
||||
_size = size;
|
||||
_type = type;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
public IndexBufferState(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;
|
||||
bool mirrorable = false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
mirrorable = _size < IndexBufferMaxMirrorable;
|
||||
|
||||
offset = _offset;
|
||||
size = _size;
|
||||
}
|
||||
|
||||
_buffer = autoBuffer;
|
||||
|
||||
if (autoBuffer != null)
|
||||
{
|
||||
DisposableBuffer buffer = mirrorable ? autoBuffer.GetMirrorable(cbs, ref offset, size, out _) : autoBuffer.Get(cbs, offset, size);
|
||||
|
||||
gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, buffer.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,
|
||||
BufferRange indirectBuffer,
|
||||
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 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 readonly int GetIndexSize()
|
||||
{
|
||||
return _type switch
|
||||
{
|
||||
IndexType.Uint32 => 4,
|
||||
IndexType.Uint16 => 2,
|
||||
_ => 1,
|
||||
};
|
||||
}
|
||||
|
||||
public readonly bool BoundEquals(Auto<DisposableBuffer> buffer)
|
||||
{
|
||||
return _buffer == buffer;
|
||||
}
|
||||
|
||||
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
if (_buffer == from)
|
||||
{
|
||||
_buffer = to;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool Overlaps(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||
{
|
||||
return buffer == _buffer && offset < _offset + _size && offset + size > _offset;
|
||||
}
|
||||
}
|
||||
}
|
59
src/Ryujinx.Graphics.Rdna3Vulkan/MemoryAllocation.cs
Normal file
59
src/Ryujinx.Graphics.Rdna3Vulkan/MemoryAllocation.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
readonly struct MemoryAllocation : IDisposable
|
||||
{
|
||||
private readonly MemoryAllocatorBlockList _owner;
|
||||
private readonly MemoryAllocatorBlockList.Block _block;
|
||||
private readonly HostMemoryAllocator _hostMemory;
|
||||
|
||||
public DeviceMemory Memory { get; }
|
||||
public nint HostPointer { get; }
|
||||
public ulong Offset { get; }
|
||||
public ulong Size { get; }
|
||||
|
||||
public MemoryAllocation(
|
||||
MemoryAllocatorBlockList owner,
|
||||
MemoryAllocatorBlockList.Block block,
|
||||
DeviceMemory memory,
|
||||
nint hostPointer,
|
||||
ulong offset,
|
||||
ulong size)
|
||||
{
|
||||
_owner = owner;
|
||||
_block = block;
|
||||
Memory = memory;
|
||||
HostPointer = hostPointer;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public MemoryAllocation(
|
||||
HostMemoryAllocator hostMemory,
|
||||
DeviceMemory memory,
|
||||
nint hostPointer,
|
||||
ulong offset,
|
||||
ulong size)
|
||||
{
|
||||
_hostMemory = hostMemory;
|
||||
Memory = memory;
|
||||
HostPointer = hostPointer;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_hostMemory != null)
|
||||
{
|
||||
_hostMemory.Free(Memory, Offset, Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
_owner.Free(_block, Offset, Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
118
src/Ryujinx.Graphics.Rdna3Vulkan/MemoryAllocator.cs
Normal file
118
src/Ryujinx.Graphics.Rdna3Vulkan/MemoryAllocator.cs
Normal file
|
@ -0,0 +1,118 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class MemoryAllocator : IDisposable
|
||||
{
|
||||
private const 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;
|
||||
private readonly ReaderWriterLockSlim _lock;
|
||||
|
||||
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 / _physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount);
|
||||
_lock = new(LockRecursionPolicy.NoRecursion);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < _blockLists.Count; i++)
|
||||
{
|
||||
var bl = _blockLists[i];
|
||||
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
|
||||
{
|
||||
return bl.Allocate(size, alignment, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
|
||||
_lock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer);
|
||||
_blockLists.Add(newBl);
|
||||
|
||||
return newBl.Allocate(size, alignment, map);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
310
src/Ryujinx.Graphics.Rdna3Vulkan/MemoryAllocatorBlockList.cs
Normal file
310
src/Ryujinx.Graphics.Rdna3Vulkan/MemoryAllocatorBlockList.cs
Normal file
|
@ -0,0 +1,310 @@
|
|||
using Ryujinx.Common;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class MemoryAllocatorBlockList : IDisposable
|
||||
{
|
||||
private const ulong InvalidOffset = ulong.MaxValue;
|
||||
|
||||
public class Block : IComparable<Block>
|
||||
{
|
||||
public DeviceMemory Memory { get; private set; }
|
||||
public nint HostPointer { get; private set; }
|
||||
public ulong Size { get; }
|
||||
public bool Mapped => HostPointer != nint.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, nint hostPointer, ulong size)
|
||||
{
|
||||
Memory = memory;
|
||||
HostPointer = hostPointer;
|
||||
Size = size;
|
||||
_freeRanges = new List<Range>
|
||||
{
|
||||
new Range(0, size),
|
||||
};
|
||||
}
|
||||
|
||||
public ulong Allocate(ulong size, ulong alignment)
|
||||
{
|
||||
for (int i = 0; i < _freeRanges.Count; i++)
|
||||
{
|
||||
var range = _freeRanges[i];
|
||||
|
||||
ulong alignedOffset = BitUtils.AlignUp(range.Offset, 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 = nint.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;
|
||||
|
||||
private readonly ReaderWriterLockSlim _lock;
|
||||
|
||||
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;
|
||||
_lock = new(LockRecursionPolicy.NoRecursion);
|
||||
}
|
||||
|
||||
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}.");
|
||||
}
|
||||
|
||||
_lock.EnterReadLock();
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
|
||||
ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment);
|
||||
|
||||
var memoryAllocateInfo = new MemoryAllocateInfo
|
||||
{
|
||||
SType = StructureType.MemoryAllocateInfo,
|
||||
AllocationSize = blockAlignedSize,
|
||||
MemoryTypeIndex = (uint)MemoryTypeIndex,
|
||||
};
|
||||
|
||||
_api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory).ThrowOnError();
|
||||
|
||||
nint hostPointer = nint.Zero;
|
||||
|
||||
if (map)
|
||||
{
|
||||
void* pointer = null;
|
||||
_api.MapMemory(_device, deviceMemory, 0, blockAlignedSize, 0, ref pointer).ThrowOnError();
|
||||
hostPointer = (nint)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 nint GetHostPointer(Block block, ulong offset)
|
||||
{
|
||||
if (block.HostPointer == nint.Zero)
|
||||
{
|
||||
return nint.Zero;
|
||||
}
|
||||
|
||||
return (nint)((nuint)block.HostPointer + offset);
|
||||
}
|
||||
|
||||
public void Free(Block block, ulong offset, ulong size)
|
||||
{
|
||||
block.Free(offset, size);
|
||||
|
||||
if (block.IsTotallyFree())
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < _blocks.Count; i++)
|
||||
{
|
||||
if (_blocks[i] == block)
|
||||
{
|
||||
_blocks.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
block.Destroy(_api, _device);
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertBlock(Block block)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
|
||||
try
|
||||
{
|
||||
int index = _blocks.BinarySearch(block);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
_blocks.Insert(index, block);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < _blocks.Count; i++)
|
||||
{
|
||||
_blocks[i].Destroy(_api, _device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
267
src/Ryujinx.Graphics.Rdna3Vulkan/MultiFenceHolder.cs
Normal file
267
src/Ryujinx.Graphics.Rdna3Vulkan/MultiFenceHolder.cs
Normal file
|
@ -0,0 +1,267 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
/// <summary>
|
||||
/// Holder for multiple host GPU fences.
|
||||
/// </summary>
|
||||
class MultiFenceHolder
|
||||
{
|
||||
private const int BufferUsageTrackingGranularity = 4096;
|
||||
|
||||
private readonly FenceHolder[] _fences;
|
||||
private readonly BufferUsageBitmap _bufferUsageBitmap;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the multiple fence holder.
|
||||
/// </summary>
|
||||
public MultiFenceHolder()
|
||||
{
|
||||
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
|
||||
}
|
||||
|
||||
/// <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 FenceHolder[CommandBufferPool.MaxCommandBuffers];
|
||||
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds read/write 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>
|
||||
/// <param name="write">Whether the access is a write or not</param>
|
||||
public void AddBufferUse(int cbIndex, int offset, int size, bool write)
|
||||
{
|
||||
_bufferUsageBitmap.Add(cbIndex, offset, size, false);
|
||||
|
||||
if (write)
|
||||
{
|
||||
_bufferUsageBitmap.Add(cbIndex, offset, size, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="write">True if only write usages should count</param>
|
||||
/// <returns>True if in use, false otherwise</returns>
|
||||
public bool IsBufferRangeInUse(int offset, int size, bool write)
|
||||
{
|
||||
return _bufferUsageBitmap.OverlapsWith(offset, size, write);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <returns>True if the command buffer's previous fence value was null</returns>
|
||||
public bool AddFence(int cbIndex, FenceHolder fence)
|
||||
{
|
||||
ref FenceHolder fenceRef = ref _fences[cbIndex];
|
||||
|
||||
if (fenceRef == null)
|
||||
{
|
||||
fenceRef = fence;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a fence from the holder.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
|
||||
public void RemoveFence(int cbIndex)
|
||||
{
|
||||
_fences[cbIndex] = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a fence referenced on the given command buffer.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Index of the command buffer to check if it's used</param>
|
||||
/// <returns>True if referenced, false otherwise</returns>
|
||||
public bool HasFence(int cbIndex)
|
||||
{
|
||||
return _fences[cbIndex] != null;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
using SpanOwner<FenceHolder> fenceHoldersOwner = SpanOwner<FenceHolder>.Rent(CommandBufferPool.MaxCommandBuffers);
|
||||
Span<FenceHolder> fenceHolders = fenceHoldersOwner.Span;
|
||||
|
||||
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
|
||||
Span<Fence> fences = stackalloc Fence[count];
|
||||
|
||||
int fenceCount = 0;
|
||||
|
||||
for (int i = 0; i < fences.Length; i++)
|
||||
{
|
||||
if (fenceHolders[i].TryGet(out Fence fence))
|
||||
{
|
||||
fences[fenceCount] = fence;
|
||||
|
||||
if (fenceCount < i)
|
||||
{
|
||||
fenceHolders[fenceCount] = fenceHolders[i];
|
||||
}
|
||||
|
||||
fenceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fenceCount == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool signaled = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (hasTimeout)
|
||||
{
|
||||
signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
for (int i = 0; i < fenceCount; i++)
|
||||
{
|
||||
fenceHolders[i].PutLock();
|
||||
}
|
||||
}
|
||||
|
||||
return signaled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets fences to wait for.
|
||||
/// </summary>
|
||||
/// <param name="storage">Span to store fences in</param>
|
||||
/// <returns>Number of fences placed in storage</returns>
|
||||
private int GetFences(Span<FenceHolder> storage)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < _fences.Length; i++)
|
||||
{
|
||||
var fence = _fences[i];
|
||||
|
||||
if (fence != null)
|
||||
{
|
||||
storage[count++] = fence;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets fences to wait for use of a given buffer region.
|
||||
/// </summary>
|
||||
/// <param name="storage">Span to store overlapping fences in</param>
|
||||
/// <param name="offset">Offset of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>Number of fences for the specified region placed in storage</returns>
|
||||
private int GetOverlappingFences(Span<FenceHolder> storage, int offset, int size)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < _fences.Length; i++)
|
||||
{
|
||||
var fence = _fences[i];
|
||||
|
||||
if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size))
|
||||
{
|
||||
storage[count++] = fence;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
48
src/Ryujinx.Graphics.Rdna3Vulkan/NativeArray.cs
Normal file
48
src/Ryujinx.Graphics.Rdna3Vulkan/NativeArray.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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((nint)Pointer);
|
||||
Pointer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
97
src/Ryujinx.Graphics.Rdna3Vulkan/PersistentFlushBuffer.cs
Normal file
97
src/Ryujinx.Graphics.Rdna3Vulkan/PersistentFlushBuffer.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
internal class PersistentFlushBuffer : IDisposable
|
||||
{
|
||||
private readonly 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)
|
||||
{
|
||||
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);
|
||||
Auto<DisposableBuffer> srcBuffer;
|
||||
|
||||
using (var cbs = cbp.Rent())
|
||||
{
|
||||
srcBuffer = buffer.GetBuffer(cbs.CommandBuffer);
|
||||
var dstBuffer = flushStorage.GetBuffer(cbs.CommandBuffer);
|
||||
|
||||
if (srcBuffer.TryIncrementReferenceCount())
|
||||
{
|
||||
BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Source buffer is no longer alive, don't copy anything to flush storage.
|
||||
srcBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
flushStorage.WaitForFences();
|
||||
srcBuffer?.DecrementReferenceCount();
|
||||
return flushStorage.GetDataStorage(0, size);
|
||||
}
|
||||
|
||||
public Span<byte> GetTextureData(CommandBufferPool cbp, TextureView view, int size)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
1810
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineBase.cs
Normal file
1810
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineBase.cs
Normal file
File diff suppressed because it is too large
Load diff
336
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineConverter.cs
Normal file
336
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineConverter.cs
Normal file
|
@ -0,0 +1,336 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Format = Silk.NET.Vulkan.Format;
|
||||
using PolygonMode = Silk.NET.Vulkan.PolygonMode;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
static class PipelineConverter
|
||||
{
|
||||
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
|
||||
{
|
||||
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
||||
|
||||
AttachmentDescription[] attachmentDescs = null;
|
||||
|
||||
var subpass = new SubpassDescription
|
||||
{
|
||||
PipelineBindPoint = PipelineBindPoint.Graphics,
|
||||
};
|
||||
|
||||
AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
|
||||
|
||||
Span<int> attachmentIndices = stackalloc int[MaxAttachments];
|
||||
Span<Format> attachmentFormats = stackalloc Format[MaxAttachments];
|
||||
|
||||
int attachmentCount = 0;
|
||||
int colorCount = 0;
|
||||
int maxColorAttachmentIndex = -1;
|
||||
|
||||
bool isNotMsOrSupportsStorage = gd.Capabilities.SupportsShaderStorageImageMultisample ||
|
||||
!state.DepthStencilFormat.IsImageCompatible();
|
||||
|
||||
for (int i = 0; i < state.AttachmentEnable.Length; i++)
|
||||
{
|
||||
if (state.AttachmentEnable[i])
|
||||
{
|
||||
bool isNotMsOrSupportsStorageAttachments = gd.Capabilities.SupportsShaderStorageImageMultisample ||
|
||||
!state.AttachmentFormats[i].IsImageCompatible();
|
||||
|
||||
attachmentFormats[attachmentCount] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i], isNotMsOrSupportsStorageAttachments);
|
||||
|
||||
attachmentIndices[attachmentCount++] = i;
|
||||
colorCount++;
|
||||
maxColorAttachmentIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.DepthStencilEnable)
|
||||
{
|
||||
attachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat, isNotMsOrSupportsStorage);
|
||||
}
|
||||
|
||||
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(gd);
|
||||
|
||||
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, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||
|
||||
return new DisposableRenderPass(gd.Api, device, renderPass);
|
||||
}
|
||||
}
|
||||
|
||||
public static SubpassDependency CreateSubpassDependency(VulkanRenderer gd)
|
||||
{
|
||||
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||
|
||||
return new SubpassDependency(
|
||||
0,
|
||||
0,
|
||||
stages,
|
||||
stages,
|
||||
access,
|
||||
access,
|
||||
0);
|
||||
}
|
||||
|
||||
public unsafe static SubpassDependency2 CreateSubpassDependency2(VulkanRenderer gd)
|
||||
{
|
||||
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||
|
||||
return new SubpassDependency2(
|
||||
StructureType.SubpassDependency2,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
stages,
|
||||
stages,
|
||||
access,
|
||||
access,
|
||||
0);
|
||||
}
|
||||
|
||||
public static PipelineState ToVulkanPipelineState(this ProgramPipelineState state, VulkanRenderer gd)
|
||||
{
|
||||
PipelineState pipeline = new();
|
||||
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.DepthMode = state.DepthMode == DepthMode.MinusOneToOne;
|
||||
|
||||
pipeline.FrontFace = state.FrontFace.Convert();
|
||||
|
||||
pipeline.HasDepthStencil = state.DepthStencilEnable;
|
||||
pipeline.LineWidth = state.LineWidth;
|
||||
pipeline.LogicOpEnable = state.LogicOpEnable;
|
||||
pipeline.LogicOp = state.LogicOp.Convert();
|
||||
|
||||
pipeline.PatchControlPoints = state.PatchControlPoints;
|
||||
pipeline.PolygonMode = 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.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
|
||||
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
|
||||
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
|
||||
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
|
||||
|
||||
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;
|
||||
uint attachmentIntegerFormatMask = 0;
|
||||
bool allFormatsFloatOrSrgb = true;
|
||||
|
||||
for (int i = 0; i < Constants.MaxRenderTargets; i++)
|
||||
{
|
||||
if (state.AttachmentEnable[i])
|
||||
{
|
||||
bool isNotMsOrSupportsStorage = gd.Capabilities.SupportsShaderStorageImageMultisample ||
|
||||
!state.AttachmentFormats[i].IsImageCompatible();
|
||||
|
||||
pipeline.Internal.AttachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.AttachmentFormats[i], isNotMsOrSupportsStorage);
|
||||
maxColorAttachmentIndex = i;
|
||||
|
||||
if (state.AttachmentFormats[i].IsInteger())
|
||||
{
|
||||
attachmentIntegerFormatMask |= 1u << i;
|
||||
}
|
||||
|
||||
allFormatsFloatOrSrgb &= state.AttachmentFormats[i].IsFloatOrSrgb();
|
||||
}
|
||||
}
|
||||
|
||||
if (state.DepthStencilEnable)
|
||||
{
|
||||
bool isNotMsOrSupportsStorage = !state.DepthStencilFormat.IsImageCompatible() ||
|
||||
gd.Capabilities.SupportsShaderStorageImageMultisample;
|
||||
|
||||
pipeline.Internal.AttachmentFormats[attachmentCount++] = gd.FormatCapabilities.ConvertToVkFormat(state.DepthStencilFormat, isNotMsOrSupportsStorage);
|
||||
}
|
||||
|
||||
pipeline.ColorBlendAttachmentStateCount = (uint)(maxColorAttachmentIndex + 1);
|
||||
pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
||||
pipeline.Internal.AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
|
||||
pipeline.Internal.LogicOpsAllowed = attachmentCount == 0 || !allFormatsFloatOrSrgb;
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
}
|
203
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineDynamicState.cs
Normal file
203
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineDynamicState.cs
Normal file
|
@ -0,0 +1,203 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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;
|
||||
|
||||
private FeedbackLoopAspects _feedbackLoopAspects;
|
||||
|
||||
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,
|
||||
FeedbackLoop = 1 << 5,
|
||||
All = Blend | DepthBias | Scissor | Stencil | Viewport | FeedbackLoop,
|
||||
}
|
||||
|
||||
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 SetFeedbackLoop(FeedbackLoopAspects aspects)
|
||||
{
|
||||
_feedbackLoopAspects = aspects;
|
||||
|
||||
_dirty |= DirtyFlags.FeedbackLoop;
|
||||
}
|
||||
|
||||
public void ForceAllDirty()
|
||||
{
|
||||
_dirty = DirtyFlags.All;
|
||||
}
|
||||
|
||||
public void ReplayIfDirty(VulkanRenderer gd, CommandBuffer commandBuffer)
|
||||
{
|
||||
Vk api = gd.Api;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (_dirty.HasFlag(DirtyFlags.FeedbackLoop) && gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
|
||||
{
|
||||
RecordFeedbackLoop(gd.DynamicFeedbackLoopApi, commandBuffer);
|
||||
}
|
||||
|
||||
_dirty = DirtyFlags.None;
|
||||
}
|
||||
|
||||
private void RecordBlend(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetBlendConstants(commandBuffer, _blendConstants.AsSpan());
|
||||
}
|
||||
|
||||
private readonly void RecordDepthBias(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
api.CmdSetDepthBias(commandBuffer, _depthBiasConstantFactor, _depthBiasClamp, _depthBiasSlopeFactor);
|
||||
}
|
||||
|
||||
private void RecordScissor(Vk api, CommandBuffer commandBuffer)
|
||||
{
|
||||
if (ScissorsCount != 0)
|
||||
{
|
||||
api.CmdSetScissor(commandBuffer, 0, (uint)ScissorsCount, _scissors.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
private readonly 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());
|
||||
}
|
||||
}
|
||||
|
||||
private readonly void RecordFeedbackLoop(ExtAttachmentFeedbackLoopDynamicState api, CommandBuffer commandBuffer)
|
||||
{
|
||||
ImageAspectFlags aspects = (_feedbackLoopAspects & FeedbackLoopAspects.Color) != 0 ? ImageAspectFlags.ColorBit : 0;
|
||||
|
||||
if ((_feedbackLoopAspects & FeedbackLoopAspects.Depth) != 0)
|
||||
{
|
||||
aspects |= ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit;
|
||||
}
|
||||
|
||||
api.CmdSetAttachmentFeedbackLoopEnable(commandBuffer, aspects);
|
||||
}
|
||||
}
|
||||
}
|
351
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineFull.cs
Normal file
351
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineFull.cs
Normal file
|
@ -0,0 +1,351 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Rdna3Vulkan.Queries;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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 readonly List<BufferHolder> _activeBufferMirrors;
|
||||
|
||||
private ulong _byteWeight;
|
||||
|
||||
private readonly List<BufferHolder> _backingSwaps;
|
||||
|
||||
public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device)
|
||||
{
|
||||
_activeQueries = new List<(QueryPool, bool)>();
|
||||
_pendingQueryCopies = new();
|
||||
_backingSwaps = new();
|
||||
_activeBufferMirrors = new();
|
||||
|
||||
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
||||
|
||||
IsMainPipeline = true;
|
||||
}
|
||||
|
||||
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 || Gd.IsQualcommProprietary)
|
||||
{
|
||||
// We can't use CmdClearAttachments if not writing all components,
|
||||
// because on Vulkan, the pipeline state does not affect clears.
|
||||
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||
var dstTexture = FramebufferParams.GetColorView(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.GetAttachmentComponentType(index),
|
||||
ClearScissor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearRenderTargetColor(index, layer, layerCount, color);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
||||
{
|
||||
if (FramebufferParams == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((stencilMask != 0 && stencilMask != 0xff) || Gd.IsQualcommProprietary)
|
||||
{
|
||||
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
|
||||
// because on Vulkan, the pipeline state does not affect clears.
|
||||
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||
var dstTexture = FramebufferParams.GetDepthStencilView();
|
||||
if (dstTexture == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Clear only the specified layer.
|
||||
Gd.HelperShader.Clear(
|
||||
Gd,
|
||||
dstTexture,
|
||||
depthValue,
|
||||
depthMask,
|
||||
stencilValue,
|
||||
stencilMask,
|
||||
(int)FramebufferParams.Width,
|
||||
(int)FramebufferParams.Height,
|
||||
FramebufferParams.AttachmentFormats[FramebufferParams.AttachmentsCount - 1],
|
||||
ClearScissor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearRenderTargetDepthStencil(layer, layerCount, depthValue, depthMask, stencilValue, stencilMask != 0);
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
PreloadCbs ??= Gd.CommandBufferPool.Rent();
|
||||
|
||||
return PreloadCbs.Value;
|
||||
}
|
||||
|
||||
public void FlushCommandsIfWeightExceeding(IAuto disposedResource, ulong byteWeight)
|
||||
{
|
||||
bool usedByCurrentCb = disposedResource.HasCommandBufferDependency(Cbs);
|
||||
|
||||
if (PreloadCbs != null && !usedByCurrentCb)
|
||||
{
|
||||
usedByCurrentCb = disposedResource.HasCommandBufferDependency(PreloadCbs.Value);
|
||||
}
|
||||
|
||||
if (usedByCurrentCb)
|
||||
{
|
||||
// Since we can only free memory after the command buffer that uses a given resource was executed,
|
||||
// keeping the command buffer might cause a high amount of memory to be in use.
|
||||
// To prevent that, we force submit command buffers if the memory usage by resources
|
||||
// in use by the current command buffer is above a given limit, and those resources were disposed.
|
||||
_byteWeight += byteWeight;
|
||||
|
||||
if (_byteWeight >= MinByteWeightForFlush)
|
||||
{
|
||||
FlushCommandsImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Restore()
|
||||
{
|
||||
if (Pipeline != null)
|
||||
{
|
||||
Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value);
|
||||
}
|
||||
|
||||
SignalCommandBufferChange();
|
||||
|
||||
if (Pipeline != null && Pbp == PipelineBindPoint.Graphics)
|
||||
{
|
||||
DynamicState.ReplayIfDirty(Gd, 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;
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs, false, null, null);
|
||||
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||
Gd.RegisterFlush();
|
||||
|
||||
// Restore per-command buffer state.
|
||||
foreach (BufferHolder buffer in _activeBufferMirrors)
|
||||
{
|
||||
buffer.ClearMirrors();
|
||||
}
|
||||
|
||||
_activeBufferMirrors.Clear();
|
||||
|
||||
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();
|
||||
|
||||
Restore();
|
||||
}
|
||||
|
||||
public void RegisterActiveMirror(BufferHolder buffer)
|
||||
{
|
||||
_activeBufferMirrors.Add(buffer);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
54
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineHelperShader.cs
Normal file
54
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineHelperShader.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using VkFormat = Silk.NET.Vulkan.Format;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class PipelineHelperShader : PipelineBase
|
||||
{
|
||||
public PipelineHelperShader(VulkanRenderer gd, Device device) : base(gd, device)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetRenderTarget(TextureView view, uint width, uint height)
|
||||
{
|
||||
CreateFramebuffer(view, width, height);
|
||||
CreateRenderPass();
|
||||
SignalStateChange();
|
||||
}
|
||||
|
||||
private void CreateFramebuffer(TextureView view, uint width, uint height)
|
||||
{
|
||||
FramebufferParams = new FramebufferParams(Device, view, width, height);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
107
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineLayoutCache.cs
Normal file
107
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineLayoutCache.cs
Normal file
|
@ -0,0 +1,107 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class PipelineLayoutCache
|
||||
{
|
||||
private readonly struct PlceKey : IEquatable<PlceKey>
|
||||
{
|
||||
public readonly ReadOnlyCollection<ResourceDescriptorCollection> SetDescriptors;
|
||||
public readonly bool UsePushDescriptors;
|
||||
|
||||
public PlceKey(ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, bool usePushDescriptors)
|
||||
{
|
||||
SetDescriptors = setDescriptors;
|
||||
UsePushDescriptors = usePushDescriptors;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
HashCode hasher = new();
|
||||
|
||||
if (SetDescriptors != null)
|
||||
{
|
||||
foreach (var setDescriptor in SetDescriptors)
|
||||
{
|
||||
hasher.Add(setDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
hasher.Add(UsePushDescriptors);
|
||||
|
||||
return hasher.ToHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is PlceKey other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(PlceKey other)
|
||||
{
|
||||
if ((SetDescriptors == null) != (other.SetDescriptors == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SetDescriptors != null)
|
||||
{
|
||||
if (SetDescriptors.Count != other.SetDescriptors.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < SetDescriptors.Count; index++)
|
||||
{
|
||||
if (!SetDescriptors[index].Equals(other.SetDescriptors[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UsePushDescriptors == other.UsePushDescriptors;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<PlceKey, PipelineLayoutCacheEntry> _plces;
|
||||
|
||||
public PipelineLayoutCache()
|
||||
{
|
||||
_plces = new ConcurrentDictionary<PlceKey, PipelineLayoutCacheEntry>();
|
||||
}
|
||||
|
||||
public PipelineLayoutCacheEntry GetOrCreate(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
|
||||
bool usePushDescriptors)
|
||||
{
|
||||
var key = new PlceKey(setDescriptors, usePushDescriptors);
|
||||
|
||||
return _plces.GetOrAdd(key, newKey => new PipelineLayoutCacheEntry(gd, device, setDescriptors, usePushDescriptors));
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (var plce in _plces.Values)
|
||||
{
|
||||
plce.Dispose();
|
||||
}
|
||||
|
||||
_plces.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
383
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineLayoutCacheEntry.cs
Normal file
383
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineLayoutCacheEntry.cs
Normal file
|
@ -0,0 +1,383 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class PipelineLayoutCacheEntry
|
||||
{
|
||||
private const int MaxPoolSizesPerSet = 8;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
|
||||
public DescriptorSetLayout[] DescriptorSetLayouts { get; }
|
||||
public bool[] DescriptorSetLayoutsUpdateAfterBind { get; }
|
||||
public PipelineLayout PipelineLayout { get; }
|
||||
|
||||
private readonly int[] _consumedDescriptorsPerSet;
|
||||
private readonly DescriptorPoolSize[][] _poolSizes;
|
||||
|
||||
private readonly DescriptorSetManager _descriptorSetManager;
|
||||
|
||||
private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
|
||||
private List<Auto<DescriptorSetCollection>>[] _currentDsCache;
|
||||
private readonly int[] _dsCacheCursor;
|
||||
private int _dsLastCbIndex;
|
||||
private int _dsLastSubmissionCount;
|
||||
|
||||
private struct ManualDescriptorSetEntry
|
||||
{
|
||||
public Auto<DescriptorSetCollection> DescriptorSet;
|
||||
public uint CbRefMask;
|
||||
public bool InUse;
|
||||
|
||||
public ManualDescriptorSetEntry(Auto<DescriptorSetCollection> descriptorSet, int cbIndex)
|
||||
{
|
||||
DescriptorSet = descriptorSet;
|
||||
CbRefMask = 1u << cbIndex;
|
||||
InUse = true;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct PendingManualDsConsumption
|
||||
{
|
||||
public FenceHolder Fence { get; }
|
||||
public int CommandBufferIndex { get; }
|
||||
public int SetIndex { get; }
|
||||
public int CacheIndex { get; }
|
||||
|
||||
public PendingManualDsConsumption(FenceHolder fence, int commandBufferIndex, int setIndex, int cacheIndex)
|
||||
{
|
||||
Fence = fence;
|
||||
CommandBufferIndex = commandBufferIndex;
|
||||
SetIndex = setIndex;
|
||||
CacheIndex = cacheIndex;
|
||||
fence.Get();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<ManualDescriptorSetEntry>[] _manualDsCache;
|
||||
private readonly Queue<PendingManualDsConsumption> _pendingManualDsConsumptions;
|
||||
private readonly Queue<int>[] _freeManualDsCacheEntries;
|
||||
|
||||
private readonly Dictionary<long, DescriptorSetTemplate> _pdTemplates;
|
||||
private readonly ResourceDescriptorCollection _pdDescriptors;
|
||||
private long _lastPdUsage;
|
||||
private DescriptorSetTemplate _lastPdTemplate;
|
||||
|
||||
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
|
||||
{
|
||||
_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>>[setsCount];
|
||||
|
||||
for (int j = 0; j < _dsCache[i].Length; j++)
|
||||
{
|
||||
_dsCache[i][j] = new List<Auto<DescriptorSetCollection>>();
|
||||
}
|
||||
}
|
||||
|
||||
_dsCacheCursor = new int[setsCount];
|
||||
_manualDsCache = new List<ManualDescriptorSetEntry>[setsCount];
|
||||
_pendingManualDsConsumptions = new Queue<PendingManualDsConsumption>();
|
||||
_freeManualDsCacheEntries = new Queue<int>[setsCount];
|
||||
}
|
||||
|
||||
public PipelineLayoutCacheEntry(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
|
||||
bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
|
||||
{
|
||||
ResourceLayouts layouts = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
|
||||
|
||||
DescriptorSetLayouts = layouts.DescriptorSetLayouts;
|
||||
DescriptorSetLayoutsUpdateAfterBind = layouts.DescriptorSetLayoutsUpdateAfterBind;
|
||||
PipelineLayout = layouts.PipelineLayout;
|
||||
|
||||
_consumedDescriptorsPerSet = new int[setDescriptors.Count];
|
||||
_poolSizes = new DescriptorPoolSize[setDescriptors.Count][];
|
||||
|
||||
Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet];
|
||||
|
||||
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (var descriptor in setDescriptors[setIndex].Descriptors)
|
||||
{
|
||||
count += descriptor.Count;
|
||||
}
|
||||
|
||||
_consumedDescriptorsPerSet[setIndex] = count;
|
||||
_poolSizes[setIndex] = GetDescriptorPoolSizes(poolSizes, setDescriptors[setIndex], DescriptorSetManager.MaxSets).ToArray();
|
||||
}
|
||||
|
||||
if (usePushDescriptors)
|
||||
{
|
||||
_pdDescriptors = setDescriptors[0];
|
||||
_pdTemplates = new();
|
||||
}
|
||||
|
||||
_descriptorSetManager = new DescriptorSetManager(_device, setDescriptors.Count);
|
||||
}
|
||||
|
||||
public void UpdateCommandBufferIndex(int commandBufferIndex)
|
||||
{
|
||||
int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex);
|
||||
|
||||
if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount)
|
||||
{
|
||||
_dsLastCbIndex = commandBufferIndex;
|
||||
_dsLastSubmissionCount = submissionCount;
|
||||
Array.Clear(_dsCacheCursor);
|
||||
}
|
||||
|
||||
_currentDsCache = _dsCache[commandBufferIndex];
|
||||
}
|
||||
|
||||
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
|
||||
{
|
||||
var list = _currentDsCache[setIndex];
|
||||
int index = _dsCacheCursor[setIndex]++;
|
||||
if (index == list.Count)
|
||||
{
|
||||
var dsc = _descriptorSetManager.AllocateDescriptorSet(
|
||||
_gd.Api,
|
||||
DescriptorSetLayouts[setIndex],
|
||||
_poolSizes[setIndex],
|
||||
setIndex,
|
||||
_consumedDescriptorsPerSet[setIndex],
|
||||
DescriptorSetLayoutsUpdateAfterBind[setIndex]);
|
||||
|
||||
list.Add(dsc);
|
||||
isNew = true;
|
||||
return dsc;
|
||||
}
|
||||
|
||||
isNew = false;
|
||||
return list[index];
|
||||
}
|
||||
|
||||
public Auto<DescriptorSetCollection> GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex)
|
||||
{
|
||||
FreeCompletedManualDescriptorSets();
|
||||
|
||||
var list = _manualDsCache[setIndex] ??= new();
|
||||
var span = CollectionsMarshal.AsSpan(list);
|
||||
|
||||
Queue<int> freeQueue = _freeManualDsCacheEntries[setIndex];
|
||||
|
||||
// Do we have at least one freed descriptor set? If so, just use that.
|
||||
if (freeQueue != null && freeQueue.TryDequeue(out int freeIndex))
|
||||
{
|
||||
ref ManualDescriptorSetEntry entry = ref span[freeIndex];
|
||||
|
||||
Debug.Assert(!entry.InUse && entry.CbRefMask == 0);
|
||||
|
||||
entry.InUse = true;
|
||||
entry.CbRefMask = 1u << cbs.CommandBufferIndex;
|
||||
cacheIndex = freeIndex;
|
||||
|
||||
_pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, freeIndex));
|
||||
|
||||
return entry.DescriptorSet;
|
||||
}
|
||||
|
||||
// Otherwise create a new descriptor set, and add to our pending queue for command buffer consumption tracking.
|
||||
var dsc = _descriptorSetManager.AllocateDescriptorSet(
|
||||
_gd.Api,
|
||||
DescriptorSetLayouts[setIndex],
|
||||
_poolSizes[setIndex],
|
||||
setIndex,
|
||||
_consumedDescriptorsPerSet[setIndex],
|
||||
DescriptorSetLayoutsUpdateAfterBind[setIndex]);
|
||||
|
||||
cacheIndex = list.Count;
|
||||
list.Add(new ManualDescriptorSetEntry(dsc, cbs.CommandBufferIndex));
|
||||
_pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex));
|
||||
|
||||
return dsc;
|
||||
}
|
||||
|
||||
public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex)
|
||||
{
|
||||
FreeCompletedManualDescriptorSets();
|
||||
|
||||
var list = _manualDsCache[setIndex];
|
||||
var span = CollectionsMarshal.AsSpan(list);
|
||||
ref var entry = ref span[cacheIndex];
|
||||
|
||||
uint cbMask = 1u << cbs.CommandBufferIndex;
|
||||
|
||||
if ((entry.CbRefMask & cbMask) == 0)
|
||||
{
|
||||
entry.CbRefMask |= cbMask;
|
||||
|
||||
_pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex));
|
||||
}
|
||||
}
|
||||
|
||||
private void FreeCompletedManualDescriptorSets()
|
||||
{
|
||||
FenceHolder signalledFence = null;
|
||||
while (_pendingManualDsConsumptions.TryPeek(out var pds) && (pds.Fence == signalledFence || pds.Fence.IsSignaled()))
|
||||
{
|
||||
signalledFence = pds.Fence; // Already checked - don't need to do it again.
|
||||
var dequeued = _pendingManualDsConsumptions.Dequeue();
|
||||
Debug.Assert(dequeued.Fence == pds.Fence);
|
||||
pds.Fence.Put();
|
||||
|
||||
var span = CollectionsMarshal.AsSpan(_manualDsCache[dequeued.SetIndex]);
|
||||
ref var entry = ref span[dequeued.CacheIndex];
|
||||
entry.CbRefMask &= ~(1u << dequeued.CommandBufferIndex);
|
||||
|
||||
if (!entry.InUse && entry.CbRefMask == 0)
|
||||
{
|
||||
// If not in use by any array, and not bound to any command buffer, the descriptor set can be re-used immediately.
|
||||
(_freeManualDsCacheEntries[dequeued.SetIndex] ??= new()).Enqueue(dequeued.CacheIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex)
|
||||
{
|
||||
var list = _manualDsCache[setIndex];
|
||||
var span = CollectionsMarshal.AsSpan(list);
|
||||
|
||||
span[cacheIndex].InUse = false;
|
||||
|
||||
if (span[cacheIndex].CbRefMask == 0)
|
||||
{
|
||||
// This is no longer in use by any array, so if not bound to any command buffer, the descriptor set can be re-used immediately.
|
||||
(_freeManualDsCacheEntries[setIndex] ??= new()).Enqueue(cacheIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, ResourceDescriptorCollection setDescriptor, uint multiplier)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int index = 0; index < setDescriptor.Descriptors.Count; index++)
|
||||
{
|
||||
ResourceDescriptor descriptor = setDescriptor.Descriptors[index];
|
||||
DescriptorType descriptorType = descriptor.Type.Convert();
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (int poolSizeIndex = 0; poolSizeIndex < count; poolSizeIndex++)
|
||||
{
|
||||
if (output[poolSizeIndex].Type == descriptorType)
|
||||
{
|
||||
output[poolSizeIndex].DescriptorCount += (uint)descriptor.Count * multiplier;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
output[count++] = new DescriptorPoolSize()
|
||||
{
|
||||
Type = descriptorType,
|
||||
DescriptorCount = (uint)descriptor.Count,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return output[..count];
|
||||
}
|
||||
|
||||
public DescriptorSetTemplate GetPushDescriptorTemplate(PipelineBindPoint pbp, long updateMask)
|
||||
{
|
||||
if (_lastPdUsage == updateMask && _lastPdTemplate != null)
|
||||
{
|
||||
// Most likely result is that it asks to update the same buffers.
|
||||
return _lastPdTemplate;
|
||||
}
|
||||
|
||||
if (!_pdTemplates.TryGetValue(updateMask, out DescriptorSetTemplate template))
|
||||
{
|
||||
template = new DescriptorSetTemplate(_gd, _device, _pdDescriptors, updateMask, this, pbp, 0);
|
||||
|
||||
_pdTemplates.Add(updateMask, template);
|
||||
}
|
||||
|
||||
_lastPdUsage = updateMask;
|
||||
_lastPdTemplate = template;
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
protected virtual unsafe void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (_pdTemplates != null)
|
||||
{
|
||||
foreach (DescriptorSetTemplate template in _pdTemplates.Values)
|
||||
{
|
||||
template.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _manualDsCache.Length; i++)
|
||||
{
|
||||
if (_manualDsCache[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < _manualDsCache[i].Count; j++)
|
||||
{
|
||||
_manualDsCache[i][j].DescriptorSet.Dispose();
|
||||
}
|
||||
|
||||
_manualDsCache[i].Clear();
|
||||
}
|
||||
|
||||
_gd.Api.DestroyPipelineLayout(_device, PipelineLayout, null);
|
||||
|
||||
for (int i = 0; i < DescriptorSetLayouts.Length; i++)
|
||||
{
|
||||
_gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null);
|
||||
}
|
||||
|
||||
while (_pendingManualDsConsumptions.TryDequeue(out var pds))
|
||||
{
|
||||
pds.Fence.Put();
|
||||
}
|
||||
|
||||
_descriptorSetManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
115
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineLayoutFactory.cs
Normal file
115
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineLayoutFactory.cs
Normal file
|
@ -0,0 +1,115 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
record struct ResourceLayouts(DescriptorSetLayout[] DescriptorSetLayouts, bool[] DescriptorSetLayoutsUpdateAfterBind, PipelineLayout PipelineLayout);
|
||||
|
||||
static class PipelineLayoutFactory
|
||||
{
|
||||
public static unsafe ResourceLayouts Create(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
|
||||
bool usePushDescriptors)
|
||||
{
|
||||
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
|
||||
bool[] updateAfterBindFlags = new bool[setDescriptors.Count];
|
||||
|
||||
bool isMoltenVk = gd.IsMoltenVk;
|
||||
|
||||
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
|
||||
{
|
||||
ResourceDescriptorCollection rdc = setDescriptors[setIndex];
|
||||
|
||||
ResourceStages activeStages = ResourceStages.None;
|
||||
|
||||
if (isMoltenVk)
|
||||
{
|
||||
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
|
||||
{
|
||||
activeStages |= rdc.Descriptors[descIndex].Stages;
|
||||
}
|
||||
}
|
||||
|
||||
DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count];
|
||||
|
||||
bool hasArray = false;
|
||||
|
||||
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
|
||||
{
|
||||
ResourceDescriptor descriptor = rdc.Descriptors[descIndex];
|
||||
ResourceStages stages = descriptor.Stages;
|
||||
|
||||
if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk)
|
||||
{
|
||||
// There's a bug on MoltenVK where using the same buffer across different stages
|
||||
// causes invalid resource errors, allow the binding on all active stages as workaround.
|
||||
stages = activeStages;
|
||||
}
|
||||
|
||||
layoutBindings[descIndex] = new DescriptorSetLayoutBinding
|
||||
{
|
||||
Binding = (uint)descriptor.Binding,
|
||||
DescriptorType = descriptor.Type.Convert(),
|
||||
DescriptorCount = (uint)descriptor.Count,
|
||||
StageFlags = stages.Convert(),
|
||||
};
|
||||
|
||||
if (descriptor.Count > 1)
|
||||
{
|
||||
hasArray = true;
|
||||
}
|
||||
}
|
||||
|
||||
fixed (DescriptorSetLayoutBinding* pLayoutBindings = layoutBindings)
|
||||
{
|
||||
DescriptorSetLayoutCreateFlags flags = DescriptorSetLayoutCreateFlags.None;
|
||||
|
||||
if (usePushDescriptors && setIndex == 0)
|
||||
{
|
||||
flags = DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr;
|
||||
}
|
||||
|
||||
if (gd.Vendor == Vendor.Intel && hasArray)
|
||||
{
|
||||
// Some vendors (like Intel) have low per-stage limits.
|
||||
// We must set the flag if we exceed those limits.
|
||||
flags |= DescriptorSetLayoutCreateFlags.UpdateAfterBindPoolBit;
|
||||
|
||||
updateAfterBindFlags[setIndex] = true;
|
||||
}
|
||||
|
||||
var descriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
PBindings = pLayoutBindings,
|
||||
BindingCount = (uint)layoutBindings.Length,
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
gd.Api.CreateDescriptorSetLayout(device, in descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
|
||||
}
|
||||
}
|
||||
|
||||
PipelineLayout layout;
|
||||
|
||||
fixed (DescriptorSetLayout* pLayouts = layouts)
|
||||
{
|
||||
var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineLayoutCreateInfo,
|
||||
PSetLayouts = pLayouts,
|
||||
SetLayoutCount = (uint)layouts.Length,
|
||||
};
|
||||
|
||||
gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
|
||||
}
|
||||
|
||||
return new ResourceLayouts(layouts, updateAfterBindFlags, layout);
|
||||
}
|
||||
}
|
||||
}
|
732
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineState.cs
Normal file
732
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineState.cs
Normal file
|
@ -0,0 +1,732 @@
|
|||
using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
struct PipelineState : IDisposable
|
||||
{
|
||||
private const int RequiredSubgroupSize = 32;
|
||||
private const int MaxDynamicStatesCount = 9;
|
||||
|
||||
public PipelineUid Internal;
|
||||
|
||||
public float LineWidth
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 0) & 0xFFFFFFFF));
|
||||
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
|
||||
}
|
||||
|
||||
public float DepthBiasClamp
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id0 >> 32) & 0xFFFFFFFF));
|
||||
set => Internal.Id0 = (Internal.Id0 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
|
||||
}
|
||||
|
||||
public float DepthBiasConstantFactor
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 0) & 0xFFFFFFFF));
|
||||
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
|
||||
}
|
||||
|
||||
public float DepthBiasSlopeFactor
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id1 >> 32) & 0xFFFFFFFF));
|
||||
set => Internal.Id1 = (Internal.Id1 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
|
||||
}
|
||||
|
||||
public uint StencilFrontCompareMask
|
||||
{
|
||||
readonly get => (uint)((Internal.Id2 >> 0) & 0xFFFFFFFF);
|
||||
set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint StencilFrontWriteMask
|
||||
{
|
||||
readonly get => (uint)((Internal.Id2 >> 32) & 0xFFFFFFFF);
|
||||
set => Internal.Id2 = (Internal.Id2 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
}
|
||||
|
||||
public uint StencilFrontReference
|
||||
{
|
||||
readonly get => (uint)((Internal.Id3 >> 0) & 0xFFFFFFFF);
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint StencilBackCompareMask
|
||||
{
|
||||
readonly get => (uint)((Internal.Id3 >> 32) & 0xFFFFFFFF);
|
||||
set => Internal.Id3 = (Internal.Id3 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
}
|
||||
|
||||
public uint StencilBackWriteMask
|
||||
{
|
||||
readonly get => (uint)((Internal.Id4 >> 0) & 0xFFFFFFFF);
|
||||
set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint StencilBackReference
|
||||
{
|
||||
readonly get => (uint)((Internal.Id4 >> 32) & 0xFFFFFFFF);
|
||||
set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
}
|
||||
|
||||
public PolygonMode PolygonMode
|
||||
{
|
||||
readonly get => (PolygonMode)((Internal.Id5 >> 0) & 0x3FFFFFFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint StagesCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id5 >> 30) & 0xFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
|
||||
}
|
||||
|
||||
public uint VertexAttributeDescriptionsCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id5 >> 38) & 0xFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
|
||||
}
|
||||
|
||||
public uint VertexBindingDescriptionsCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id5 >> 46) & 0xFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
|
||||
}
|
||||
|
||||
public uint ViewportsCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id5 >> 54) & 0xFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
|
||||
}
|
||||
|
||||
public uint ScissorsCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id6 >> 0) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint ColorBlendAttachmentStateCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id6 >> 8) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
|
||||
}
|
||||
|
||||
public PrimitiveTopology Topology
|
||||
{
|
||||
readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
|
||||
}
|
||||
|
||||
public LogicOp LogicOp
|
||||
{
|
||||
readonly get => (LogicOp)((Internal.Id6 >> 20) & 0xF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
|
||||
}
|
||||
|
||||
public CompareOp DepthCompareOp
|
||||
{
|
||||
readonly get => (CompareOp)((Internal.Id6 >> 24) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontFailOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 27) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontPassOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 30) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontDepthFailOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 33) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
|
||||
}
|
||||
|
||||
public CompareOp StencilFrontCompareOp
|
||||
{
|
||||
readonly get => (CompareOp)((Internal.Id6 >> 36) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackFailOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 39) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackPassOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 42) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackDepthFailOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 45) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
|
||||
}
|
||||
|
||||
public CompareOp StencilBackCompareOp
|
||||
{
|
||||
readonly get => (CompareOp)((Internal.Id6 >> 48) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
|
||||
}
|
||||
|
||||
public CullModeFlags CullMode
|
||||
{
|
||||
readonly get => (CullModeFlags)((Internal.Id6 >> 51) & 0x3);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
|
||||
}
|
||||
|
||||
public bool PrimitiveRestartEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 53) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
|
||||
}
|
||||
|
||||
public bool DepthClampEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 54) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
|
||||
}
|
||||
|
||||
public bool RasterizerDiscardEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 55) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
|
||||
}
|
||||
|
||||
public FrontFace FrontFace
|
||||
{
|
||||
readonly get => (FrontFace)((Internal.Id6 >> 56) & 0x1);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
|
||||
}
|
||||
|
||||
public bool DepthBiasEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 57) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
|
||||
}
|
||||
|
||||
public bool DepthTestEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 58) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
|
||||
}
|
||||
|
||||
public bool DepthWriteEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 59) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
|
||||
}
|
||||
|
||||
public bool DepthBoundsTestEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 60) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
|
||||
}
|
||||
|
||||
public bool StencilTestEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 61) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
|
||||
}
|
||||
|
||||
public bool LogicOpEnable
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 62) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
|
||||
}
|
||||
|
||||
public bool HasDepthStencil
|
||||
{
|
||||
readonly get => ((Internal.Id6 >> 63) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
|
||||
}
|
||||
|
||||
public uint PatchControlPoints
|
||||
{
|
||||
readonly get => (uint)((Internal.Id7 >> 0) & 0xFFFFFFFF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint SamplesCount
|
||||
{
|
||||
readonly get => (uint)((Internal.Id7 >> 32) & 0xFFFFFFFF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
}
|
||||
|
||||
public bool AlphaToCoverageEnable
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 0) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
|
||||
}
|
||||
|
||||
public bool AlphaToOneEnable
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 1) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
|
||||
}
|
||||
|
||||
public bool AdvancedBlendSrcPreMultiplied
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 2) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
|
||||
}
|
||||
|
||||
public bool AdvancedBlendDstPreMultiplied
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 3) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
|
||||
}
|
||||
|
||||
public BlendOverlapEXT AdvancedBlendOverlap
|
||||
{
|
||||
readonly get => (BlendOverlapEXT)((Internal.Id8 >> 4) & 0x3);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
|
||||
}
|
||||
|
||||
public bool DepthMode
|
||||
{
|
||||
readonly get => ((Internal.Id8 >> 6) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
||||
}
|
||||
|
||||
public FeedbackLoopAspects FeedbackLoopAspects
|
||||
{
|
||||
readonly get => (FeedbackLoopAspects)((Internal.Id8 >> 7) & 0x3);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFE7F) | (((ulong)value) << 7);
|
||||
}
|
||||
|
||||
public bool HasTessellationControlShader;
|
||||
public NativeArray<PipelineShaderStageCreateInfo> Stages;
|
||||
public PipelineLayout PipelineLayout;
|
||||
public SpecData SpecializationData;
|
||||
|
||||
private Array32<VertexInputAttributeDescription> _vertexAttributeDescriptions2;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
HasTessellationControlShader = false;
|
||||
Stages = new NativeArray<PipelineShaderStageCreateInfo>(Constants.MaxShaderStages);
|
||||
|
||||
AdvancedBlendSrcPreMultiplied = true;
|
||||
AdvancedBlendDstPreMultiplied = true;
|
||||
AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt;
|
||||
|
||||
LineWidth = 1f;
|
||||
SamplesCount = 1;
|
||||
DepthMode = true;
|
||||
}
|
||||
|
||||
public unsafe Auto<DisposablePipeline> CreateComputePipeline(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ShaderCollection program,
|
||||
PipelineCache cache)
|
||||
{
|
||||
if (program.TryGetComputePipeline(ref SpecializationData, out var pipeline))
|
||||
{
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
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,
|
||||
bool throwOnError = false)
|
||||
{
|
||||
if (program.TryGetGraphicsPipeline(ref Internal, out var pipeline))
|
||||
{
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
Pipeline pipelineHandle = default;
|
||||
|
||||
bool isMoltenVk = gd.IsMoltenVk;
|
||||
|
||||
if (isMoltenVk)
|
||||
{
|
||||
UpdateVertexAttributeDescriptions(gd);
|
||||
}
|
||||
|
||||
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0])
|
||||
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions2 = &_vertexAttributeDescriptions2[0])
|
||||
fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0])
|
||||
fixed (PipelineColorBlendAttachmentState* pColorBlendAttachmentState = &Internal.ColorBlendAttachmentState[0])
|
||||
{
|
||||
var vertexInputState = new PipelineVertexInputStateCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineVertexInputStateCreateInfo,
|
||||
VertexAttributeDescriptionCount = VertexAttributeDescriptionsCount,
|
||||
PVertexAttributeDescriptions = isMoltenVk ? pVertexAttributeDescriptions2 : pVertexAttributeDescriptions,
|
||||
VertexBindingDescriptionCount = VertexBindingDescriptionsCount,
|
||||
PVertexBindingDescriptions = pVertexBindingDescriptions,
|
||||
};
|
||||
|
||||
// Using patches topology without a tessellation shader is invalid.
|
||||
// If we find such a case, return null pipeline to skip the draw.
|
||||
if (Topology == PrimitiveTopology.PatchList && !HasTessellationControlShader)
|
||||
{
|
||||
program.AddGraphicsPipeline(ref Internal, null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
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 = HasTessellationControlShader ? PrimitiveTopology.PatchList : 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,
|
||||
};
|
||||
|
||||
var viewportState = new PipelineViewportStateCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineViewportStateCreateInfo,
|
||||
ViewportCount = ViewportsCount,
|
||||
ScissorCount = ScissorsCount,
|
||||
};
|
||||
|
||||
if (gd.Capabilities.SupportsDepthClipControl)
|
||||
{
|
||||
var viewportDepthClipControlState = new PipelineViewportDepthClipControlCreateInfoEXT
|
||||
{
|
||||
SType = StructureType.PipelineViewportDepthClipControlCreateInfoExt,
|
||||
NegativeOneToOne = DepthMode,
|
||||
};
|
||||
|
||||
viewportState.PNext = &viewportDepthClipControlState;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var stencilBack = new StencilOpState(
|
||||
StencilBackFailOp,
|
||||
StencilBackPassOp,
|
||||
StencilBackDepthFailOp,
|
||||
StencilBackCompareOp);
|
||||
|
||||
var depthStencilState = new PipelineDepthStencilStateCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineDepthStencilStateCreateInfo,
|
||||
DepthTestEnable = DepthTestEnable,
|
||||
DepthWriteEnable = DepthWriteEnable,
|
||||
DepthCompareOp = DepthCompareOp,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = StencilTestEnable,
|
||||
Front = stencilFront,
|
||||
Back = stencilBack,
|
||||
};
|
||||
|
||||
uint blendEnables = 0;
|
||||
|
||||
if (gd.IsMoltenVk && Internal.AttachmentIntegerFormatMask != 0)
|
||||
{
|
||||
// Blend can't be enabled for integer formats, so let's make sure it is disabled.
|
||||
uint attachmentIntegerFormatMask = Internal.AttachmentIntegerFormatMask;
|
||||
|
||||
while (attachmentIntegerFormatMask != 0)
|
||||
{
|
||||
int i = BitOperations.TrailingZeroCount(attachmentIntegerFormatMask);
|
||||
|
||||
if (Internal.ColorBlendAttachmentState[i].BlendEnable)
|
||||
{
|
||||
blendEnables |= 1u << i;
|
||||
}
|
||||
|
||||
Internal.ColorBlendAttachmentState[i].BlendEnable = false;
|
||||
attachmentIntegerFormatMask &= ~(1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
// Vendors other than NVIDIA have a bug where it enables logical operations even for float formats,
|
||||
// so we need to force disable them here.
|
||||
bool logicOpEnable = LogicOpEnable && (gd.Vendor == Vendor.Nvidia || Internal.LogicOpsAllowed);
|
||||
|
||||
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;
|
||||
bool supportsFeedbackLoopDynamicState = gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop;
|
||||
|
||||
DynamicState* dynamicStates = stackalloc DynamicState[MaxDynamicStatesCount];
|
||||
|
||||
int dynamicStatesCount = 7;
|
||||
|
||||
dynamicStates[0] = DynamicState.Viewport;
|
||||
dynamicStates[1] = DynamicState.Scissor;
|
||||
dynamicStates[2] = DynamicState.DepthBias;
|
||||
dynamicStates[3] = DynamicState.StencilCompareMask;
|
||||
dynamicStates[4] = DynamicState.StencilWriteMask;
|
||||
dynamicStates[5] = DynamicState.StencilReference;
|
||||
dynamicStates[6] = DynamicState.BlendConstants;
|
||||
|
||||
if (supportsExtDynamicState)
|
||||
{
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.VertexInputBindingStrideExt;
|
||||
}
|
||||
|
||||
if (supportsFeedbackLoopDynamicState)
|
||||
{
|
||||
dynamicStates[dynamicStatesCount++] = DynamicState.AttachmentFeedbackLoopEnableExt;
|
||||
}
|
||||
|
||||
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineDynamicStateCreateInfo,
|
||||
DynamicStateCount = (uint)dynamicStatesCount,
|
||||
PDynamicStates = dynamicStates,
|
||||
};
|
||||
|
||||
PipelineCreateFlags flags = 0;
|
||||
|
||||
if (gd.Capabilities.SupportsAttachmentFeedbackLoop)
|
||||
{
|
||||
FeedbackLoopAspects aspects = FeedbackLoopAspects;
|
||||
|
||||
if ((aspects & FeedbackLoopAspects.Color) != 0)
|
||||
{
|
||||
flags |= PipelineCreateFlags.CreateColorAttachmentFeedbackLoopBitExt;
|
||||
}
|
||||
|
||||
if ((aspects & FeedbackLoopAspects.Depth) != 0)
|
||||
{
|
||||
flags |= PipelineCreateFlags.CreateDepthStencilAttachmentFeedbackLoopBitExt;
|
||||
}
|
||||
}
|
||||
|
||||
var pipelineCreateInfo = new GraphicsPipelineCreateInfo
|
||||
{
|
||||
SType = StructureType.GraphicsPipelineCreateInfo,
|
||||
Flags = flags,
|
||||
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,
|
||||
};
|
||||
|
||||
Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle);
|
||||
|
||||
if (throwOnError)
|
||||
{
|
||||
result.ThrowOnError();
|
||||
}
|
||||
else if (result.IsError())
|
||||
{
|
||||
program.AddGraphicsPipeline(ref Internal, null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Restore previous blend enable values if we changed it.
|
||||
while (blendEnables != 0)
|
||||
{
|
||||
int i = BitOperations.TrailingZeroCount(blendEnables);
|
||||
|
||||
Internal.ColorBlendAttachmentState[i].BlendEnable = true;
|
||||
blendEnables &= ~(1u << i);
|
||||
}
|
||||
}
|
||||
|
||||
pipeline = new Auto<DisposablePipeline>(new DisposablePipeline(gd.Api, device, pipelineHandle));
|
||||
|
||||
program.AddGraphicsPipeline(ref Internal, pipeline);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
private void UpdateVertexAttributeDescriptions(VulkanRenderer gd)
|
||||
{
|
||||
// Vertex attributes exceeding the stride are invalid.
|
||||
// In metal, they cause glitches with the vertex shader fetching incorrect values.
|
||||
// To work around this, we reduce the format to something that doesn't exceed the stride if possible.
|
||||
// The assumption is that the exceeding components are not actually accessed on the shader.
|
||||
|
||||
for (int index = 0; index < VertexAttributeDescriptionsCount; index++)
|
||||
{
|
||||
var attribute = Internal.VertexAttributeDescriptions[index];
|
||||
int vbIndex = GetVertexBufferIndex(attribute.Binding);
|
||||
|
||||
if (vbIndex >= 0)
|
||||
{
|
||||
ref var vb = ref Internal.VertexBindingDescriptions[vbIndex];
|
||||
|
||||
Format format = attribute.Format;
|
||||
|
||||
while (vb.Stride != 0 && attribute.Offset + FormatTable.GetAttributeFormatSize(format) > vb.Stride)
|
||||
{
|
||||
Format newFormat = FormatTable.DropLastComponent(format);
|
||||
|
||||
if (newFormat == format)
|
||||
{
|
||||
// That case means we failed to find a format that fits within the stride,
|
||||
// so just restore the original format and give up.
|
||||
format = attribute.Format;
|
||||
break;
|
||||
}
|
||||
|
||||
format = newFormat;
|
||||
}
|
||||
|
||||
if (attribute.Format != format && gd.FormatCapabilities.BufferFormatSupports(FormatFeatureFlags.VertexBufferBit, format))
|
||||
{
|
||||
attribute.Format = format;
|
||||
}
|
||||
}
|
||||
|
||||
_vertexAttributeDescriptions2[index] = attribute;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetVertexBufferIndex(uint binding)
|
||||
{
|
||||
for (int index = 0; index < VertexBindingDescriptionsCount; index++)
|
||||
{
|
||||
if (Internal.VertexBindingDescriptions[index].Binding == binding)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
Stages.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
125
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineUid.cs
Normal file
125
src/Ryujinx.Graphics.Rdna3Vulkan/PipelineUid.cs
Normal file
|
@ -0,0 +1,125 @@
|
|||
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.Rdna3Vulkan
|
||||
{
|
||||
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;
|
||||
|
||||
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id5 >> 38) & 0xFF);
|
||||
private readonly uint VertexBindingDescriptionsCount => (byte)((Id5 >> 46) & 0xFF);
|
||||
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id6 >> 8) & 0xFF);
|
||||
private readonly bool HasDepthStencil => ((Id6 >> 63) & 0x1) != 0UL;
|
||||
|
||||
public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions;
|
||||
public Array33<VertexInputBindingDescription> VertexBindingDescriptions;
|
||||
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
|
||||
public Array9<Format> AttachmentFormats;
|
||||
public uint AttachmentIntegerFormatMask;
|
||||
public bool LogicOpsAllowed;
|
||||
|
||||
public readonly 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 Id7).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id7)))
|
||||
{
|
||||
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[..(int)count]).SequenceEqual(MemoryMarshal.Cast<T, byte>(y[..(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;
|
||||
|
||||
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.Rdna3Vulkan/Queries/BufferedQuery.cs
Normal file
216
src/Ryujinx.Graphics.Rdna3Vulkan/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.Rdna3Vulkan.Queries
|
||||
{
|
||||
class BufferedQuery : IDisposable
|
||||
{
|
||||
private const int MaxQueryRetries = 5000;
|
||||
private const long DefaultValue = unchecked((long)0xFFFFFFFEFFFFFFFE);
|
||||
private const long DefaultValueInt = 0xFFFFFFFE;
|
||||
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 nint _bufferMap;
|
||||
private readonly CounterType _type;
|
||||
private readonly bool _result32Bit;
|
||||
private readonly bool _isSupported;
|
||||
|
||||
private readonly 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, in 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 static 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 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), true).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;
|
||||
}
|
||||
}
|
||||
}
|
252
src/Ryujinx.Graphics.Rdna3Vulkan/Queries/CounterQueue.cs
Normal file
252
src/Ryujinx.Graphics.Rdna3Vulkan/Queries/CounterQueue.cs
Normal file
|
@ -0,0 +1,252 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.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 readonly Queue<CounterQueueEvent> _events = new();
|
||||
private CounterQueueEvent _current;
|
||||
|
||||
private ulong _accumulatedCounter;
|
||||
private int _waiterCount;
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private readonly Queue<BufferedQuery> _queryPool;
|
||||
private readonly AutoResetEvent _queuedEvent = new(false);
|
||||
private readonly AutoResetEvent _wakeSignal = new(false);
|
||||
private readonly AutoResetEvent _eventConsumed = new(false);
|
||||
|
||||
private readonly 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) { Name = "CPU.CounterQueue." + (int)type };
|
||||
_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);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
foreach (BufferedQuery query in _queryPool)
|
||||
{
|
||||
query.PoolReset(cmd, ResetSequence);
|
||||
|
||||
if (--count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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, float divisor, 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, divisor);
|
||||
_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.Rdna3Vulkan/Queries/CounterQueueEvent.cs
Normal file
170
src/Ryujinx.Graphics.Rdna3Vulkan/Queries/CounterQueueEvent.cs
Normal file
|
@ -0,0 +1,170 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.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 readonly CounterQueue _queue;
|
||||
private readonly BufferedQuery _counter;
|
||||
|
||||
private bool _hostAccessReserved;
|
||||
private int _refCount = 1; // Starts with a reference from the counter queue.
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
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.Rdna3Vulkan/Queries/Counters.cs
Normal file
71
src/Ryujinx.Graphics.Rdna3Vulkan/Queries/Counters.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan.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<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, float divisor, bool hostReserved)
|
||||
{
|
||||
return _counterQueues[(int)type].QueueReport(resultHandler, divisor, _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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
src/Ryujinx.Graphics.Rdna3Vulkan/RenderPassCacheKey.cs
Normal file
43
src/Ryujinx.Graphics.Rdna3Vulkan/RenderPassCacheKey.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
internal readonly struct RenderPassCacheKey : IRefEquatable<RenderPassCacheKey>
|
||||
{
|
||||
private readonly TextureView _depthStencil;
|
||||
private readonly TextureView[] _colors;
|
||||
|
||||
public RenderPassCacheKey(TextureView depthStencil, TextureView[] colors)
|
||||
{
|
||||
_depthStencil = depthStencil;
|
||||
_colors = colors;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
HashCode hc = new();
|
||||
|
||||
hc.Add(_depthStencil);
|
||||
|
||||
if (_colors != null)
|
||||
{
|
||||
foreach (var color in _colors)
|
||||
{
|
||||
hc.Add(color);
|
||||
}
|
||||
}
|
||||
|
||||
return hc.ToHashCode();
|
||||
}
|
||||
|
||||
public bool Equals(ref RenderPassCacheKey other)
|
||||
{
|
||||
bool colorsNull = _colors == null;
|
||||
bool otherNull = other._colors == null;
|
||||
return other._depthStencil == _depthStencil &&
|
||||
colorsNull == otherNull &&
|
||||
(colorsNull || other._colors.SequenceEqual(_colors));
|
||||
}
|
||||
}
|
||||
}
|
221
src/Ryujinx.Graphics.Rdna3Vulkan/RenderPassHolder.cs
Normal file
221
src/Ryujinx.Graphics.Rdna3Vulkan/RenderPassHolder.cs
Normal file
|
@ -0,0 +1,221 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
internal class RenderPassHolder
|
||||
{
|
||||
private readonly struct FramebufferCacheKey : IRefEquatable<FramebufferCacheKey>
|
||||
{
|
||||
private readonly uint _width;
|
||||
private readonly uint _height;
|
||||
private readonly uint _layers;
|
||||
|
||||
public FramebufferCacheKey(uint width, uint height, uint layers)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
_layers = layers;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_width, _height, _layers);
|
||||
}
|
||||
|
||||
public bool Equals(ref FramebufferCacheKey other)
|
||||
{
|
||||
return other._width == _width && other._height == _height && other._layers == _layers;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct ForcedFence(TextureStorage Texture, PipelineStageFlags StageFlags);
|
||||
|
||||
private readonly TextureView[] _textures;
|
||||
private readonly Auto<DisposableRenderPass> _renderPass;
|
||||
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
|
||||
private readonly RenderPassCacheKey _key;
|
||||
private readonly List<ForcedFence> _forcedFences;
|
||||
|
||||
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
|
||||
{
|
||||
// Create render pass using framebuffer params.
|
||||
|
||||
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
||||
|
||||
AttachmentDescription[] attachmentDescs = null;
|
||||
|
||||
var subpass = new SubpassDescription
|
||||
{
|
||||
PipelineBindPoint = PipelineBindPoint.Graphics,
|
||||
};
|
||||
|
||||
AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
|
||||
|
||||
var hasFramebuffer = fb != null;
|
||||
|
||||
if (hasFramebuffer && fb.AttachmentsCount != 0)
|
||||
{
|
||||
attachmentDescs = new AttachmentDescription[fb.AttachmentsCount];
|
||||
|
||||
for (int i = 0; i < fb.AttachmentsCount; i++)
|
||||
{
|
||||
attachmentDescs[i] = new AttachmentDescription(
|
||||
0,
|
||||
fb.AttachmentFormats[i],
|
||||
TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, fb.AttachmentSamples[i]),
|
||||
AttachmentLoadOp.Load,
|
||||
AttachmentStoreOp.Store,
|
||||
AttachmentLoadOp.Load,
|
||||
AttachmentStoreOp.Store,
|
||||
ImageLayout.General,
|
||||
ImageLayout.General);
|
||||
}
|
||||
|
||||
int colorAttachmentsCount = fb.ColorAttachmentsCount;
|
||||
|
||||
if (colorAttachmentsCount > MaxAttachments - 1)
|
||||
{
|
||||
colorAttachmentsCount = MaxAttachments - 1;
|
||||
}
|
||||
|
||||
if (colorAttachmentsCount != 0)
|
||||
{
|
||||
int maxAttachmentIndex = fb.MaxColorAttachmentIndex;
|
||||
subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
|
||||
subpass.PColorAttachments = &attachmentReferences[0];
|
||||
|
||||
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
|
||||
for (int i = 0; i <= maxAttachmentIndex; i++)
|
||||
{
|
||||
subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
|
||||
}
|
||||
|
||||
for (int i = 0; i < colorAttachmentsCount; i++)
|
||||
{
|
||||
int bindIndex = fb.AttachmentIndices[i];
|
||||
|
||||
subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
|
||||
}
|
||||
}
|
||||
|
||||
if (fb.HasDepthStencil)
|
||||
{
|
||||
uint dsIndex = (uint)fb.AttachmentsCount - 1;
|
||||
|
||||
subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
|
||||
*subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
|
||||
}
|
||||
}
|
||||
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency(gd);
|
||||
|
||||
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, in renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||
|
||||
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
|
||||
}
|
||||
|
||||
_framebuffers = new HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>>();
|
||||
|
||||
// Register this render pass with all render target views.
|
||||
|
||||
var textures = fb.GetAttachmentViews();
|
||||
|
||||
foreach (var texture in textures)
|
||||
{
|
||||
texture.AddRenderPass(key, this);
|
||||
}
|
||||
|
||||
_textures = textures;
|
||||
_key = key;
|
||||
|
||||
_forcedFences = new List<ForcedFence>();
|
||||
}
|
||||
|
||||
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
|
||||
{
|
||||
var key = new FramebufferCacheKey(fb.Width, fb.Height, fb.Layers);
|
||||
|
||||
if (!_framebuffers.TryGetValue(ref key, out Auto<DisposableFramebuffer> result))
|
||||
{
|
||||
result = fb.Create(gd.Api, cbs, _renderPass);
|
||||
|
||||
_framebuffers.Add(ref key, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Auto<DisposableRenderPass> GetRenderPass()
|
||||
{
|
||||
return _renderPass;
|
||||
}
|
||||
|
||||
public void AddForcedFence(TextureStorage storage, PipelineStageFlags stageFlags)
|
||||
{
|
||||
if (!_forcedFences.Any(fence => fence.Texture == storage))
|
||||
{
|
||||
_forcedFences.Add(new ForcedFence(storage, stageFlags));
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertForcedFences(CommandBufferScoped cbs)
|
||||
{
|
||||
if (_forcedFences.Count > 0)
|
||||
{
|
||||
_forcedFences.RemoveAll((entry) =>
|
||||
{
|
||||
if (entry.Texture.Disposed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags);
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsAttachment(TextureStorage storage)
|
||||
{
|
||||
return _textures.Any(view => view.Storage == storage);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Dispose all framebuffers.
|
||||
|
||||
foreach (var fb in _framebuffers.Values)
|
||||
{
|
||||
fb.Dispose();
|
||||
}
|
||||
|
||||
// Notify all texture views that this render pass has been disposed.
|
||||
|
||||
foreach (var texture in _textures)
|
||||
{
|
||||
texture.RemoveRenderPass(_key);
|
||||
}
|
||||
|
||||
// Dispose render pass.
|
||||
|
||||
_renderPass.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
81
src/Ryujinx.Graphics.Rdna3Vulkan/ResourceArray.cs
Normal file
81
src/Ryujinx.Graphics.Rdna3Vulkan/ResourceArray.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class ResourceArray : IDisposable
|
||||
{
|
||||
private DescriptorSet[] _cachedDescriptorSets;
|
||||
|
||||
private ShaderCollection _cachedDscProgram;
|
||||
private int _cachedDscSetIndex;
|
||||
private int _cachedDscIndex;
|
||||
|
||||
private int _bindCount;
|
||||
|
||||
protected void SetDirty(VulkanRenderer gd, bool isImage)
|
||||
{
|
||||
ReleaseDescriptorSet();
|
||||
|
||||
if (_bindCount != 0)
|
||||
{
|
||||
if (isImage)
|
||||
{
|
||||
gd.PipelineInternal.ForceImageDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.PipelineInternal.ForceTextureDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetCachedDescriptorSets(CommandBufferScoped cbs, ShaderCollection program, int setIndex, out DescriptorSet[] sets)
|
||||
{
|
||||
if (_cachedDescriptorSets != null)
|
||||
{
|
||||
_cachedDscProgram.UpdateManualDescriptorSetCollectionOwnership(cbs, _cachedDscSetIndex, _cachedDscIndex);
|
||||
|
||||
sets = _cachedDescriptorSets;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var dsc = program.GetNewManualDescriptorSetCollection(cbs, setIndex, out _cachedDscIndex).Get(cbs);
|
||||
|
||||
sets = dsc.GetSets();
|
||||
|
||||
_cachedDescriptorSets = sets;
|
||||
_cachedDscProgram = program;
|
||||
_cachedDscSetIndex = setIndex;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void IncrementBindCount()
|
||||
{
|
||||
_bindCount++;
|
||||
}
|
||||
|
||||
public void DecrementBindCount()
|
||||
{
|
||||
int newBindCount = --_bindCount;
|
||||
Debug.Assert(newBindCount >= 0);
|
||||
}
|
||||
|
||||
private void ReleaseDescriptorSet()
|
||||
{
|
||||
if (_cachedDescriptorSets != null)
|
||||
{
|
||||
_cachedDscProgram.ReleaseManualDescriptorSetCollection(_cachedDscSetIndex, _cachedDscIndex);
|
||||
_cachedDescriptorSets = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseDescriptorSet();
|
||||
}
|
||||
}
|
||||
}
|
22
src/Ryujinx.Graphics.Rdna3Vulkan/ResourceBindingSegment.cs
Normal file
22
src/Ryujinx.Graphics.Rdna3Vulkan/ResourceBindingSegment.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
readonly struct ResourceBindingSegment
|
||||
{
|
||||
public readonly int Binding;
|
||||
public readonly int Count;
|
||||
public readonly ResourceType Type;
|
||||
public readonly ResourceStages Stages;
|
||||
public readonly bool IsArray;
|
||||
|
||||
public ResourceBindingSegment(int binding, int count, ResourceType type, ResourceStages stages, bool isArray)
|
||||
{
|
||||
Binding = binding;
|
||||
Count = count;
|
||||
Type = type;
|
||||
Stages = stages;
|
||||
IsArray = isArray;
|
||||
}
|
||||
}
|
||||
}
|
57
src/Ryujinx.Graphics.Rdna3Vulkan/ResourceLayoutBuilder.cs
Normal file
57
src/Ryujinx.Graphics.Rdna3Vulkan/ResourceLayoutBuilder.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class ResourceLayoutBuilder
|
||||
{
|
||||
private const int TotalSets = PipelineBase.DescriptorSetLayouts;
|
||||
|
||||
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
|
||||
private readonly List<ResourceUsage>[] _resourceUsages;
|
||||
|
||||
public ResourceLayoutBuilder()
|
||||
{
|
||||
_resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
|
||||
_resourceUsages = new List<ResourceUsage>[TotalSets];
|
||||
|
||||
for (int index = 0; index < TotalSets; index++)
|
||||
{
|
||||
_resourceDescriptors[index] = new();
|
||||
_resourceUsages[index] = new();
|
||||
}
|
||||
}
|
||||
|
||||
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding, bool write = false)
|
||||
{
|
||||
int setIndex = type switch
|
||||
{
|
||||
ResourceType.UniformBuffer => PipelineBase.UniformSetIndex,
|
||||
ResourceType.StorageBuffer => PipelineBase.StorageSetIndex,
|
||||
ResourceType.TextureAndSampler or ResourceType.BufferTexture => PipelineBase.TextureSetIndex,
|
||||
ResourceType.Image or ResourceType.BufferImage => PipelineBase.ImageSetIndex,
|
||||
_ => throw new ArgumentException($"Invalid resource type \"{type}\"."),
|
||||
};
|
||||
|
||||
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages, write));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResourceLayout Build()
|
||||
{
|
||||
var descriptors = new ResourceDescriptorCollection[TotalSets];
|
||||
var usages = new ResourceUsageCollection[TotalSets];
|
||||
|
||||
for (int index = 0; index < TotalSets; index++)
|
||||
{
|
||||
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
|
||||
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
|
||||
}
|
||||
|
||||
return new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.Windowing.GraphicsLibraryFramework" />
|
||||
<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>
|
120
src/Ryujinx.Graphics.Rdna3Vulkan/SamplerHolder.cs
Normal file
120
src/Ryujinx.Graphics.Rdna3Vulkan/SamplerHolder.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class SamplerHolder : ISampler
|
||||
{
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Auto<DisposableSampler> _sampler;
|
||||
|
||||
public unsafe SamplerHolder(VulkanRenderer gd, Device device, SamplerCreateInfo info)
|
||||
{
|
||||
_gd = gd;
|
||||
|
||||
gd.Samplers.Add(this);
|
||||
|
||||
(Filter minFilter, SamplerMipmapMode mipFilter) = info.MinFilter.Convert();
|
||||
|
||||
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, in 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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
161
src/Ryujinx.Graphics.Rdna3Vulkan/Shader.cs
Normal file
161
src/Ryujinx.Graphics.Rdna3Vulkan/Shader.cs
Normal file
|
@ -0,0 +1,161 @@
|
|||
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;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
class Shader : IDisposable
|
||||
{
|
||||
// The shaderc.net dependency's Options constructor and dispose are not thread safe.
|
||||
// Take this lock when using them.
|
||||
private static readonly Lock _shaderOptionsLock = new();
|
||||
|
||||
private static readonly nint _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 ProgramLinkStatus CompileStatus { private set; get; }
|
||||
|
||||
public readonly Task CompileTask;
|
||||
|
||||
public unsafe Shader(Vk api, Device device, ShaderSource shaderSource)
|
||||
{
|
||||
_api = api;
|
||||
_device = device;
|
||||
|
||||
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, in 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(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()[..(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
767
src/Ryujinx.Graphics.Rdna3Vulkan/ShaderCollection.cs
Normal file
767
src/Ryujinx.Graphics.Rdna3Vulkan/ShaderCollection.cs
Normal file
|
@ -0,0 +1,767 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
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 bool HasTessellationControlShader => (Stages & (1u << 3)) != 0;
|
||||
|
||||
public bool UpdateTexturesWithoutTemplate { get; }
|
||||
|
||||
public uint Stages { get; }
|
||||
|
||||
public PipelineStageFlags IncoherentBufferWriteStages { get; }
|
||||
public PipelineStageFlags IncoherentTextureWriteStages { get; }
|
||||
|
||||
public ResourceBindingSegment[][] ClearSegments { get; }
|
||||
public ResourceBindingSegment[][] BindingSegments { get; }
|
||||
public DescriptorSetTemplate[] Templates { 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 readonly VulkanRenderer _gd;
|
||||
private Device _device;
|
||||
private bool _initialized;
|
||||
|
||||
private ProgramPipelineState _state;
|
||||
private DisposableRenderPass _dummyRenderPass;
|
||||
private readonly Task _compileTask;
|
||||
private bool _firstBackgroundUse;
|
||||
|
||||
public ShaderCollection(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ShaderSource[] shaders,
|
||||
ResourceLayout resourceLayout,
|
||||
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 usePushDescriptors = !isMinimal &&
|
||||
VulkanConfiguration.UsePushDescriptors &&
|
||||
_gd.Capabilities.SupportsPushDescriptors &&
|
||||
!IsCompute &&
|
||||
!HasPushDescriptorsBug(gd) &&
|
||||
CanUsePushDescriptors(gd, resourceLayout, IsCompute);
|
||||
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> sets = usePushDescriptors ?
|
||||
BuildPushDescriptorSets(gd, resourceLayout.Sets) : resourceLayout.Sets;
|
||||
|
||||
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, sets, usePushDescriptors);
|
||||
|
||||
HasMinimalLayout = isMinimal;
|
||||
UsePushDescriptors = usePushDescriptors;
|
||||
|
||||
Stages = stages;
|
||||
|
||||
ClearSegments = BuildClearSegments(sets);
|
||||
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
|
||||
Templates = BuildTemplates(usePushDescriptors);
|
||||
(IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
|
||||
|
||||
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
|
||||
UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
|
||||
|
||||
_compileTask = Task.CompletedTask;
|
||||
_firstBackgroundUse = false;
|
||||
}
|
||||
|
||||
public ShaderCollection(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ShaderSource[] sources,
|
||||
ResourceLayout resourceLayout,
|
||||
ProgramPipelineState state,
|
||||
bool fromCache) : this(gd, device, sources, resourceLayout)
|
||||
{
|
||||
_state = state;
|
||||
|
||||
_compileTask = BackgroundCompilation();
|
||||
_firstBackgroundUse = !fromCache;
|
||||
}
|
||||
|
||||
private static bool HasPushDescriptorsBug(VulkanRenderer gd)
|
||||
{
|
||||
// Those GPUs/drivers do not work properly with push descriptors, so we must force disable them.
|
||||
return gd.IsNvidiaPreTuring || (gd.IsIntelArc && gd.IsIntelWindows);
|
||||
}
|
||||
|
||||
private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute)
|
||||
{
|
||||
// If binding 3 is immediately used, use an alternate set of reserved bindings.
|
||||
ReadOnlyCollection<ResourceUsage> uniformUsage = layout.SetUsages[0].Usages;
|
||||
bool hasBinding3 = uniformUsage.Any(x => x.Binding == 3);
|
||||
int[] reserved = isCompute ? Array.Empty<int>() : gd.GetPushDescriptorReservedBindings(hasBinding3);
|
||||
|
||||
// Can't use any of the reserved usages.
|
||||
for (int i = 0; i < uniformUsage.Count; i++)
|
||||
{
|
||||
var binding = uniformUsage[i].Binding;
|
||||
|
||||
if (reserved.Contains(binding) ||
|
||||
binding >= Constants.MaxPushDescriptorBinding ||
|
||||
binding >= gd.Capabilities.MaxPushDescriptors + reserved.Count(id => id < binding))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Prevent the sum of descriptors from exceeding MaxPushDescriptors
|
||||
int totalDescriptors = 0;
|
||||
foreach (ResourceDescriptor desc in layout.Sets.First().Descriptors)
|
||||
{
|
||||
if (!reserved.Contains(desc.Binding))
|
||||
totalDescriptors += desc.Count;
|
||||
}
|
||||
if (totalDescriptors > gd.Capabilities.MaxPushDescriptors)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<ResourceDescriptorCollection> BuildPushDescriptorSets(
|
||||
VulkanRenderer gd,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> sets)
|
||||
{
|
||||
// The reserved bindings were selected when determining if push descriptors could be used.
|
||||
int[] reserved = gd.GetPushDescriptorReservedBindings(false);
|
||||
|
||||
var result = new ResourceDescriptorCollection[sets.Count];
|
||||
|
||||
for (int i = 0; i < sets.Count; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
// Push descriptors apply here. Remove reserved bindings.
|
||||
ResourceDescriptorCollection original = sets[i];
|
||||
|
||||
var pdUniforms = new ResourceDescriptor[original.Descriptors.Count];
|
||||
int j = 0;
|
||||
|
||||
foreach (ResourceDescriptor descriptor in original.Descriptors)
|
||||
{
|
||||
if (reserved.Contains(descriptor.Binding))
|
||||
{
|
||||
// If the binding is reserved, set its descriptor count to 0.
|
||||
pdUniforms[j++] = new ResourceDescriptor(
|
||||
descriptor.Binding,
|
||||
0,
|
||||
descriptor.Type,
|
||||
descriptor.Stages);
|
||||
}
|
||||
else
|
||||
{
|
||||
pdUniforms[j++] = descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
result[i] = new ResourceDescriptorCollection(new(pdUniforms));
|
||||
}
|
||||
else
|
||||
{
|
||||
result[i] = sets[i];
|
||||
}
|
||||
}
|
||||
|
||||
return new(result);
|
||||
}
|
||||
|
||||
private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets)
|
||||
{
|
||||
ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][];
|
||||
|
||||
for (int setIndex = 0; setIndex < sets.Count; setIndex++)
|
||||
{
|
||||
List<ResourceBindingSegment> currentSegments = new();
|
||||
|
||||
ResourceDescriptor currentDescriptor = default;
|
||||
int currentCount = 0;
|
||||
|
||||
for (int index = 0; index < sets[setIndex].Descriptors.Count; index++)
|
||||
{
|
||||
ResourceDescriptor descriptor = sets[setIndex].Descriptors[index];
|
||||
|
||||
if (currentDescriptor.Binding + currentCount != descriptor.Binding ||
|
||||
currentDescriptor.Type != descriptor.Type ||
|
||||
currentDescriptor.Stages != descriptor.Stages ||
|
||||
currentDescriptor.Count > 1 ||
|
||||
descriptor.Count > 1)
|
||||
{
|
||||
if (currentCount != 0)
|
||||
{
|
||||
currentSegments.Add(new ResourceBindingSegment(
|
||||
currentDescriptor.Binding,
|
||||
currentCount,
|
||||
currentDescriptor.Type,
|
||||
currentDescriptor.Stages,
|
||||
currentDescriptor.Count > 1));
|
||||
}
|
||||
|
||||
currentDescriptor = descriptor;
|
||||
currentCount = descriptor.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentCount += descriptor.Count;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentCount != 0)
|
||||
{
|
||||
currentSegments.Add(new ResourceBindingSegment(
|
||||
currentDescriptor.Binding,
|
||||
currentCount,
|
||||
currentDescriptor.Type,
|
||||
currentDescriptor.Stages,
|
||||
currentDescriptor.Count > 1));
|
||||
}
|
||||
|
||||
segments[setIndex] = currentSegments.ToArray();
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages, out bool usesBufferTextures)
|
||||
{
|
||||
usesBufferTextures = false;
|
||||
|
||||
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
|
||||
|
||||
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
|
||||
{
|
||||
List<ResourceBindingSegment> currentSegments = new();
|
||||
|
||||
ResourceUsage currentUsage = default;
|
||||
int currentCount = 0;
|
||||
|
||||
for (int index = 0; index < setUsages[setIndex].Usages.Count; index++)
|
||||
{
|
||||
ResourceUsage usage = setUsages[setIndex].Usages[index];
|
||||
|
||||
if (usage.Type == ResourceType.BufferTexture)
|
||||
{
|
||||
usesBufferTextures = true;
|
||||
}
|
||||
|
||||
if (currentUsage.Binding + currentCount != usage.Binding ||
|
||||
currentUsage.Type != usage.Type ||
|
||||
currentUsage.Stages != usage.Stages ||
|
||||
currentUsage.ArrayLength > 1 ||
|
||||
usage.ArrayLength > 1)
|
||||
{
|
||||
if (currentCount != 0)
|
||||
{
|
||||
currentSegments.Add(new ResourceBindingSegment(
|
||||
currentUsage.Binding,
|
||||
currentCount,
|
||||
currentUsage.Type,
|
||||
currentUsage.Stages,
|
||||
currentUsage.ArrayLength > 1));
|
||||
}
|
||||
|
||||
currentUsage = usage;
|
||||
currentCount = usage.ArrayLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentCount != 0)
|
||||
{
|
||||
currentSegments.Add(new ResourceBindingSegment(
|
||||
currentUsage.Binding,
|
||||
currentCount,
|
||||
currentUsage.Type,
|
||||
currentUsage.Stages,
|
||||
currentUsage.ArrayLength > 1));
|
||||
}
|
||||
|
||||
segments[setIndex] = currentSegments.ToArray();
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
private DescriptorSetTemplate[] BuildTemplates(bool usePushDescriptors)
|
||||
{
|
||||
var templates = new DescriptorSetTemplate[BindingSegments.Length];
|
||||
|
||||
for (int setIndex = 0; setIndex < BindingSegments.Length; setIndex++)
|
||||
{
|
||||
if (usePushDescriptors && setIndex == 0)
|
||||
{
|
||||
// Push descriptors get updated using templates owned by the pipeline layout.
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceBindingSegment[] segments = BindingSegments[setIndex];
|
||||
|
||||
if (segments != null && segments.Length > 0)
|
||||
{
|
||||
templates[setIndex] = new DescriptorSetTemplate(
|
||||
_gd,
|
||||
_device,
|
||||
segments,
|
||||
_plce,
|
||||
IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics,
|
||||
setIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return templates;
|
||||
}
|
||||
|
||||
private PipelineStageFlags GetPipelineStages(ResourceStages stages)
|
||||
{
|
||||
PipelineStageFlags result = 0;
|
||||
|
||||
if ((stages & ResourceStages.Compute) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Vertex) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.VertexShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Fragment) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.FragmentShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Geometry) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.GeometryShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.TessellationControl) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.TessellationControlShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.TessellationEvaluation) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.TessellationEvaluationShaderBit;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection<ResourceUsageCollection> setUsages)
|
||||
{
|
||||
PipelineStageFlags buffer = PipelineStageFlags.None;
|
||||
PipelineStageFlags texture = PipelineStageFlags.None;
|
||||
|
||||
foreach (var set in setUsages)
|
||||
{
|
||||
foreach (var range in set.Usages)
|
||||
{
|
||||
if (range.Write)
|
||||
{
|
||||
PipelineStageFlags stages = GetPipelineStages(range.Stages);
|
||||
|
||||
switch (range.Type)
|
||||
{
|
||||
case ResourceType.Image:
|
||||
texture |= stages;
|
||||
break;
|
||||
case ResourceType.StorageBuffer:
|
||||
case ResourceType.BufferImage:
|
||||
buffer |= stages;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (buffer, texture);
|
||||
}
|
||||
|
||||
private async Task BackgroundCompilation()
|
||||
{
|
||||
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
||||
|
||||
if (Array.Exists(_shaders, 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 DisposableRenderPass CreateDummyRenderPass()
|
||||
{
|
||||
if (_dummyRenderPass.Value.Handle != 0)
|
||||
{
|
||||
return _dummyRenderPass;
|
||||
}
|
||||
|
||||
return _dummyRenderPass = _state.ToRenderPass(_gd, _device);
|
||||
}
|
||||
|
||||
public void CreateBackgroundComputePipeline()
|
||||
{
|
||||
PipelineState pipeline = new();
|
||||
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.HasTessellationControlShader = HasTessellationControlShader;
|
||||
pipeline.StagesCount = (uint)_shaders.Length;
|
||||
pipeline.PipelineLayout = PipelineLayout;
|
||||
|
||||
pipeline.CreateGraphicsPipeline(_gd, _device, this, (_gd.Pipeline as PipelineBase).PipelineCache, renderPass.Value, throwOnError: true);
|
||||
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 DescriptorSetTemplate GetPushDescriptorTemplate(long updateMask)
|
||||
{
|
||||
return _plce.GetPushDescriptorTemplate(IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, updateMask);
|
||||
}
|
||||
|
||||
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 void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex)
|
||||
{
|
||||
_plce.UpdateCommandBufferIndex(commandBufferIndex);
|
||||
}
|
||||
|
||||
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
|
||||
{
|
||||
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
|
||||
}
|
||||
|
||||
public Auto<DescriptorSetCollection> GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex)
|
||||
{
|
||||
return _plce.GetNewManualDescriptorSetCollection(cbs, setIndex, out cacheIndex);
|
||||
}
|
||||
|
||||
public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex)
|
||||
{
|
||||
_plce.UpdateManualDescriptorSetCollectionOwnership(cbs, setIndex, cacheIndex);
|
||||
}
|
||||
|
||||
public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex)
|
||||
{
|
||||
_plce.ReleaseManualDescriptorSetCollection(setIndex, cacheIndex);
|
||||
}
|
||||
|
||||
public bool HasSameLayout(ShaderCollection other)
|
||||
{
|
||||
return other != null && _plce == other._plce;
|
||||
}
|
||||
|
||||
protected virtual 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();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Templates.Length; i++)
|
||||
{
|
||||
Templates[i]?.Dispose();
|
||||
}
|
||||
|
||||
if (_dummyRenderPass.Value.Handle != 0)
|
||||
{
|
||||
_dummyRenderPass.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
100
src/Ryujinx.Graphics.Rdna3Vulkan/SpecInfo.cs
Normal file
100
src/Ryujinx.Graphics.Rdna3Vulkan/SpecInfo.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Rdna3Vulkan
|
||||
{
|
||||
public enum SpecConstType
|
||||
{
|
||||
Bool32,
|
||||
Int16,
|
||||
Int32,
|
||||
Int64,
|
||||
Float16,
|
||||
Float32,
|
||||
Float64,
|
||||
}
|
||||
|
||||
sealed class SpecDescription
|
||||
{
|
||||
public readonly SpecializationInfo Info;
|
||||
public readonly SpecializationMapEntry[] Map;
|
||||
|
||||
// For mapping a simple packed struct or single entry
|
||||
public SpecDescription(params (uint Id, SpecConstType Type)[] description)
|
||||
{
|
||||
int count = description.Length;
|
||||
Map = new SpecializationMapEntry[count];
|
||||
|
||||
uint structSize = 0;
|
||||
|
||||
for (int i = 0; i < Map.Length; ++i)
|
||||
{
|
||||
var typeSize = SizeOf(description[i].Type);
|
||||
Map[i] = new SpecializationMapEntry(description[i].Id, structSize, typeSize);
|
||||
structSize += typeSize;
|
||||
}
|
||||
|
||||
Info = new SpecializationInfo
|
||||
{
|
||||
DataSize = structSize,
|
||||
MapEntryCount = (uint)count,
|
||||
};
|
||||
}
|
||||
|
||||
// For advanced mapping with overlapping or staggered fields
|
||||
public SpecDescription(SpecializationMapEntry[] map)
|
||||
{
|
||||
Map = map;
|
||||
|
||||
uint structSize = 0;
|
||||
for (int i = 0; i < map.Length; ++i)
|
||||
{
|
||||
structSize = Math.Max(structSize, map[i].Offset + (uint)map[i].Size);
|
||||
}
|
||||
|
||||
Info = new SpecializationInfo
|
||||
{
|
||||
DataSize = structSize,
|
||||
MapEntryCount = (uint)map.Length,
|
||||
};
|
||||
}
|
||||
|
||||
private static uint SizeOf(SpecConstType type) => type switch
|
||||
{
|
||||
SpecConstType.Int16 or SpecConstType.Float16 => 2,
|
||||
SpecConstType.Bool32 or SpecConstType.Int32 or SpecConstType.Float32 => 4,
|
||||
SpecConstType.Int64 or SpecConstType.Float64 => 8,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type)),
|
||||
};
|
||||
|
||||
private SpecDescription()
|
||||
{
|
||||
Info = new();
|
||||
}
|
||||
|
||||
public static readonly SpecDescription Empty = new();
|
||||
}
|
||||
|
||||
readonly struct SpecData : IRefEquatable<SpecData>
|
||||
{
|
||||
private readonly byte[] _data;
|
||||
private readonly int _hash;
|
||||
|
||||
public int Length => _data.Length;
|
||||
public ReadOnlySpan<byte> Span => _data.AsSpan();
|
||||
public override int GetHashCode() => _hash;
|
||||
|
||||
public SpecData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
_data = new byte[data.Length];
|
||||
data.CopyTo(_data);
|
||||
|
||||
var hc = new HashCode();
|
||||
hc.AddBytes(data);
|
||||
_hash = hc.ToHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => obj is SpecData other && Equals(other);
|
||||
public bool Equals(ref SpecData other) => _data.AsSpan().SequenceEqual(other._data);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue