diff --git a/Directory.Packages.props b/Directory.Packages.props
index 261753ad11..6f690e87ca 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -20,9 +20,9 @@
-
+
-
+
diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
index 12ebabddd5..89b1e9e6bb 100644
--- a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
@@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
long originalPosition = _stream.Position;
_stream.Seek(0, SeekOrigin.Begin);
- _stream.Read(code, 0, code.Length);
+ _stream.ReadExactly(code, 0, code.Length);
_stream.Seek(originalPosition, SeekOrigin.Begin);
RelocInfo relocInfo;
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
index f156e0886b..16feeb914b 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
@@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
- int selectedReg = GetHighestValueIndex(freePositions);
+ // If this is a copy destination variable, we prefer the register used for the copy source.
+ // If the register is available, then the copy can be eliminated later as both source
+ // and destination will use the same register.
+ int selectedReg;
+
+ if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
+ {
+ selectedReg = preferredReg;
+ }
+ else
+ {
+ selectedReg = GetHighestValueIndex(freePositions);
+ }
+
int selectedNextUse = freePositions[selectedReg];
// Intervals starts and ends at odd positions, unless they span an entire
@@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
- private static int GetHighestValueIndex(Span span)
+ private static int GetHighestValueIndex(ReadOnlySpan span)
{
int highest = int.MinValue;
@@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
// The "visited" state is stored in the MSB of the local's value.
const ulong VisitedMask = 1ul << 63;
- bool IsVisited(Operand local)
+ static bool IsVisited(Operand local)
{
return (local.GetValueUnsafe() & VisitedMask) != 0;
}
- void SetVisited(Operand local)
+ static void SetVisited(Operand local)
{
local.GetValueUnsafe() |= VisitedMask;
}
@@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
dest.NumberLocal(_intervals.Count);
- _intervals.Add(new LiveInterval(dest));
+ LiveInterval interval = new LiveInterval(dest);
+ _intervals.Add(interval);
SetVisited(dest);
+
+ // If this is a copy (or copy-like operation), set the copy source interval as well.
+ // This is used for register preferencing later on, which allows the copy to be eliminated
+ // in some cases.
+ if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
+ {
+ Operand source = node.GetSource(0);
+
+ if (source.Kind == OperandKind.LocalVariable &&
+ source.GetLocalNumber() > 0 &&
+ (node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
+ {
+ interval.SetCopySource(_intervals[source.GetLocalNumber()]);
+ }
+ }
}
}
}
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
index 333d3951b1..cfe1bc7ca9 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
@@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public LiveRange CurrRange;
public LiveInterval Parent;
+ public LiveInterval CopySource;
public UseList Uses;
public LiveIntervalList Children;
@@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private ref LiveRange CurrRange => ref _data->CurrRange;
private ref LiveRange PrevRange => ref _data->PrevRange;
private ref LiveInterval Parent => ref _data->Parent;
+ private ref LiveInterval CopySource => ref _data->CopySource;
private ref UseList Uses => ref _data->Uses;
private ref LiveIntervalList Children => ref _data->Children;
@@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
Register = register;
}
+ public void SetCopySource(LiveInterval copySource)
+ {
+ CopySource = copySource;
+ }
+
+ public bool TryGetCopySourceRegister(out int copySourceRegIndex)
+ {
+ if (CopySource._data != null)
+ {
+ copySourceRegIndex = CopySource.Register.Index;
+
+ return true;
+ }
+
+ copySourceRegIndex = 0;
+
+ return false;
+ }
+
public void Reset()
{
PrevRange = default;
diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs
index 55bf072484..96f4de049c 100644
--- a/src/ARMeilleure/CodeGen/X86/Assembler.cs
+++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs
@@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
Span buffer = new byte[jump.JumpPosition - _stream.Position];
- _stream.Read(buffer);
+ _stream.ReadExactly(buffer);
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
codeStream.Write(buffer);
diff --git a/src/ARMeilleure/Translation/ControlFlowGraph.cs b/src/ARMeilleure/Translation/ControlFlowGraph.cs
index 3ead49c936..45b092ec5f 100644
--- a/src/ARMeilleure/Translation/ControlFlowGraph.cs
+++ b/src/ARMeilleure/Translation/ControlFlowGraph.cs
@@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
private int[] _postOrderMap;
public int LocalsCount { get; private set; }
- public BasicBlock Entry { get; }
+ public BasicBlock Entry { get; private set; }
public IntrusiveList Blocks { get; }
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
public int[] PostOrderMap => _postOrderMap;
@@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
return result;
}
+ public void UpdateEntry(BasicBlock newEntry)
+ {
+ newEntry.AddSuccessor(Entry);
+
+ Entry = newEntry;
+ Blocks.AddFirst(newEntry);
+ Update();
+ }
+
public void Update()
{
RemoveUnreachableBlocks(Blocks);
diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs
index f56bdce1cd..c2eed7a552 100644
--- a/src/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/src/ARMeilleure/Translation/PTC/Ptc.cs
@@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
- private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project.
+ private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
diff --git a/src/ARMeilleure/Translation/RegisterUsage.cs b/src/ARMeilleure/Translation/RegisterUsage.cs
index c8c2506267..472b0f67bc 100644
--- a/src/ARMeilleure/Translation/RegisterUsage.cs
+++ b/src/ARMeilleure/Translation/RegisterUsage.cs
@@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
{
+ if (cfg.Entry.Predecessors.Count != 0)
+ {
+ // We expect the entry block to have no predecessors.
+ // This is required because we have a implicit context load at the start of the function,
+ // but if there is a jump to the start of the function, the context load would trash the modified values.
+ // Here we insert a new entry block that will jump to the existing entry block.
+ BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
+
+ cfg.UpdateEntry(newEntry);
+ }
+
// Compute local register inputs and outputs used inside blocks.
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
@@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
// The only block without any predecessor should be the entry block.
// It always needs a context load as it is the first block to run.
- if (block.Predecessors.Count == 0 || hasContextLoad)
+ if (block == cfg.Entry || hasContextLoad)
{
long vecMask = globalInputs[block.Index].VecMask;
long intMask = globalInputs[block.Index].IntMask;
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
index 62fe5025d6..4eb75a578b 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
@@ -89,9 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
return;
}
- using IMemoryOwner samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame);
+ using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * _bytesPerFrame);
- Span samples = samplesOwner.Memory.Span;
+ Span samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
index 4011a12142..e9cc6a8e1a 100644
--- a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
@@ -122,9 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
int channelCount = areas.Length;
- using IMemoryOwner samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame);
+ using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * bytesPerFrame);
- Span samples = samplesOwner.Memory.Span;
+ Span samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);
diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
index b95e5bed14..7aefe88654 100644
--- a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
+++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Backends.Common
private readonly object _lock = new();
- private IMemoryOwner _bufferOwner;
+ private MemoryOwner _bufferOwner;
private Memory _buffer;
private int _size;
private int _headOffset;
@@ -24,7 +24,7 @@ namespace Ryujinx.Audio.Backends.Common
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
{
- _bufferOwner = ByteMemoryPool.RentCleared(initialCapacity);
+ _bufferOwner = MemoryOwner.RentCleared(initialCapacity);
_buffer = _bufferOwner.Memory;
}
@@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Backends.Common
private void SetCapacityLocked(int capacity)
{
- IMemoryOwner newBufferOwner = ByteMemoryPool.RentCleared(capacity);
+ MemoryOwner newBufferOwner = MemoryOwner.RentCleared(capacity);
Memory newBuffer = newBufferOwner.Memory;
if (_size > 0)
diff --git a/src/Ryujinx.Common/Memory/MemoryOwner.cs b/src/Ryujinx.Common/Memory/MemoryOwner.cs
new file mode 100644
index 0000000000..b7fe1db778
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/MemoryOwner.cs
@@ -0,0 +1,140 @@
+#nullable enable
+using System;
+using System.Buffers;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// An implementation with an embedded length and fast
+ /// accessor, with memory allocated from .
+ ///
+ /// The type of item to store.
+ public sealed class MemoryOwner : IMemoryOwner
+ {
+ private readonly int _length;
+ private T[]? _array;
+
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The length of the new memory buffer to use
+ private MemoryOwner(int length)
+ {
+ _length = length;
+ _array = ArrayPool.Shared.Rent(length);
+ }
+
+ ///
+ /// Creates a new instance with the specified length.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryOwner Rent(int length) => new(length);
+
+ ///
+ /// Creates a new instance with the specified length and the content cleared.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length and the content cleared
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryOwner RentCleared(int length)
+ {
+ MemoryOwner result = new(length);
+
+ result._array.AsSpan(0, length).Clear();
+
+ return result;
+ }
+
+ ///
+ /// Creates a new instance with the content copied from the specified buffer.
+ ///
+ /// The buffer to copy
+ /// A instance with the same length and content as
+ public static MemoryOwner RentCopy(ReadOnlySpan buffer)
+ {
+ MemoryOwner result = new(buffer.Length);
+
+ buffer.CopyTo(result._array);
+
+ return result;
+ }
+
+ ///
+ /// Gets the number of items in the current instance.
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+
+ ///
+ public Memory Memory
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = _array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return new(array, 0, _length);
+ }
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ ///
+ /// Uses a trick made possible by the .NET 6+ runtime array layout.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = _array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array);
+
+ return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ T[]? array = Interlocked.Exchange(ref _array, null);
+
+ if (array is not null)
+ {
+ ArrayPool.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences());
+ }
+ }
+
+ ///
+ /// Throws an when is .
+ ///
+ [DoesNotReturn]
+ private static void ThrowObjectDisposedException()
+ {
+ throw new ObjectDisposedException(nameof(MemoryOwner), "The buffer has already been disposed.");
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Memory/SpanOwner.cs b/src/Ryujinx.Common/Memory/SpanOwner.cs
new file mode 100644
index 0000000000..acb20bcad6
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/SpanOwner.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// A stack-only type that rents a buffer of a specified length from .
+ /// It does not implement to avoid being boxed, but should still be disposed. This
+ /// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method.
+ /// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals.
+ /// For all these reasons, all usage should be with a `using` block or statement.
+ ///
+ /// The type of item to store.
+ public readonly ref struct SpanOwner
+ {
+ private readonly int _length;
+ private readonly T[] _array;
+
+ ///
+ /// Initializes a new instance of the struct with the specified parameters.
+ ///
+ /// The length of the new memory buffer to use
+ private SpanOwner(int length)
+ {
+ _length = length;
+ _array = ArrayPool.Shared.Rent(length);
+ }
+
+ ///
+ /// Gets an empty instance.
+ ///
+ public static SpanOwner Empty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new(0);
+ }
+
+ ///
+ /// Creates a new instance with the specified length.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanOwner Rent(int length) => new(length);
+
+ ///
+ /// Creates a new instance with the length and the content cleared.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length and the content cleared
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanOwner RentCleared(int length)
+ {
+ SpanOwner result = new(length);
+
+ result._array.AsSpan(0, length).Clear();
+
+ return result;
+ }
+
+ ///
+ /// Creates a new instance with the content copied from the specified buffer.
+ ///
+ /// The buffer to copy
+ /// A instance with the same length and content as
+ public static SpanOwner RentCopy(ReadOnlySpan buffer)
+ {
+ SpanOwner result = new(buffer.Length);
+
+ buffer.CopyTo(result._array);
+
+ return result;
+ }
+
+ ///
+ /// Gets the number of items in the current instance
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ ///
+ /// Uses a trick made possible by the .NET 6+ runtime array layout.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
+
+ return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
+ }
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ ArrayPool.Shared.Return(_array, RuntimeHelpers.IsReferenceOrContainsReferences());
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/IImageArray.cs b/src/Ryujinx.Graphics.GAL/IImageArray.cs
index 30cff50b15..d119aa9fbd 100644
--- a/src/Ryujinx.Graphics.GAL/IImageArray.cs
+++ b/src/Ryujinx.Graphics.GAL/IImageArray.cs
@@ -1,6 +1,8 @@
+using System;
+
namespace Ryujinx.Graphics.GAL
{
- public interface IImageArray
+ public interface IImageArray : IDisposable
{
void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images);
diff --git a/src/Ryujinx.Graphics.GAL/ITextureArray.cs b/src/Ryujinx.Graphics.GAL/ITextureArray.cs
index 35c2116b54..9ee79dacbb 100644
--- a/src/Ryujinx.Graphics.GAL/ITextureArray.cs
+++ b/src/Ryujinx.Graphics.GAL/ITextureArray.cs
@@ -1,6 +1,8 @@
+using System;
+
namespace Ryujinx.Graphics.GAL
{
- public interface ITextureArray
+ public interface ITextureArray : IDisposable
{
void SetSamplers(int index, ISampler[] samplers);
void SetTextures(int index, ITexture[] textures);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
index edaae3042d..ef227d4a54 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
@@ -66,6 +66,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.CounterEventDispose);
Register(CommandType.CounterEventFlush);
+ Register(CommandType.ImageArrayDispose);
Register(CommandType.ImageArraySetFormats);
Register(CommandType.ImageArraySetImages);
@@ -88,6 +89,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.TextureSetDataSliceRegion);
Register(CommandType.TextureSetStorage);
+ Register(CommandType.TextureArrayDispose);
Register(CommandType.TextureArraySetSamplers);
Register(CommandType.TextureArraySetTextures);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
index 7586953526..cf3f5d6c14 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
@@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventDispose,
CounterEventFlush,
+ ImageArrayDispose,
ImageArraySetFormats,
ImageArraySetImages,
@@ -48,6 +49,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
TextureSetDataSliceRegion,
TextureSetStorage,
+ TextureArrayDispose,
TextureArraySetSamplers,
TextureArraySetTextures,
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs
new file mode 100644
index 0000000000..ac2ac933b7
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs
@@ -0,0 +1,21 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
+{
+ struct ImageArrayDisposeCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.ImageArrayDispose;
+ private TableRef _imageArray;
+
+ public void Set(TableRef imageArray)
+ {
+ _imageArray = imageArray;
+ }
+
+ public static void Run(ref ImageArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._imageArray.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs
new file mode 100644
index 0000000000..fec1c48f0d
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs
@@ -0,0 +1,21 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
+{
+ struct TextureArrayDisposeCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.TextureArrayDispose;
+ private TableRef _textureArray;
+
+ public void Set(TableRef textureArray)
+ {
+ _textureArray = textureArray;
+ }
+
+ public static void Run(ref TextureArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._textureArray.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs
similarity index 100%
rename from src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs
rename to src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs
similarity index 100%
rename from src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs
rename to src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
index d26ee1fbd5..19bc6f233a 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
@@ -21,6 +21,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef(_renderer, reference);
}
+ public void Dispose()
+ {
+ _renderer.New().Set(Ref(this));
+ _renderer.QueueCommand();
+ }
+
public void SetFormats(int index, Format[] imageFormats)
{
_renderer.New().Set(Ref(this), index, Ref(imageFormats));
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
index 82405a1f69..4334c70484 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
@@ -22,6 +22,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef(_renderer, reference);
}
+ public void Dispose()
+ {
+ _renderer.New().Set(Ref(this));
+ _renderer.QueueCommand();
+ }
+
public void SetSamplers(int index, ISampler[] samplers)
{
_renderer.New().Set(Ref(this), index, Ref(samplers.ToArray()));
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
index 18e28b3dd7..01e34c7771 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
@@ -1113,6 +1113,15 @@ namespace Ryujinx.Graphics.Gpu.Image
nextNode = nextNode.Next;
_cacheFromBuffer.Remove(toRemove.Value.Key);
_lruCache.Remove(toRemove);
+
+ if (toRemove.Value.Key.IsImage)
+ {
+ toRemove.Value.ImageArray.Dispose();
+ }
+ else
+ {
+ toRemove.Value.TextureArray.Dispose();
+ }
}
}
@@ -1124,11 +1133,20 @@ namespace Ryujinx.Graphics.Gpu.Image
{
List keysToRemove = null;
- foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys)
+ foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool)
{
if (key.MatchesPool(pool))
{
(keysToRemove ??= new()).Add(key);
+
+ if (key.IsImage)
+ {
+ entry.ImageArray.Dispose();
+ }
+ else
+ {
+ entry.TextureArray.Dispose();
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
index ab4508f6d1..3837092c94 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
@@ -195,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
switch (algorithm)
{
case CompressionAlgorithm.None:
- stream.Read(data);
+ stream.ReadExactly(data);
break;
case CompressionAlgorithm.Deflate:
stream = new DeflateStream(stream, CompressionMode.Decompress, true);
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
index 59d2cfb3fe..08cd3bb02e 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
@@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
- dataFileStream.Read(cb1Data);
+ dataFileStream.ReadExactly(cb1Data);
BinarySerializer.ReadCompressed(dataFileStream, guestCode);
_cache[index] = (guestCode, cb1Data);
@@ -279,7 +279,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
byte[] cachedCode = new byte[entry.CodeSize];
byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
- dataFileStream.Read(cachedCb1Data);
+ dataFileStream.ReadExactly(cachedCb1Data);
BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index fbf48f017e..c4b5a13801 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
- private const uint CodeGenVersion = 6852;
+ private const uint CodeGenVersion = 6921;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
index 1c5acedf3a..6198823d9d 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
@@ -63,5 +63,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
}
+
+ public void Dispose()
+ {
+ }
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
index d70b0a0081..41ac058c1c 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
@@ -48,5 +48,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
}
+
+ public void Dispose()
+ {
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
index f3be29bb9d..cc7977f848 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
@@ -98,11 +98,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
- AddCapability(Capability.Shader);
- AddCapability(Capability.Float64);
-
- SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
-
Delegates = new SpirvDelegates(this);
}
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
index ccfdc46d09..b259dde28c 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
@@ -43,6 +43,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
CodeGenContext context = new(info, parameters, instPool, integerPool);
+ context.AddCapability(Capability.Shader);
+
+ context.SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
+
context.AddCapability(Capability.GroupNonUniformBallot);
context.AddCapability(Capability.GroupNonUniformShuffle);
context.AddCapability(Capability.GroupNonUniformVote);
@@ -51,6 +55,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.ImageQuery);
context.AddCapability(Capability.SampledBuffer);
+ if (parameters.HostCapabilities.SupportsShaderFloat64)
+ {
+ context.AddCapability(Capability.Float64);
+ }
+
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
{
context.AddCapability(Capability.TransformFeedback);
@@ -58,7 +67,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (parameters.Definitions.Stage == ShaderStage.Fragment)
{
- if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
+ if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)) ||
+ context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.PrimitiveId)))
{
context.AddCapability(Capability.Geometry);
}
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
index 630162ade5..1d8651254f 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
@@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.BVal)
{
- context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
+ context.Copy(dest, context.ConditionalSelect(res, ConstF(1), ConstF(0)));
}
else
{
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
index 8703e660e0..273a38a5b6 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
@@ -156,6 +156,26 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
return false;
}
+ public static bool IsComparison(this Instruction inst)
+ {
+ switch (inst & Instruction.Mask)
+ {
+ case Instruction.CompareEqual:
+ case Instruction.CompareGreater:
+ case Instruction.CompareGreaterOrEqual:
+ case Instruction.CompareGreaterOrEqualU32:
+ case Instruction.CompareGreaterU32:
+ case Instruction.CompareLess:
+ case Instruction.CompareLessOrEqual:
+ case Instruction.CompareLessOrEqualU32:
+ case Instruction.CompareLessU32:
+ case Instruction.CompareNotEqual:
+ return true;
+ }
+
+ return false;
+ }
+
public static bool IsTextureQuery(this Instruction inst)
{
inst &= Instruction.Mask;
diff --git a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
index 2523272b0c..11fe6599db 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
+ public readonly bool SupportsShaderFloat64;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportMask;
@@ -18,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool supportsGeometryShaderPassthrough,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
+ bool supportsShaderFloat64,
bool supportsTextureShadowLod,
bool supportsViewportMask)
{
@@ -27,6 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
+ SupportsShaderFloat64 = supportsShaderFloat64;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportMask = supportsViewportMask;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
index 02a83fbe40..1f2f79a2dd 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
@@ -141,16 +141,16 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return true;
}
- private static bool IsBindlessAccessAllowed(Operand nvHandle)
+ private static bool IsBindlessAccessAllowed(Operand bindlessHandle)
{
- if (nvHandle.Type == OperandType.ConstantBuffer)
+ if (bindlessHandle.Type == OperandType.ConstantBuffer)
{
// Bindless access with handles from constant buffer is allowed.
return true;
}
- if (nvHandle.AsgOp is not Operation handleOp ||
+ if (bindlessHandle.AsgOp is not Operation handleOp ||
handleOp.Inst != Instruction.Load ||
(handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
{
@@ -300,7 +300,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resourceManager,
gpuAccessor,
texOp,
- TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
+ TextureHandle.PackOffsets(src0.GetCbufOffset(), (src1.Value >> 20) & 0xfff, handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
rewriteSamplerType,
isImage: false);
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
index 8eed139d6c..1e0b3b645a 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
@@ -126,7 +126,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
- if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
+ Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
+
+ if (bindlessHandle.AsgOp is not Operation handleAsgOp)
{
continue;
}
@@ -137,8 +139,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (handleAsgOp.Inst == Instruction.BitwiseOr)
{
- Operand src0 = handleAsgOp.GetSource(0);
- Operand src1 = handleAsgOp.GetSource(1);
+ Operand src0 = Utils.FindLastOperation(handleAsgOp.GetSource(0), block);
+ Operand src1 = Utils.FindLastOperation(handleAsgOp.GetSource(1), block);
if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation)
{
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
index 49eb3a89b3..1be7c5c520 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
@@ -152,18 +152,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
// If all phi sources are the same, we can propagate it and remove the phi.
- Operand firstSrc = phi.GetSource(0);
-
- for (int index = 1; index < phi.SourcesCount; index++)
+ if (!Utils.AreAllSourcesTheSameOperand(phi))
{
- if (!IsSameOperand(firstSrc, phi.GetSource(index)))
- {
- return false;
- }
+ return false;
}
// All sources are equal, we can propagate the value.
+ Operand firstSrc = phi.GetSource(0);
Operand dest = phi.Dest;
INode[] uses = dest.UseOps.ToArray();
@@ -182,17 +178,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return true;
}
- private static bool IsSameOperand(Operand x, Operand y)
- {
- if (x.Type != y.Type || x.Value != y.Value)
- {
- return false;
- }
-
- // TODO: Handle Load operations with the same storage and the same constant parameters.
- return x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
- }
-
private static bool PropagatePack(Operation packOp)
{
// Propagate pack source operands to uses by unpack
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
index a509fcb425..097c8aa88c 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
@@ -31,6 +31,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
TryEliminateBitwiseOr(operation);
break;
+ case Instruction.CompareNotEqual:
+ TryEliminateCompareNotEqual(operation);
+ break;
+
case Instruction.ConditionalSelect:
TryEliminateConditionalSelect(operation);
break;
@@ -174,6 +178,32 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
+ private static void TryEliminateCompareNotEqual(Operation operation)
+ {
+ // Comparison instruction returns 0 if the result is false, and -1 if true.
+ // Doing a not equal zero comparison on the result is redundant, so we can just copy the first result in this case.
+
+ Operand lhs = operation.GetSource(0);
+ Operand rhs = operation.GetSource(1);
+
+ if (lhs.Type == OperandType.Constant)
+ {
+ (lhs, rhs) = (rhs, lhs);
+ }
+
+ if (rhs.Type != OperandType.Constant || rhs.Value != 0)
+ {
+ return;
+ }
+
+ if (lhs.AsgOp is not Operation compareOp || !compareOp.Inst.IsComparison())
+ {
+ return;
+ }
+
+ operation.TurnIntoCopy(lhs);
+ }
+
private static void TryEliminateConditionalSelect(Operation operation)
{
Operand cond = operation.GetSource(0);
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
index 74a6d5a1e7..23180ff825 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
@@ -34,6 +34,50 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return elemIndexSrc.Type == OperandType.Constant && elemIndexSrc.Value == elemIndex;
}
+ private static bool IsSameOperand(Operand x, Operand y)
+ {
+ if (x.Type != y.Type || x.Value != y.Value)
+ {
+ return false;
+ }
+
+ // TODO: Handle Load operations with the same storage and the same constant parameters.
+ return x == y || x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
+ }
+
+ private static bool AreAllSourcesEqual(INode node, INode otherNode)
+ {
+ if (node.SourcesCount != otherNode.SourcesCount)
+ {
+ return false;
+ }
+
+ for (int index = 0; index < node.SourcesCount; index++)
+ {
+ if (!IsSameOperand(node.GetSource(index), otherNode.GetSource(index)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool AreAllSourcesTheSameOperand(INode node)
+ {
+ Operand firstSrc = node.GetSource(0);
+
+ for (int index = 1; index < node.SourcesCount; index++)
+ {
+ if (!IsSameOperand(firstSrc, node.GetSource(index)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private static Operation FindBranchSource(BasicBlock block)
{
foreach (BasicBlock sourceBlock in block.Predecessors)
@@ -55,6 +99,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return inst == Instruction.BranchIfFalse || inst == Instruction.BranchIfTrue;
}
+ private static bool IsSameCondition(Operand currentCondition, Operand queryCondition)
+ {
+ if (currentCondition == queryCondition)
+ {
+ return true;
+ }
+
+ return currentCondition.AsgOp is Operation currentOperation &&
+ queryCondition.AsgOp is Operation queryOperation &&
+ currentOperation.Inst == queryOperation.Inst &&
+ AreAllSourcesEqual(currentOperation, queryOperation);
+ }
+
private static bool BlockConditionsMatch(BasicBlock currentBlock, BasicBlock queryBlock)
{
// Check if all the conditions for the query block are satisfied by the current block.
@@ -70,10 +127,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return currentBranch != null && queryBranch != null &&
currentBranch.Inst == queryBranch.Inst &&
- currentCondition == queryCondition;
+ IsSameCondition(currentCondition, queryCondition);
}
- public static Operand FindLastOperation(Operand source, BasicBlock block)
+ public static Operand FindLastOperation(Operand source, BasicBlock block, bool recurse = true)
{
if (source.AsgOp is PhiNode phiNode)
{
@@ -84,10 +141,23 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
{
BasicBlock phiBlock = phiNode.GetBlock(i);
+ Operand phiSource = phiNode.GetSource(i);
if (BlockConditionsMatch(block, phiBlock))
{
- return phiNode.GetSource(i);
+ return phiSource;
+ }
+ else if (recurse && phiSource.AsgOp is PhiNode)
+ {
+ // Phi source is another phi.
+ // Let's check if that phi has a block that matches our condition.
+
+ Operand match = FindLastOperation(phiSource, block, false);
+
+ if (match != phiSource)
+ {
+ return match;
+ }
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
index 59914736ee..a579433f92 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
@@ -363,6 +363,7 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(),
GpuAccessor.QueryHostSupportsShaderBallot(),
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
+ GpuAccessor.QueryHostSupportsShaderFloat64(),
GpuAccessor.QueryHostSupportsTextureShadowLod(),
GpuAccessor.QueryHostSupportsViewportMask());
diff --git a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs
index e73cde83c8..1b6ac99880 100644
--- a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs
+++ b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs
@@ -103,12 +103,19 @@ namespace Ryujinx.Graphics.Vulkan
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();
diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
index 382f88d053..3590d5d057 100644
--- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
+++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
@@ -291,8 +291,9 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
- PipelineStageFlags stageFlags = _textureArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags();
- _textureArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags);
+ ref var arrayRef = ref _textureArrayRefs[segment.Binding];
+ PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
+ arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
}
}
}
@@ -311,8 +312,40 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
- PipelineStageFlags stageFlags = _imageArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags();
- _imageArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags);
+ ref var arrayRef = ref _imageArrayRefs[segment.Binding];
+ PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
+ arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
+ }
+ }
+ }
+
+ for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < _program.BindingSegments.Length; setIndex++)
+ {
+ var bindingSegments = _program.BindingSegments[setIndex];
+
+ if (bindingSegments.Length == 0)
+ {
+ continue;
+ }
+
+ ResourceBindingSegment segment = bindingSegments[0];
+
+ if (segment.IsArray)
+ {
+ if (segment.Type == ResourceType.Texture ||
+ segment.Type == ResourceType.Sampler ||
+ segment.Type == ResourceType.TextureAndSampler ||
+ segment.Type == ResourceType.BufferTexture)
+ {
+ ref var arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
+ PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
+ arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
+ }
+ else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage)
+ {
+ ref var arrayRef = ref _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
+ PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
+ arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
}
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs
index 3c7f321ff7..e42750d3ce 100644
--- a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs
+++ b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs
@@ -2,11 +2,10 @@ using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
-using System.Diagnostics;
namespace Ryujinx.Graphics.Vulkan
{
- class ImageArray : IImageArray
+ class ImageArray : ResourceArray, IImageArray
{
private readonly VulkanRenderer _gd;
@@ -25,19 +24,11 @@ namespace Ryujinx.Graphics.Vulkan
private HashSet _storages;
- private DescriptorSet[] _cachedDescriptorSets;
-
private int _cachedCommandBufferIndex;
private int _cachedSubmissionCount;
- private ShaderCollection _cachedDscProgram;
- private int _cachedDscSetIndex;
- private int _cachedDscIndex;
-
private readonly bool _isBuffer;
- private int _bindCount;
-
public ImageArray(VulkanRenderer gd, int size, bool isBuffer)
{
_gd = gd;
@@ -104,12 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_cachedCommandBufferIndex = -1;
_storages = null;
- _cachedDescriptorSets = null;
-
- if (_bindCount != 0)
- {
- _gd.PipelineInternal.ForceImageDirty();
- }
+ SetDirty(_gd);
}
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
@@ -195,7 +181,7 @@ namespace Ryujinx.Graphics.Vulkan
int setIndex,
TextureView dummyTexture)
{
- if (_cachedDescriptorSets != null)
+ if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets))
{
// We still need to ensure the current command buffer holds a reference to all used textures.
@@ -208,12 +194,9 @@ namespace Ryujinx.Graphics.Vulkan
GetBufferViews(cbs);
}
- return _cachedDescriptorSets;
+ return sets;
}
- _cachedDscProgram?.ReleaseManualDescriptorSetCollection(_cachedDscSetIndex, _cachedDscIndex);
- var dsc = program.GetNewManualDescriptorSetCollection(cbs.CommandBufferIndex, setIndex, out _cachedDscIndex).Get(cbs);
-
DescriptorSetTemplate template = program.Templates[setIndex];
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
@@ -227,24 +210,9 @@ namespace Ryujinx.Graphics.Vulkan
tu.Push(GetBufferViews(cbs));
}
- var sets = dsc.GetSets();
templateUpdater.Commit(_gd, device, sets[0]);
- _cachedDescriptorSets = sets;
- _cachedDscProgram = program;
- _cachedDscSetIndex = setIndex;
return sets;
}
-
- public void IncrementBindCount()
- {
- _bindCount++;
- }
-
- public void DecrementBindCount()
- {
- int newBindCount = --_bindCount;
- Debug.Assert(newBindCount >= 0);
- }
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
index 0bce3b72df..806b872bc2 100644
--- a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using System;
@@ -165,14 +166,15 @@ namespace Ryujinx.Graphics.Vulkan
/// True if all fences were signaled before the timeout expired, false otherwise
private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
{
- Span fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
+ using SpanOwner fenceHoldersOwner = SpanOwner.Rent(CommandBufferPool.MaxCommandBuffers);
+ Span fenceHolders = fenceHoldersOwner.Span;
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
Span fences = stackalloc Fence[count];
int fenceCount = 0;
- for (int i = 0; i < count; i++)
+ for (int i = 0; i < fences.Length; i++)
{
if (fenceHolders[i].TryGet(out Fence fence))
{
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
index ea2fb3080b..f64d3e5261 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
@@ -170,9 +170,6 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.HasDepthStencil = state.DepthStencilEnable;
pipeline.LogicOpEnable = state.LogicOpEnable;
- pipeline.MinDepthBounds = 0f; // Not implemented.
- pipeline.MaxDepthBounds = 0f; // Not implemented.
-
pipeline.PolygonMode = PolygonMode.Fill; // Not implemented.
if (!gd.Capabilities.SupportsExtendedDynamicState2)
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
index 7d0948d6e6..ae296b033f 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
@@ -3,6 +3,7 @@ using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Vulkan
@@ -15,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device;
public DescriptorSetLayout[] DescriptorSetLayouts { get; }
+ public bool[] DescriptorSetLayoutsUpdateAfterBind { get; }
public PipelineLayout PipelineLayout { get; }
private readonly int[] _consumedDescriptorsPerSet;
@@ -31,20 +33,37 @@ namespace Ryujinx.Graphics.Vulkan
private struct ManualDescriptorSetEntry
{
public Auto DescriptorSet;
- public int CbIndex;
- public int CbSubmissionCount;
+ public uint CbRefMask;
public bool InUse;
- public ManualDescriptorSetEntry(Auto descriptorSet, int cbIndex, int cbSubmissionCount, bool inUse)
+ public ManualDescriptorSetEntry(Auto descriptorSet, int cbIndex)
{
DescriptorSet = descriptorSet;
- CbIndex = cbIndex;
- CbSubmissionCount = cbSubmissionCount;
- InUse = inUse;
+ 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[] _manualDsCache;
+ private readonly Queue _pendingManualDsConsumptions;
+ private readonly Queue[] _freeManualDsCacheEntries;
private readonly Dictionary _pdTemplates;
private readonly ResourceDescriptorCollection _pdDescriptors;
@@ -70,6 +89,8 @@ namespace Ryujinx.Graphics.Vulkan
_dsCacheCursor = new int[setsCount];
_manualDsCache = new List[setsCount];
+ _pendingManualDsConsumptions = new Queue();
+ _freeManualDsCacheEntries = new Queue[setsCount];
}
public PipelineLayoutCacheEntry(
@@ -78,7 +99,11 @@ namespace Ryujinx.Graphics.Vulkan
ReadOnlyCollection setDescriptors,
bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
{
- (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
+ 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][];
@@ -133,7 +158,7 @@ namespace Ryujinx.Graphics.Vulkan
_poolSizes[setIndex],
setIndex,
_consumedDescriptorsPerSet[setIndex],
- false);
+ DescriptorSetLayoutsUpdateAfterBind[setIndex]);
list.Add(dsc);
isNew = true;
@@ -144,49 +169,99 @@ namespace Ryujinx.Graphics.Vulkan
return list[index];
}
- public Auto GetNewManualDescriptorSetCollection(int commandBufferIndex, int setIndex, out int cacheIndex)
+ public Auto GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex)
{
- int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex);
+ FreeCompletedManualDescriptorSets();
var list = _manualDsCache[setIndex] ??= new();
var span = CollectionsMarshal.AsSpan(list);
- for (int index = 0; index < span.Length; index++)
+ Queue 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[index];
+ ref ManualDescriptorSetEntry entry = ref span[freeIndex];
- if (!entry.InUse && (entry.CbIndex != commandBufferIndex || entry.CbSubmissionCount != submissionCount))
- {
- entry.InUse = true;
- entry.CbIndex = commandBufferIndex;
- entry.CbSubmissionCount = submissionCount;
+ Debug.Assert(!entry.InUse && entry.CbRefMask == 0);
- cacheIndex = index;
+ entry.InUse = true;
+ entry.CbRefMask = 1u << cbs.CommandBufferIndex;
+ cacheIndex = freeIndex;
- return entry.DescriptorSet;
- }
+ _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],
- false);
+ DescriptorSetLayoutsUpdateAfterBind[setIndex]);
cacheIndex = list.Count;
- list.Add(new ManualDescriptorSetEntry(dsc, commandBufferIndex, submissionCount, inUse: true));
+ 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 GetDescriptorPoolSizes(Span output, ResourceDescriptorCollection setDescriptor, uint multiplier)
@@ -291,6 +366,11 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null);
}
+ while (_pendingManualDsConsumptions.TryDequeue(out var pds))
+ {
+ pds.Fence.Put();
+ }
+
_descriptorSetManager.Dispose();
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
index 8bf286c654..bca119f6ad 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
@@ -1,18 +1,23 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
+using System;
using System.Collections.ObjectModel;
namespace Ryujinx.Graphics.Vulkan
{
+ record struct ResourceLayouts(DescriptorSetLayout[] DescriptorSetLayouts, bool[] DescriptorSetLayoutsUpdateAfterBind, PipelineLayout PipelineLayout);
+
static class PipelineLayoutFactory
{
- public static unsafe (DescriptorSetLayout[], PipelineLayout) Create(
+ public static unsafe ResourceLayouts Create(
VulkanRenderer gd,
Device device,
ReadOnlyCollection setDescriptors,
bool usePushDescriptors)
{
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
+ bool[] updateAfterBindFlags = new bool[setDescriptors.Count];
bool isMoltenVk = gd.IsMoltenVk;
@@ -32,10 +37,11 @@ namespace Ryujinx.Graphics.Vulkan
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)
@@ -52,16 +58,37 @@ namespace Ryujinx.Graphics.Vulkan
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 = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None,
+ Flags = flags,
};
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
@@ -82,7 +109,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
}
- return (layouts, layout);
+ return new ResourceLayouts(layouts, updateAfterBindFlags, layout);
}
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
index ace47bef88..3a659bd8f2 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
@@ -71,246 +71,234 @@ namespace Ryujinx.Graphics.Vulkan
set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32);
}
- public float MinDepthBounds
- {
- readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id5 >> 0) & 0xFFFFFFFF));
- set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
- }
-
- public float MaxDepthBounds
- {
- readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id5 >> 32) & 0xFFFFFFFF));
- set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
- }
-
public PolygonMode PolygonMode
{
- readonly get => (PolygonMode)((Internal.Id6 >> 0) & 0x3FFFFFFF);
- set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
+ readonly get => (PolygonMode)((Internal.Id5 >> 0) & 0x3FFFFFFF);
+ set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
}
public uint StagesCount
{
- readonly get => (byte)((Internal.Id6 >> 30) & 0xFF);
- set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
+ readonly get => (byte)((Internal.Id5 >> 30) & 0xFF);
+ set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
}
public uint VertexAttributeDescriptionsCount
{
- readonly get => (byte)((Internal.Id6 >> 38) & 0xFF);
- set => Internal.Id6 = (Internal.Id6 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
+ readonly get => (byte)((Internal.Id5 >> 38) & 0xFF);
+ set => Internal.Id5 = (Internal.Id5 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
}
public uint VertexBindingDescriptionsCount
{
- readonly get => (byte)((Internal.Id6 >> 46) & 0xFF);
- set => Internal.Id6 = (Internal.Id6 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
+ readonly get => (byte)((Internal.Id5 >> 46) & 0xFF);
+ set => Internal.Id5 = (Internal.Id5 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
}
public uint ViewportsCount
{
- readonly get => (byte)((Internal.Id6 >> 54) & 0xFF);
- set => Internal.Id6 = (Internal.Id6 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
+ readonly get => (byte)((Internal.Id5 >> 54) & 0xFF);
+ set => Internal.Id5 = (Internal.Id5 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
}
public uint ScissorsCount
{
- readonly get => (byte)((Internal.Id7 >> 0) & 0xFF);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
+ readonly get => (byte)((Internal.Id6 >> 0) & 0xFF);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
}
public uint ColorBlendAttachmentStateCount
{
- readonly get => (byte)((Internal.Id7 >> 8) & 0xFF);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
+ readonly get => (byte)((Internal.Id6 >> 8) & 0xFF);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
}
public PrimitiveTopology Topology
{
- readonly get => (PrimitiveTopology)((Internal.Id7 >> 16) & 0xF);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
+ readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
}
public PrimitiveTopology TopologyClass;
public LogicOp LogicOp
{
- readonly get => (LogicOp)((Internal.Id7 >> 20) & 0xF);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
+ readonly get => (LogicOp)((Internal.Id6 >> 20) & 0xF);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
}
public CompareOp DepthCompareOp
{
- readonly get => (CompareOp)((Internal.Id7 >> 24) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
+ readonly get => (CompareOp)((Internal.Id6 >> 24) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
}
public StencilOp StencilFrontFailOp
{
- readonly get => (StencilOp)((Internal.Id7 >> 27) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
+ readonly get => (StencilOp)((Internal.Id6 >> 27) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
}
public StencilOp StencilFrontPassOp
{
- readonly get => (StencilOp)((Internal.Id7 >> 30) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
+ readonly get => (StencilOp)((Internal.Id6 >> 30) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
}
public StencilOp StencilFrontDepthFailOp
{
- readonly get => (StencilOp)((Internal.Id7 >> 33) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
+ readonly get => (StencilOp)((Internal.Id6 >> 33) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
}
public CompareOp StencilFrontCompareOp
{
- readonly get => (CompareOp)((Internal.Id7 >> 36) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
+ readonly get => (CompareOp)((Internal.Id6 >> 36) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
}
public StencilOp StencilBackFailOp
{
- readonly get => (StencilOp)((Internal.Id7 >> 39) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
+ readonly get => (StencilOp)((Internal.Id6 >> 39) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
}
public StencilOp StencilBackPassOp
{
- readonly get => (StencilOp)((Internal.Id7 >> 42) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
+ readonly get => (StencilOp)((Internal.Id6 >> 42) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
}
public StencilOp StencilBackDepthFailOp
{
- readonly get => (StencilOp)((Internal.Id7 >> 45) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
+ readonly get => (StencilOp)((Internal.Id6 >> 45) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
}
public CompareOp StencilBackCompareOp
{
- readonly get => (CompareOp)((Internal.Id7 >> 48) & 0x7);
- set => Internal.Id7 = (Internal.Id7 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
+ readonly get => (CompareOp)((Internal.Id6 >> 48) & 0x7);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
}
public CullModeFlags CullMode
{
- readonly get => (CullModeFlags)((Internal.Id7 >> 51) & 0x3);
- set => Internal.Id7 = (Internal.Id7 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
+ readonly get => (CullModeFlags)((Internal.Id6 >> 51) & 0x3);
+ set => Internal.Id6 = (Internal.Id6 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
}
public bool PrimitiveRestartEnable
{
- readonly get => ((Internal.Id7 >> 53) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
+ readonly get => ((Internal.Id6 >> 53) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
}
public bool DepthClampEnable
{
- readonly get => ((Internal.Id7 >> 54) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
+ readonly get => ((Internal.Id6 >> 54) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
}
public bool RasterizerDiscardEnable
{
- readonly get => ((Internal.Id7 >> 55) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
+ readonly get => ((Internal.Id6 >> 55) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
}
public FrontFace FrontFace
{
- readonly get => (FrontFace)((Internal.Id7 >> 56) & 0x1);
- set => Internal.Id7 = (Internal.Id7 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
+ readonly get => (FrontFace)((Internal.Id6 >> 56) & 0x1);
+ set => Internal.Id6 = (Internal.Id6 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
}
public bool DepthBiasEnable
{
- readonly get => ((Internal.Id7 >> 57) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
+ readonly get => ((Internal.Id6 >> 57) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
}
public bool DepthTestEnable
{
- readonly get => ((Internal.Id7 >> 58) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
+ readonly get => ((Internal.Id6 >> 58) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
}
public bool DepthWriteEnable
{
- readonly get => ((Internal.Id7 >> 59) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
+ readonly get => ((Internal.Id6 >> 59) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
}
public bool DepthBoundsTestEnable
{
- readonly get => ((Internal.Id7 >> 60) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
+ readonly get => ((Internal.Id6 >> 60) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
}
public bool StencilTestEnable
{
- readonly get => ((Internal.Id7 >> 61) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
+ readonly get => ((Internal.Id6 >> 61) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
}
public bool LogicOpEnable
{
- readonly get => ((Internal.Id7 >> 62) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
+ readonly get => ((Internal.Id6 >> 62) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
}
public bool HasDepthStencil
{
- readonly get => ((Internal.Id7 >> 63) & 0x1) != 0UL;
- set => Internal.Id7 = (Internal.Id7 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
+ readonly get => ((Internal.Id6 >> 63) & 0x1) != 0UL;
+ set => Internal.Id6 = (Internal.Id6 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
}
public uint PatchControlPoints
{
- readonly get => (uint)((Internal.Id8 >> 0) & 0xFFFFFFFF);
- set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
+ readonly get => (uint)((Internal.Id7 >> 0) & 0xFFFFFFFF);
+ set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
}
public uint SamplesCount
{
- readonly get => (uint)((Internal.Id8 >> 32) & 0xFFFFFFFF);
- set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFF) | ((ulong)value << 32);
+ readonly get => (uint)((Internal.Id7 >> 32) & 0xFFFFFFFF);
+ set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)value << 32);
}
public bool AlphaToCoverageEnable
{
- readonly get => ((Internal.Id9 >> 0) & 0x1) != 0UL;
- set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
+ readonly get => ((Internal.Id8 >> 0) & 0x1) != 0UL;
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
}
public bool AlphaToOneEnable
{
- readonly get => ((Internal.Id9 >> 1) & 0x1) != 0UL;
- set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
+ readonly get => ((Internal.Id8 >> 1) & 0x1) != 0UL;
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
}
public bool AdvancedBlendSrcPreMultiplied
{
- readonly get => ((Internal.Id9 >> 2) & 0x1) != 0UL;
- set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
+ readonly get => ((Internal.Id8 >> 2) & 0x1) != 0UL;
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
}
public bool AdvancedBlendDstPreMultiplied
{
- readonly get => ((Internal.Id9 >> 3) & 0x1) != 0UL;
- set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
+ readonly get => ((Internal.Id8 >> 3) & 0x1) != 0UL;
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
}
public BlendOverlapEXT AdvancedBlendOverlap
{
- readonly get => (BlendOverlapEXT)((Internal.Id9 >> 4) & 0x3);
- set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
+ readonly get => (BlendOverlapEXT)((Internal.Id8 >> 4) & 0x3);
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
}
public bool DepthMode
{
- readonly get => ((Internal.Id9 >> 6) & 0x1) != 0UL;
- set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
+ readonly get => ((Internal.Id8 >> 6) & 0x1) != 0UL;
+ set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
}
public bool HasTessellationControlShader;
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
index 9dfe786692..c562242165 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
@@ -17,15 +17,14 @@ namespace Ryujinx.Graphics.Vulkan
public ulong Id4;
public ulong Id5;
public ulong Id6;
+
public ulong Id7;
-
public ulong Id8;
- public ulong Id9;
- private readonly uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF);
- private readonly uint VertexBindingDescriptionsCount => (byte)((Id6 >> 46) & 0xFF);
- private readonly uint ColorBlendAttachmentStateCount => (byte)((Id7 >> 8) & 0xFF);
- private readonly bool HasDepthStencil => ((Id7 >> 63) & 0x1) != 0UL;
+ 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 VertexAttributeDescriptions;
public Array33 VertexBindingDescriptions;
@@ -43,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan
{
if (!Unsafe.As>(ref Id0).Equals(Unsafe.As>(ref other.Id0)) ||
!Unsafe.As>(ref Id4).Equals(Unsafe.As>(ref other.Id4)) ||
- !Unsafe.As>(ref Id8).Equals(Unsafe.As>(ref other.Id8)))
+ !Unsafe.As>(ref Id7).Equals(Unsafe.As>(ref other.Id7)))
{
return false;
}
@@ -86,8 +85,7 @@ namespace Ryujinx.Graphics.Vulkan
Id5 * 23 ^
Id6 * 23 ^
Id7 * 23 ^
- Id8 * 23 ^
- Id9 * 23;
+ Id8 * 23;
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
{
diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs b/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs
new file mode 100644
index 0000000000..0880a10f07
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs
@@ -0,0 +1,74 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class ResourceArray : IDisposable
+ {
+ private DescriptorSet[] _cachedDescriptorSets;
+
+ private ShaderCollection _cachedDscProgram;
+ private int _cachedDscSetIndex;
+ private int _cachedDscIndex;
+
+ private int _bindCount;
+
+ protected void SetDirty(VulkanRenderer gd)
+ {
+ ReleaseDescriptorSet();
+
+ if (_bindCount != 0)
+ {
+ 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();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
index f2d648a517..f9637789e3 100644
--- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
+++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
@@ -604,9 +604,14 @@ namespace Ryujinx.Graphics.Vulkan
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
}
- public Auto GetNewManualDescriptorSetCollection(int commandBufferIndex, int setIndex, out int cacheIndex)
+ public Auto GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex)
{
- return _plce.GetNewManualDescriptorSetCollection(commandBufferIndex, setIndex, out 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)
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs
index fe834225c5..31c408d64f 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs
@@ -2,11 +2,10 @@ using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
-using System.Diagnostics;
namespace Ryujinx.Graphics.Vulkan
{
- class TextureArray : ITextureArray
+ class TextureArray : ResourceArray, ITextureArray
{
private readonly VulkanRenderer _gd;
@@ -25,19 +24,11 @@ namespace Ryujinx.Graphics.Vulkan
private HashSet _storages;
- private DescriptorSet[] _cachedDescriptorSets;
-
private int _cachedCommandBufferIndex;
private int _cachedSubmissionCount;
- private ShaderCollection _cachedDscProgram;
- private int _cachedDscSetIndex;
- private int _cachedDscIndex;
-
private readonly bool _isBuffer;
- private int _bindCount;
-
public TextureArray(VulkanRenderer gd, int size, bool isBuffer)
{
_gd = gd;
@@ -113,12 +104,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_cachedCommandBufferIndex = -1;
_storages = null;
- _cachedDescriptorSets = null;
-
- if (_bindCount != 0)
- {
- _gd.PipelineInternal.ForceTextureDirty();
- }
+ SetDirty(_gd);
}
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
@@ -211,7 +197,7 @@ namespace Ryujinx.Graphics.Vulkan
TextureView dummyTexture,
SamplerHolder dummySampler)
{
- if (_cachedDescriptorSets != null)
+ if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets))
{
// We still need to ensure the current command buffer holds a reference to all used textures.
@@ -224,12 +210,9 @@ namespace Ryujinx.Graphics.Vulkan
GetBufferViews(cbs);
}
- return _cachedDescriptorSets;
+ return sets;
}
- _cachedDscProgram?.ReleaseManualDescriptorSetCollection(_cachedDscSetIndex, _cachedDscIndex);
- var dsc = program.GetNewManualDescriptorSetCollection(cbs.CommandBufferIndex, setIndex, out _cachedDscIndex).Get(cbs);
-
DescriptorSetTemplate template = program.Templates[setIndex];
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
@@ -243,24 +226,9 @@ namespace Ryujinx.Graphics.Vulkan
tu.Push(GetBufferViews(cbs));
}
- var sets = dsc.GetSets();
templateUpdater.Commit(_gd, device, sets[0]);
- _cachedDescriptorSets = sets;
- _cachedDscProgram = program;
- _cachedDscSetIndex = setIndex;
return sets;
}
-
- public void IncrementBindCount()
- {
- _bindCount++;
- }
-
- public void DecrementBindCount()
- {
- int newBindCount = --_bindCount;
- Debug.Assert(newBindCount >= 0);
- }
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
index 230dbd4e87..1aaf2fbbee 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
- var flags = ImageCreateFlags.CreateMutableFormatBit;
+ var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
// This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index f2aaf4693f..5206680280 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -100,7 +100,7 @@ namespace Ryujinx.Graphics.Vulkan
unsafe Auto CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags)
{
- var usage = new ImageViewUsageCreateInfo
+ var imageViewUsage = new ImageViewUsageCreateInfo
{
SType = StructureType.ImageViewUsageCreateInfo,
Usage = usageFlags,
@@ -114,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
Format = format,
Components = cm,
SubresourceRange = sr,
- PNext = &usage,
+ PNext = &imageViewUsage,
};
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
@@ -123,7 +123,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit;
- if (info.Format.IsImageCompatible())
+ if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample()))
{
shaderUsage |= ImageUsageFlags.StorageBit;
}
@@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
- subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
+ subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, 1, (uint)firstLayer, (uint)info.Depth);
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage);
}
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
index eae4edb7fb..aec4a8d3e8 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
@@ -43,6 +43,8 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_depth_clip_control",
"VK_KHR_portability_subset", // As per spec, we should enable this if present.
"VK_EXT_4444_formats",
+ "VK_KHR_8bit_storage",
+ "VK_KHR_maintenance2",
};
private static readonly string[] _requiredExtensions = {
@@ -367,6 +369,14 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesDepthClipControl;
}
+ PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
+ {
+ SType = StructureType.PhysicalDeviceVulkan12Features,
+ PNext = features2.PNext,
+ };
+
+ features2.PNext = &supportedPhysicalDeviceVulkan12Features;
+
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
var supportedFeatures = features2.Features;
@@ -395,6 +405,7 @@ namespace Ryujinx.Graphics.Vulkan
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
RobustBufferAccess = useRobustBufferAccess,
WideLines = supportedFeatures.WideLines,
+ SampleRateShading = supportedFeatures.SampleRateShading,
};
void* pExtendedFeatures = null;
@@ -484,9 +495,11 @@ namespace Ryujinx.Graphics.Vulkan
{
SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = pExtendedFeatures,
- DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"),
- DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
- UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout"),
+ DescriptorIndexing = supportedPhysicalDeviceVulkan12Features.DescriptorIndexing,
+ DrawIndirectCount = supportedPhysicalDeviceVulkan12Features.DrawIndirectCount,
+ UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout,
+ UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess,
+ StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess,
};
pExtendedFeatures = &featuresVk12;
diff --git a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
index 7cddc362be..d9ecd47b76 100644
--- a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
+++ b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
@@ -233,7 +233,7 @@ namespace Ryujinx.UI.Windows
reader.ReadInt64(); // Padding
byte[] input = new byte[stream.Length - stream.Position];
- stream.Read(input, 0, input.Length);
+ stream.ReadExactly(input, 0, input.Length);
long inputOffset = 0;
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
index 6595ecef2a..8f104b0b7d 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
@@ -616,7 +616,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
}
- ArrayPool.Shared.Return(syncObjsArray);
+ ArrayPool.Shared.Return(syncObjsArray, true);
return result;
}
@@ -1546,8 +1546,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
#pragma warning disable CA1822 // Mark member as static
public Result SetProcessMemoryPermission(
int handle,
- [PointerSized] ulong src,
- [PointerSized] ulong size,
+ ulong src,
+ ulong size,
KMemoryPermission permission)
{
if (!PageAligned(src))
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
index b1af06b0d5..21c2730bf9 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
@@ -104,7 +104,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
}
- ArrayPool>.Shared.Return(syncNodesArray);
+ ArrayPool>.Shared.Return(syncNodesArray, true);
}
_context.CriticalSection.Leave();
diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs
index 82783e638a..176011ddee 100644
--- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs
+++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs
@@ -65,7 +65,7 @@ namespace Ryujinx.UI.App.Common
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
byte[] resourceByteArray = new byte[resourceStream.Length];
- resourceStream.Read(resourceByteArray);
+ resourceStream.ReadExactly(resourceByteArray);
return resourceByteArray;
}
diff --git a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs
index fb07195d08..6966038b61 100644
--- a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs
+++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs
@@ -104,8 +104,13 @@ namespace Ryujinx.UI.Common
// Find the length to trim the string to guarantee we have space for the trailing ellipsis.
int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis);
- // Basic trim to best case scenario of 1 byte characters.
- input = input[..trimLimit];
+ // Make sure the string is long enough to perform the basic trim.
+ // Amount of bytes != Length of the string
+ if (input.Length > trimLimit)
+ {
+ // Basic trim to best case scenario of 1 byte characters.
+ input = input[..trimLimit];
+ }
while (Encoding.UTF8.GetByteCount(input) > trimLimit)
{
diff --git a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs
index 89b591229f..12adfe94bb 100644
--- a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs
@@ -151,7 +151,7 @@ namespace Ryujinx.Ava.UI.ViewModels
reader.ReadInt64(); // Padding
byte[] input = new byte[stream.Length - stream.Position];
- stream.Read(input, 0, input.Length);
+ stream.ReadExactly(input, 0, input.Length);
uint inputOffset = 0;