RDNA3 Vulkan project

This commit is contained in:
Evan Husted 2025-01-05 23:04:17 -06:00
parent a23d1d660e
commit 7ffc1f0d2f
119 changed files with 38581 additions and 0 deletions

View file

@ -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

View 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;
}
}
}
}

View 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})");
}
}
}
}

View 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();
}
}
}
}
}

View 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();
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.Graphics.Rdna3Vulkan
{
internal enum BufferAllocationType
{
Auto = 0,
HostMappedNoCache,
HostMapped,
DeviceLocal,
DeviceLocalMapped,
Sparse,
}
}

View 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, &region);
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();
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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;
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View file

@ -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();
}
}
}

File diff suppressed because it is too large Load diff

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}

View 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);
}
}

View file

@ -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));
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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;
}
}

View file

@ -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));
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Ryujinx.Graphics.Rdna3Vulkan
{
[Flags]
internal enum FeedbackLoopAspects
{
None = 0,
Color = 1 << 0,
Depth = 1 << 1,
}
}

View 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();
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View 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,
};
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

File diff suppressed because it is too large Load diff

View 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);
}
}
}

View 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];
}
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}

View 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();
}
}
}
}

View 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);
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}
}

View 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();
}
}
}

File diff suppressed because it is too large Load diff

View 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;
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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();
}
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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();
}
}
}
}

View 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));
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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;
}
}
}

View 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());
}
}
}

View file

@ -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>

View 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();
}
}
}
}

View 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;
}
}
}
}

View 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);
}
}
}

View 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