Compare commits

..

14 Commits

Author SHA1 Message Date
0c3421973c SetProcessMemoryPermission address and size are always 64-bit (#6977) 2024-06-25 09:40:53 +02:00
0afa8f2c14 JIT: Coalesce copies on LSRA with simple register preferencing (#6950)
* JIT: Coalesce copies on LSRA with simple register preferencing

* PPTC version bump
2024-06-19 09:39:29 -03:00
d25a084858 JIT: Ensure entry block has no predecessors on RegisterUsage pass (#6951) 2024-06-19 09:25:47 -03:00
311ca3c3f1 fix: for pooled memory used for reference types, clear it on return to the pool so that it doesn't prevent GC of the instances it contained (#6937) 2024-06-16 17:47:47 -03:00
3193ef1083 Extend bindless elimination to catch a few more specific cases (#6921)
* Catch more cases on bindless elimination

* Match blocks with the same comparison condition

* Shader cache version bump
2024-06-16 14:46:27 -03:00
5a878ae9af replace ByteMemoryPool use with MemoryOwner<byte> and SpanOwner<byte> (#6911) 2024-06-15 23:00:13 +02:00
1828bc949e nuget: bump Microsoft.IO.RecyclableMemoryStream from 3.0.0 to 3.0.1 (#6936)
Bumps [Microsoft.IO.RecyclableMemoryStream](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/releases)
- [Changelog](https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream/blob/master/CHANGES.md)
- [Commits](https://github.com/Microsoft/Microsoft.IO.RecyclableMemoryStream/compare/3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: Microsoft.IO.RecyclableMemoryStream
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-15 22:51:50 +02:00
c0f2491eae Vulkan separate descriptor set fixes (#6895)
* Ensure descriptor sets are only re-used when all command buffers using it have completed

* Fix some SPIR-V capabilities

* Set update after bind flag if we exceed limits

* Simpler fix for Intel

* Format whitespace

* Make struct readonly

* Add barriers for extra set arrays too
2024-06-02 22:40:28 -03:00
d7c6474729 GPU: Remove unused dynamic state and pipeline settings (#6796)
* Dynamic state for Depth Bounds should not be passed to PipelineDynamicStateCreateInfo as the command to set them is never called.

Do not pass pointer to viewport and scissor as those dynamic states should be supported on all devices.

Same as above for DepthBias values.

* Code Review Suggestion

* Pipeline derivation is not implemented and is not suggested.

* Depth Bounds are not used.
2024-06-02 22:32:10 -03:00
1ecc8fbc3b New pooled memory types (#6821)
* feat: add new types MemoryOwner and SpanOwner

* use SpanOwner instead of new array allocation

* change for loop condition to `fences.Length` instead of `count` to elide Span boundary checks on `fences`
2024-06-02 22:24:14 -03:00
888402ecaf Avoid inexact read with 'Stream.Read' (#6847) 2024-06-02 22:16:48 +02:00
971d24aef0 nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.5.2 to 7.6.0 (#6893)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.5.2 to 7.6.0.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.5.2...7.6.0)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-02 22:10:47 +02:00
c41fddd25e Vulkan: Extend full bindless to cover cases with phi nodes (#6853)
* Key textures using set and binding (rather than just binding)

* Extend full bindless to cover cases with phi nodes

* Log error on bindless access failure

* Shader cache version bump

* Remove constant buffer match to reduce the chances of full bindless triggering

* Re-enable it for constant buffers, paper mario does actually need it

* Format whitespace
2024-05-26 15:20:10 -03:00
2ebe929fa5 misc: Change disk shader cache compression algorithm to Brotli (RFC 7932) (#6841)
* Prefer `Brotli` compression for disk shader cache.

* Final default case for decompression switch.

* Prefer fastest compression.
2024-05-26 20:06:41 +02:00
58 changed files with 1034 additions and 320 deletions

View File

@ -20,9 +20,9 @@
<PackageVersion Include="LibHac" Version="0.19.0" /> <PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.2" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" /> <PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" /> <PackageVersion Include="NetCoreServer" Version="8.0.7" />
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.13.3" />

View File

@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
long originalPosition = _stream.Position; long originalPosition = _stream.Position;
_stream.Seek(0, SeekOrigin.Begin); _stream.Seek(0, SeekOrigin.Begin);
_stream.Read(code, 0, code.Length); _stream.ReadExactly(code, 0, code.Length);
_stream.Seek(originalPosition, SeekOrigin.Begin); _stream.Seek(originalPosition, SeekOrigin.Begin);
RelocInfo relocInfo; RelocInfo relocInfo;

View File

@ -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]; int selectedNextUse = freePositions[selectedReg];
// Intervals starts and ends at odd positions, unless they span an entire // 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<int> span) private static int GetHighestValueIndex(ReadOnlySpan<int> span)
{ {
int highest = int.MinValue; 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. // The "visited" state is stored in the MSB of the local's value.
const ulong VisitedMask = 1ul << 63; const ulong VisitedMask = 1ul << 63;
bool IsVisited(Operand local) static bool IsVisited(Operand local)
{ {
return (local.GetValueUnsafe() & VisitedMask) != 0; return (local.GetValueUnsafe() & VisitedMask) != 0;
} }
void SetVisited(Operand local) static void SetVisited(Operand local)
{ {
local.GetValueUnsafe() |= VisitedMask; local.GetValueUnsafe() |= VisitedMask;
} }
@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{ {
dest.NumberLocal(_intervals.Count); dest.NumberLocal(_intervals.Count);
_intervals.Add(new LiveInterval(dest)); LiveInterval interval = new LiveInterval(dest);
_intervals.Add(interval);
SetVisited(dest); 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()]);
}
}
} }
} }
} }

View File

@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public LiveRange CurrRange; public LiveRange CurrRange;
public LiveInterval Parent; public LiveInterval Parent;
public LiveInterval CopySource;
public UseList Uses; public UseList Uses;
public LiveIntervalList Children; public LiveIntervalList Children;
@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private ref LiveRange CurrRange => ref _data->CurrRange; private ref LiveRange CurrRange => ref _data->CurrRange;
private ref LiveRange PrevRange => ref _data->PrevRange; private ref LiveRange PrevRange => ref _data->PrevRange;
private ref LiveInterval Parent => ref _data->Parent; private ref LiveInterval Parent => ref _data->Parent;
private ref LiveInterval CopySource => ref _data->CopySource;
private ref UseList Uses => ref _data->Uses; private ref UseList Uses => ref _data->Uses;
private ref LiveIntervalList Children => ref _data->Children; private ref LiveIntervalList Children => ref _data->Children;
@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
Register = register; 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() public void Reset()
{ {
PrevRange = default; PrevRange = default;

View File

@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position]; Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
_stream.Read(buffer); _stream.ReadExactly(buffer);
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current); _stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
codeStream.Write(buffer); codeStream.Write(buffer);

View File

@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
private int[] _postOrderMap; private int[] _postOrderMap;
public int LocalsCount { get; private set; } public int LocalsCount { get; private set; }
public BasicBlock Entry { get; } public BasicBlock Entry { get; private set; }
public IntrusiveList<BasicBlock> Blocks { get; } public IntrusiveList<BasicBlock> Blocks { get; }
public BasicBlock[] PostOrderBlocks => _postOrderBlocks; public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
public int[] PostOrderMap => _postOrderMap; public int[] PostOrderMap => _postOrderMap;
@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
return result; return result;
} }
public void UpdateEntry(BasicBlock newEntry)
{
newEntry.AddSuccessor(Entry);
Entry = newEntry;
Blocks.AddFirst(newEntry);
Update();
}
public void Update() public void Update()
{ {
RemoveUnreachableBlocks(Blocks); RemoveUnreachableBlocks(Blocks);

View File

@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\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 ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";

View File

@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode) 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. // Compute local register inputs and outputs used inside blocks.
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count]; RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
RegisterMask[] localOutputs = 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. // 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. // 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 vecMask = globalInputs[block.Index].VecMask;
long intMask = globalInputs[block.Index].IntMask; long intMask = globalInputs[block.Index].IntMask;

View File

@ -89,9 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
return; return;
} }
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame); using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame);
Span<byte> samples = samplesOwner.Memory.Span; Span<byte> samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length); _ringBuffer.Read(samples, 0, samples.Length);

View File

@ -122,9 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
int channelCount = areas.Length; int channelCount = areas.Length;
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame); using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * bytesPerFrame);
Span<byte> samples = samplesOwner.Memory.Span; Span<byte> samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length); _ringBuffer.Read(samples, 0, samples.Length);

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Backends.Common
private readonly object _lock = new(); private readonly object _lock = new();
private IMemoryOwner<byte> _bufferOwner; private MemoryOwner<byte> _bufferOwner;
private Memory<byte> _buffer; private Memory<byte> _buffer;
private int _size; private int _size;
private int _headOffset; private int _headOffset;
@ -24,7 +24,7 @@ namespace Ryujinx.Audio.Backends.Common
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
{ {
_bufferOwner = ByteMemoryPool.RentCleared(initialCapacity); _bufferOwner = MemoryOwner<byte>.RentCleared(initialCapacity);
_buffer = _bufferOwner.Memory; _buffer = _bufferOwner.Memory;
} }
@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Backends.Common
private void SetCapacityLocked(int capacity) private void SetCapacityLocked(int capacity)
{ {
IMemoryOwner<byte> newBufferOwner = ByteMemoryPool.RentCleared(capacity); MemoryOwner<byte> newBufferOwner = MemoryOwner<byte>.RentCleared(capacity);
Memory<byte> newBuffer = newBufferOwner.Memory; Memory<byte> newBuffer = newBufferOwner.Memory;
if (_size > 0) if (_size > 0)

View File

@ -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
{
/// <summary>
/// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and fast <see cref="Span{T}"/>
/// accessor, with memory allocated from <seealso cref="ArrayPool{T}.Shared"/>.
/// </summary>
/// <typeparam name="T">The type of item to store.</typeparam>
public sealed class MemoryOwner<T> : IMemoryOwner<T>
{
private readonly int _length;
private T[]? _array;
/// <summary>
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
private MemoryOwner(int length)
{
_length = length;
_array = ArrayPool<T>.Shared.Rent(length);
}
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> Rent(int length) => new(length);
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length and the content cleared.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length and the content cleared</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryOwner<T> RentCleared(int length)
{
MemoryOwner<T> result = new(length);
result._array.AsSpan(0, length).Clear();
return result;
}
/// <summary>
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the content copied from the specified buffer.
/// </summary>
/// <param name="buffer">The buffer to copy</param>
/// <returns>A <see cref="MemoryOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
public static MemoryOwner<T> RentCopy(ReadOnlySpan<T> buffer)
{
MemoryOwner<T> result = new(buffer.Length);
buffer.CopyTo(result._array);
return result;
}
/// <summary>
/// Gets the number of items in the current instance.
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _length;
}
/// <inheritdoc/>
public Memory<T> Memory
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[]? array = _array;
if (array is null)
{
ThrowObjectDisposedException();
}
return new(array, 0, _length);
}
}
/// <summary>
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
/// </summary>
/// <remarks>
/// Uses a trick made possible by the .NET 6+ runtime array layout.
/// </remarks>
public Span<T> 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);
}
}
/// <inheritdoc/>
public void Dispose()
{
T[]? array = Interlocked.Exchange(ref _array, null);
if (array is not null)
{
ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
}
}
/// <summary>
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="_array"/> is <see langword="null"/>.
/// </summary>
[DoesNotReturn]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The buffer has already been disposed.");
}
}
}

View File

@ -0,0 +1,114 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Memory
{
/// <summary>
/// A stack-only type that rents a buffer of a specified length from <seealso cref="ArrayPool{T}.Shared"/>.
/// It does not implement <see cref="IDisposable"/> 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.
/// </summary>
/// <typeparam name="T">The type of item to store.</typeparam>
public readonly ref struct SpanOwner<T>
{
private readonly int _length;
private readonly T[] _array;
/// <summary>
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
private SpanOwner(int length)
{
_length = length;
_array = ArrayPool<T>.Shared.Rent(length);
}
/// <summary>
/// Gets an empty <see cref="SpanOwner{T}"/> instance.
/// </summary>
public static SpanOwner<T> Empty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(0);
}
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified length.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> Rent(int length) => new(length);
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the length and the content cleared.
/// </summary>
/// <param name="length">The length of the new memory buffer to use</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length and the content cleared</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanOwner<T> RentCleared(int length)
{
SpanOwner<T> result = new(length);
result._array.AsSpan(0, length).Clear();
return result;
}
/// <summary>
/// Creates a new <see cref="SpanOwner{T}"/> instance with the content copied from the specified buffer.
/// </summary>
/// <param name="buffer">The buffer to copy</param>
/// <returns>A <see cref="SpanOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
public static SpanOwner<T> RentCopy(ReadOnlySpan<T> buffer)
{
SpanOwner<T> result = new(buffer.Length);
buffer.CopyTo(result._array);
return result;
}
/// <summary>
/// Gets the number of items in the current instance
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _length;
}
/// <summary>
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
/// </summary>
/// <remarks>
/// Uses a trick made possible by the .NET 6+ runtime array layout.
/// </remarks>
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
}
}
/// <summary>
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
ArrayPool<T>.Shared.Return(_array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
}
}
}

View File

@ -1,6 +1,8 @@
using System;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
public interface IImageArray public interface IImageArray : IDisposable
{ {
void SetFormats(int index, Format[] imageFormats); void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images); void SetImages(int index, ITexture[] images);

View File

@ -1,6 +1,8 @@
using System;
namespace Ryujinx.Graphics.GAL namespace Ryujinx.Graphics.GAL
{ {
public interface ITextureArray public interface ITextureArray : IDisposable
{ {
void SetSamplers(int index, ISampler[] samplers); void SetSamplers(int index, ISampler[] samplers);
void SetTextures(int index, ITexture[] textures); void SetTextures(int index, ITexture[] textures);

View File

@ -66,6 +66,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose); Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush); Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
Register<ImageArrayDisposeCommand>(CommandType.ImageArrayDispose);
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats); Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages); Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
@ -88,6 +89,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion); Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage); Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
Register<TextureArrayDisposeCommand>(CommandType.TextureArrayDispose);
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers); Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures); Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);

View File

@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventDispose, CounterEventDispose,
CounterEventFlush, CounterEventFlush,
ImageArrayDispose,
ImageArraySetFormats, ImageArraySetFormats,
ImageArraySetImages, ImageArraySetImages,
@ -48,6 +49,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
TextureSetDataSliceRegion, TextureSetDataSliceRegion,
TextureSetStorage, TextureSetStorage,
TextureArrayDispose,
TextureArraySetSamplers, TextureArraySetSamplers,
TextureArraySetTextures, TextureArraySetTextures,

View File

@ -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<ImageArrayDisposeCommand>
{
public readonly CommandType CommandType => CommandType.ImageArrayDispose;
private TableRef<ThreadedImageArray> _imageArray;
public void Set(TableRef<ThreadedImageArray> imageArray)
{
_imageArray = imageArray;
}
public static void Run(ref ImageArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._imageArray.Get(threaded).Base.Dispose();
}
}
}

View File

@ -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<TextureArrayDisposeCommand>
{
public readonly CommandType CommandType => CommandType.TextureArrayDispose;
private TableRef<ThreadedTextureArray> _textureArray;
public void Set(TableRef<ThreadedTextureArray> textureArray)
{
_textureArray = textureArray;
}
public static void Run(ref TextureArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
command._textureArray.Get(threaded).Base.Dispose();
}
}
}

View File

@ -21,6 +21,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef<T>(_renderer, reference); return new TableRef<T>(_renderer, reference);
} }
public void Dispose()
{
_renderer.New<ImageArrayDisposeCommand>().Set(Ref(this));
_renderer.QueueCommand();
}
public void SetFormats(int index, Format[] imageFormats) public void SetFormats(int index, Format[] imageFormats)
{ {
_renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats)); _renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats));

View File

@ -22,6 +22,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef<T>(_renderer, reference); return new TableRef<T>(_renderer, reference);
} }
public void Dispose()
{
_renderer.New<TextureArrayDisposeCommand>().Set(Ref(this));
_renderer.QueueCommand();
}
public void SetSamplers(int index, ISampler[] samplers) public void SetSamplers(int index, ISampler[] samplers)
{ {
_renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray())); _renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray()));

View File

@ -1113,6 +1113,15 @@ namespace Ryujinx.Graphics.Gpu.Image
nextNode = nextNode.Next; nextNode = nextNode.Next;
_cacheFromBuffer.Remove(toRemove.Value.Key); _cacheFromBuffer.Remove(toRemove.Value.Key);
_lruCache.Remove(toRemove); _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<CacheEntryFromPoolKey> keysToRemove = null; List<CacheEntryFromPoolKey> keysToRemove = null;
foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys) foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool)
{ {
if (key.MatchesPool(pool)) if (key.MatchesPool(pool))
{ {
(keysToRemove ??= new()).Add(key); (keysToRemove ??= new()).Add(key);
if (key.IsImage)
{
entry.ImageArray.Dispose();
}
else
{
entry.TextureArray.Dispose();
}
} }
} }

View File

@ -125,9 +125,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
CompressionAlgorithm algorithm = CompressionAlgorithm.None; CompressionAlgorithm algorithm = CompressionAlgorithm.None;
Read(ref algorithm); Read(ref algorithm);
if (algorithm == CompressionAlgorithm.Deflate) switch (algorithm)
{ {
_activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true); case CompressionAlgorithm.None:
break;
case CompressionAlgorithm.Deflate:
_activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
break;
case CompressionAlgorithm.Brotli:
_activeStream = new BrotliStream(_stream, CompressionMode.Decompress, true);
break;
default:
throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
} }
} }
@ -139,9 +148,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{ {
Write(ref algorithm); Write(ref algorithm);
if (algorithm == CompressionAlgorithm.Deflate) switch (algorithm)
{ {
_activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true); case CompressionAlgorithm.None:
break;
case CompressionAlgorithm.Deflate:
_activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true);
break;
case CompressionAlgorithm.Brotli:
_activeStream = new BrotliStream(_stream, CompressionLevel.Fastest, true);
break;
default:
throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
} }
} }
@ -177,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
switch (algorithm) switch (algorithm)
{ {
case CompressionAlgorithm.None: case CompressionAlgorithm.None:
stream.Read(data); stream.ReadExactly(data);
break; break;
case CompressionAlgorithm.Deflate: case CompressionAlgorithm.Deflate:
stream = new DeflateStream(stream, CompressionMode.Decompress, true); stream = new DeflateStream(stream, CompressionMode.Decompress, true);
@ -187,6 +205,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
} }
stream.Dispose(); stream.Dispose();
break; break;
case CompressionAlgorithm.Brotli:
stream = new BrotliStream(stream, CompressionMode.Decompress, true);
for (int offset = 0; offset < data.Length;)
{
offset += stream.Read(data[offset..]);
}
stream.Dispose();
break;
} }
} }
@ -210,6 +236,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
stream.Write(data); stream.Write(data);
stream.Dispose(); stream.Dispose();
break; break;
case CompressionAlgorithm.Brotli:
stream = new BrotliStream(stream, CompressionLevel.Fastest, true);
stream.Write(data);
stream.Dispose();
break;
} }
} }
} }

View File

@ -14,5 +14,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Deflate compression (RFC 1951). /// Deflate compression (RFC 1951).
/// </summary> /// </summary>
Deflate, Deflate,
/// <summary>
/// Brotli compression (RFC 7932).
/// </summary>
Brotli,
} }
} }

View File

@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// <returns>Compression algorithm</returns> /// <returns>Compression algorithm</returns>
public static CompressionAlgorithm GetCompressionAlgorithm() public static CompressionAlgorithm GetCompressionAlgorithm()
{ {
return CompressionAlgorithm.Deflate; return CompressionAlgorithm.Brotli;
} }
} }
} }

View File

@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
} }
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin); dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
dataFileStream.Read(cb1Data); dataFileStream.ReadExactly(cb1Data);
BinarySerializer.ReadCompressed(dataFileStream, guestCode); BinarySerializer.ReadCompressed(dataFileStream, guestCode);
_cache[index] = (guestCode, cb1Data); _cache[index] = (guestCode, cb1Data);
@ -279,7 +279,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin); dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
byte[] cachedCode = new byte[entry.CodeSize]; byte[] cachedCode = new byte[entry.CodeSize];
byte[] cachedCb1Data = new byte[entry.Cb1DataSize]; byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
dataFileStream.Read(cachedCb1Data); dataFileStream.ReadExactly(cachedCb1Data);
BinarySerializer.ReadCompressed(dataFileStream, cachedCode); BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data)) if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))

View File

@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2; private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 6870; private const uint CodeGenVersion = 6921;
private const string SharedTocFileName = "shared.toc"; private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data"; private const string SharedDataFileName = "shared.data";

View File

@ -63,5 +63,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
} }
public void Dispose()
{
}
} }
} }

View File

@ -48,5 +48,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
} }
} }
public void Dispose()
{
}
} }
} }

View File

@ -98,11 +98,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Logger = parameters.Logger; Logger = parameters.Logger;
TargetApi = parameters.TargetApi; TargetApi = parameters.TargetApi;
AddCapability(Capability.Shader);
AddCapability(Capability.Float64);
SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
Delegates = new SpirvDelegates(this); Delegates = new SpirvDelegates(this);
} }

View File

@ -43,6 +43,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
CodeGenContext context = new(info, parameters, instPool, integerPool); CodeGenContext context = new(info, parameters, instPool, integerPool);
context.AddCapability(Capability.Shader);
context.SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
context.AddCapability(Capability.GroupNonUniformBallot); context.AddCapability(Capability.GroupNonUniformBallot);
context.AddCapability(Capability.GroupNonUniformShuffle); context.AddCapability(Capability.GroupNonUniformShuffle);
context.AddCapability(Capability.GroupNonUniformVote); context.AddCapability(Capability.GroupNonUniformVote);
@ -51,6 +55,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.ImageQuery); context.AddCapability(Capability.ImageQuery);
context.AddCapability(Capability.SampledBuffer); context.AddCapability(Capability.SampledBuffer);
if (parameters.HostCapabilities.SupportsShaderFloat64)
{
context.AddCapability(Capability.Float64);
}
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline) if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
{ {
context.AddCapability(Capability.TransformFeedback); context.AddCapability(Capability.TransformFeedback);
@ -58,7 +67,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (parameters.Definitions.Stage == ShaderStage.Fragment) 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); context.AddCapability(Capability.Geometry);
} }

View File

@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.BVal) if (op.BVal)
{ {
context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0))); context.Copy(dest, context.ConditionalSelect(res, ConstF(1), ConstF(0)));
} }
else else
{ {

View File

@ -156,6 +156,26 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
return false; 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) public static bool IsTextureQuery(this Instruction inst)
{ {
inst &= Instruction.Mask; inst &= Instruction.Mask;

View File

@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly bool SupportsGeometryShaderPassthrough; public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
public readonly bool SupportsTextureShadowLod; public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportMask; public readonly bool SupportsViewportMask;
@ -18,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool supportsGeometryShaderPassthrough, bool supportsGeometryShaderPassthrough,
bool supportsShaderBallot, bool supportsShaderBallot,
bool supportsShaderBarrierDivergence, bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
bool supportsTextureShadowLod, bool supportsTextureShadowLod,
bool supportsViewportMask) bool supportsViewportMask)
{ {
@ -27,6 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsShaderBallot = supportsShaderBallot; SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64;
SupportsTextureShadowLod = supportsTextureShadowLod; SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportMask = supportsViewportMask; SupportsViewportMask = supportsViewportMask;
} }

View File

@ -38,6 +38,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// If we can't do bindless elimination, remove the texture operation. // If we can't do bindless elimination, remove the texture operation.
// Set any destination variables to zero. // Set any destination variables to zero.
string typeName = texOp.Inst.IsImage()
? texOp.Type.ToGlslImageType(texOp.Format.GetComponentType())
: texOp.Type.ToGlslTextureType();
gpuAccessor.Log($"Failed to find handle source for bindless access of type \"{typeName}\".");
for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++) for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++)
{ {
block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0))); block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0)));
@ -62,17 +68,22 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return false; return false;
} }
Operand nvHandle = texOp.GetSource(0); Operand bindlessHandle = texOp.GetSource(0);
if (nvHandle.AsgOp is not Operation handleOp || if (bindlessHandle.AsgOp is PhiNode phi)
handleOp.Inst != Instruction.Load ||
(handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
{ {
// Right now, we only allow bindless access when the handle comes from a shader input or storage buffer. for (int srcIndex = 0; srcIndex < phi.SourcesCount; srcIndex++)
// This is an artificial limitation to prevent it from being used in cases where it {
// would have a large performance impact of loading all textures in the pool. Operand phiSource = phi.GetSource(srcIndex);
// It might be removed in the future, if we can mitigate the performance impact.
if (phiSource.AsgOp is not PhiNode && !IsBindlessAccessAllowed(phiSource))
{
return false;
}
}
}
else if (!IsBindlessAccessAllowed(bindlessHandle))
{
return false; return false;
} }
@ -80,8 +91,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand samplerHandle = OperandHelper.Local(); Operand samplerHandle = OperandHelper.Local();
Operand textureIndex = OperandHelper.Local(); Operand textureIndex = OperandHelper.Local();
block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, nvHandle, OperandHelper.Const(0xfffff))); block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, bindlessHandle, OperandHelper.Const(0xfffff)));
block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, nvHandle, OperandHelper.Const(20))); block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, bindlessHandle, OperandHelper.Const(20)));
int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool()); int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool());
@ -130,6 +141,30 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return true; return true;
} }
private static bool IsBindlessAccessAllowed(Operand bindlessHandle)
{
if (bindlessHandle.Type == OperandType.ConstantBuffer)
{
// Bindless access with handles from constant buffer is allowed.
return true;
}
if (bindlessHandle.AsgOp is not Operation handleOp ||
handleOp.Inst != Instruction.Load ||
(handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
{
// Right now, we only allow bindless access when the handle comes from a shader input or storage buffer.
// This is an artificial limitation to prevent it from being used in cases where it
// would have a large performance impact of loading all textures in the pool.
// It might be removed in the future, if we can mitigate the performance impact.
return false;
}
return true;
}
private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp) private static bool TryConvertBindless(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor, TextureOperation texOp)
{ {
if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery()) if (texOp.Inst == Instruction.TextureSample || texOp.Inst.IsTextureQuery())
@ -265,7 +300,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
resourceManager, resourceManager,
gpuAccessor, gpuAccessor,
texOp, texOp,
TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType), TextureHandle.PackOffsets(src0.GetCbufOffset(), (src1.Value >> 20) & 0xfff, handleType),
TextureHandle.PackSlots(src0.GetCbufSlot(), 0), TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
rewriteSamplerType, rewriteSamplerType,
isImage: false); isImage: false);

View File

@ -126,7 +126,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue; 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; continue;
} }
@ -137,8 +139,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (handleAsgOp.Inst == Instruction.BitwiseOr) if (handleAsgOp.Inst == Instruction.BitwiseOr)
{ {
Operand src0 = handleAsgOp.GetSource(0); Operand src0 = Utils.FindLastOperation(handleAsgOp.GetSource(0), block);
Operand src1 = handleAsgOp.GetSource(1); Operand src1 = Utils.FindLastOperation(handleAsgOp.GetSource(1), block);
if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation) if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation)
{ {

View File

@ -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. // If all phi sources are the same, we can propagate it and remove the phi.
Operand firstSrc = phi.GetSource(0); if (!Utils.AreAllSourcesTheSameOperand(phi))
for (int index = 1; index < phi.SourcesCount; index++)
{ {
if (!IsSameOperand(firstSrc, phi.GetSource(index))) return false;
{
return false;
}
} }
// All sources are equal, we can propagate the value. // All sources are equal, we can propagate the value.
Operand firstSrc = phi.GetSource(0);
Operand dest = phi.Dest; Operand dest = phi.Dest;
INode[] uses = dest.UseOps.ToArray(); INode[] uses = dest.UseOps.ToArray();
@ -182,17 +178,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return true; 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) private static bool PropagatePack(Operation packOp)
{ {
// Propagate pack source operands to uses by unpack // Propagate pack source operands to uses by unpack

View File

@ -31,6 +31,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
TryEliminateBitwiseOr(operation); TryEliminateBitwiseOr(operation);
break; break;
case Instruction.CompareNotEqual:
TryEliminateCompareNotEqual(operation);
break;
case Instruction.ConditionalSelect: case Instruction.ConditionalSelect:
TryEliminateConditionalSelect(operation); TryEliminateConditionalSelect(operation);
break; 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) private static void TryEliminateConditionalSelect(Operation operation)
{ {
Operand cond = operation.GetSource(0); Operand cond = operation.GetSource(0);

View File

@ -34,6 +34,50 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return elemIndexSrc.Type == OperandType.Constant && elemIndexSrc.Value == elemIndex; 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) private static Operation FindBranchSource(BasicBlock block)
{ {
foreach (BasicBlock sourceBlock in block.Predecessors) foreach (BasicBlock sourceBlock in block.Predecessors)
@ -55,6 +99,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return inst == Instruction.BranchIfFalse || inst == Instruction.BranchIfTrue; 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) private static bool BlockConditionsMatch(BasicBlock currentBlock, BasicBlock queryBlock)
{ {
// Check if all the conditions for the query block are satisfied by the current block. // 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 && return currentBranch != null && queryBranch != null &&
currentBranch.Inst == queryBranch.Inst && 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) 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--) for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
{ {
BasicBlock phiBlock = phiNode.GetBlock(i); BasicBlock phiBlock = phiNode.GetBlock(i);
Operand phiSource = phiNode.GetSource(i);
if (BlockConditionsMatch(block, phiBlock)) 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;
}
} }
} }
} }

View File

@ -363,6 +363,7 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(), GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(),
GpuAccessor.QueryHostSupportsShaderBallot(), GpuAccessor.QueryHostSupportsShaderBallot(),
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(), GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
GpuAccessor.QueryHostSupportsShaderFloat64(),
GpuAccessor.QueryHostSupportsTextureShadowLod(), GpuAccessor.QueryHostSupportsTextureShadowLod(),
GpuAccessor.QueryHostSupportsViewportMask()); GpuAccessor.QueryHostSupportsViewportMask());

View File

@ -291,8 +291,9 @@ namespace Ryujinx.Graphics.Vulkan
} }
else else
{ {
PipelineStageFlags stageFlags = _textureArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags(); ref var arrayRef = ref _textureArrayRefs[segment.Binding];
_textureArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags); PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
} }
} }
} }
@ -311,8 +312,40 @@ namespace Ryujinx.Graphics.Vulkan
} }
else else
{ {
PipelineStageFlags stageFlags = _imageArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags(); ref var arrayRef = ref _imageArrayRefs[segment.Binding];
_imageArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags); 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);
} }
} }
} }

View File

@ -2,11 +2,10 @@ using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
class ImageArray : IImageArray class ImageArray : ResourceArray, IImageArray
{ {
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
@ -25,19 +24,11 @@ namespace Ryujinx.Graphics.Vulkan
private HashSet<TextureStorage> _storages; private HashSet<TextureStorage> _storages;
private DescriptorSet[] _cachedDescriptorSets;
private int _cachedCommandBufferIndex; private int _cachedCommandBufferIndex;
private int _cachedSubmissionCount; private int _cachedSubmissionCount;
private ShaderCollection _cachedDscProgram;
private int _cachedDscSetIndex;
private int _cachedDscIndex;
private readonly bool _isBuffer; private readonly bool _isBuffer;
private int _bindCount;
public ImageArray(VulkanRenderer gd, int size, bool isBuffer) public ImageArray(VulkanRenderer gd, int size, bool isBuffer)
{ {
_gd = gd; _gd = gd;
@ -104,12 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_cachedCommandBufferIndex = -1; _cachedCommandBufferIndex = -1;
_storages = null; _storages = null;
_cachedDescriptorSets = null; SetDirty(_gd);
if (_bindCount != 0)
{
_gd.PipelineInternal.ForceImageDirty();
}
} }
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
@ -195,7 +181,7 @@ namespace Ryujinx.Graphics.Vulkan
int setIndex, int setIndex,
TextureView dummyTexture) 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. // 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); 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]; DescriptorSetTemplate template = program.Templates[setIndex];
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template); DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
@ -227,24 +210,9 @@ namespace Ryujinx.Graphics.Vulkan
tu.Push(GetBufferViews(cbs)); tu.Push(GetBufferViews(cbs));
} }
var sets = dsc.GetSets();
templateUpdater.Commit(_gd, device, sets[0]); templateUpdater.Commit(_gd, device, sets[0]);
_cachedDescriptorSets = sets;
_cachedDscProgram = program;
_cachedDscSetIndex = setIndex;
return sets; return sets;
} }
public void IncrementBindCount()
{
_bindCount++;
}
public void DecrementBindCount()
{
int newBindCount = --_bindCount;
Debug.Assert(newBindCount >= 0);
}
} }
} }

View File

@ -1,3 +1,4 @@
using Ryujinx.Common.Memory;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
@ -165,14 +166,15 @@ namespace Ryujinx.Graphics.Vulkan
/// <returns>True if all fences were signaled before the timeout expired, false otherwise</returns> /// <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) private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
{ {
Span<FenceHolder> fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers]; using SpanOwner<FenceHolder> fenceHoldersOwner = SpanOwner<FenceHolder>.Rent(CommandBufferPool.MaxCommandBuffers);
Span<FenceHolder> fenceHolders = fenceHoldersOwner.Span;
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders); int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
Span<Fence> fences = stackalloc Fence[count]; Span<Fence> fences = stackalloc Fence[count];
int fenceCount = 0; 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)) if (fenceHolders[i].TryGet(out Fence fence))
{ {

View File

@ -180,9 +180,6 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.LogicOpEnable = state.LogicOpEnable; pipeline.LogicOpEnable = state.LogicOpEnable;
pipeline.LogicOp = state.LogicOp.Convert(); pipeline.LogicOp = state.LogicOp.Convert();
pipeline.MinDepthBounds = 0f; // Not implemented.
pipeline.MaxDepthBounds = 0f; // Not implemented.
pipeline.PatchControlPoints = state.PatchControlPoints; pipeline.PatchControlPoints = state.PatchControlPoints;
pipeline.PolygonMode = PolygonMode.Fill; // Not implemented. pipeline.PolygonMode = PolygonMode.Fill; // Not implemented.
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable; pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
@ -208,17 +205,11 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert(); pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert(); pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert();
pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert(); pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert();
pipeline.StencilFrontCompareMask = 0;
pipeline.StencilFrontWriteMask = 0;
pipeline.StencilFrontReference = 0;
pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert(); pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert(); pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert(); pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert(); pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
pipeline.StencilBackCompareMask = 0;
pipeline.StencilBackWriteMask = 0;
pipeline.StencilBackReference = 0;
pipeline.StencilTestEnable = state.StencilTest.TestEnable; pipeline.StencilTestEnable = state.StencilTest.TestEnable;

View File

@ -3,6 +3,7 @@ using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
@ -15,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device; private readonly Device _device;
public DescriptorSetLayout[] DescriptorSetLayouts { get; } public DescriptorSetLayout[] DescriptorSetLayouts { get; }
public bool[] DescriptorSetLayoutsUpdateAfterBind { get; }
public PipelineLayout PipelineLayout { get; } public PipelineLayout PipelineLayout { get; }
private readonly int[] _consumedDescriptorsPerSet; private readonly int[] _consumedDescriptorsPerSet;
@ -31,20 +33,37 @@ namespace Ryujinx.Graphics.Vulkan
private struct ManualDescriptorSetEntry private struct ManualDescriptorSetEntry
{ {
public Auto<DescriptorSetCollection> DescriptorSet; public Auto<DescriptorSetCollection> DescriptorSet;
public int CbIndex; public uint CbRefMask;
public int CbSubmissionCount;
public bool InUse; public bool InUse;
public ManualDescriptorSetEntry(Auto<DescriptorSetCollection> descriptorSet, int cbIndex, int cbSubmissionCount, bool inUse) public ManualDescriptorSetEntry(Auto<DescriptorSetCollection> descriptorSet, int cbIndex)
{ {
DescriptorSet = descriptorSet; DescriptorSet = descriptorSet;
CbIndex = cbIndex; CbRefMask = 1u << cbIndex;
CbSubmissionCount = cbSubmissionCount; InUse = true;
InUse = inUse; }
}
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 List<ManualDescriptorSetEntry>[] _manualDsCache;
private readonly Queue<PendingManualDsConsumption> _pendingManualDsConsumptions;
private readonly Queue<int>[] _freeManualDsCacheEntries;
private readonly Dictionary<long, DescriptorSetTemplate> _pdTemplates; private readonly Dictionary<long, DescriptorSetTemplate> _pdTemplates;
private readonly ResourceDescriptorCollection _pdDescriptors; private readonly ResourceDescriptorCollection _pdDescriptors;
@ -70,6 +89,8 @@ namespace Ryujinx.Graphics.Vulkan
_dsCacheCursor = new int[setsCount]; _dsCacheCursor = new int[setsCount];
_manualDsCache = new List<ManualDescriptorSetEntry>[setsCount]; _manualDsCache = new List<ManualDescriptorSetEntry>[setsCount];
_pendingManualDsConsumptions = new Queue<PendingManualDsConsumption>();
_freeManualDsCacheEntries = new Queue<int>[setsCount];
} }
public PipelineLayoutCacheEntry( public PipelineLayoutCacheEntry(
@ -78,7 +99,11 @@ namespace Ryujinx.Graphics.Vulkan
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
bool usePushDescriptors) : this(gd, device, setDescriptors.Count) 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]; _consumedDescriptorsPerSet = new int[setDescriptors.Count];
_poolSizes = new DescriptorPoolSize[setDescriptors.Count][]; _poolSizes = new DescriptorPoolSize[setDescriptors.Count][];
@ -133,7 +158,7 @@ namespace Ryujinx.Graphics.Vulkan
_poolSizes[setIndex], _poolSizes[setIndex],
setIndex, setIndex,
_consumedDescriptorsPerSet[setIndex], _consumedDescriptorsPerSet[setIndex],
false); DescriptorSetLayoutsUpdateAfterBind[setIndex]);
list.Add(dsc); list.Add(dsc);
isNew = true; isNew = true;
@ -144,49 +169,99 @@ namespace Ryujinx.Graphics.Vulkan
return list[index]; return list[index];
} }
public Auto<DescriptorSetCollection> GetNewManualDescriptorSetCollection(int commandBufferIndex, int setIndex, out int cacheIndex) public Auto<DescriptorSetCollection> GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex)
{ {
int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex); FreeCompletedManualDescriptorSets();
var list = _manualDsCache[setIndex] ??= new(); var list = _manualDsCache[setIndex] ??= new();
var span = CollectionsMarshal.AsSpan(list); var span = CollectionsMarshal.AsSpan(list);
for (int index = 0; index < span.Length; index++) 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[index]; ref ManualDescriptorSetEntry entry = ref span[freeIndex];
if (!entry.InUse && (entry.CbIndex != commandBufferIndex || entry.CbSubmissionCount != submissionCount)) Debug.Assert(!entry.InUse && entry.CbRefMask == 0);
{
entry.InUse = true;
entry.CbIndex = commandBufferIndex;
entry.CbSubmissionCount = submissionCount;
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( var dsc = _descriptorSetManager.AllocateDescriptorSet(
_gd.Api, _gd.Api,
DescriptorSetLayouts[setIndex], DescriptorSetLayouts[setIndex],
_poolSizes[setIndex], _poolSizes[setIndex],
setIndex, setIndex,
_consumedDescriptorsPerSet[setIndex], _consumedDescriptorsPerSet[setIndex],
false); DescriptorSetLayoutsUpdateAfterBind[setIndex]);
cacheIndex = list.Count; 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; 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) public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex)
{ {
var list = _manualDsCache[setIndex]; var list = _manualDsCache[setIndex];
var span = CollectionsMarshal.AsSpan(list); var span = CollectionsMarshal.AsSpan(list);
span[cacheIndex].InUse = false; 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) private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, ResourceDescriptorCollection setDescriptor, uint multiplier)
@ -291,6 +366,11 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null); _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null);
} }
while (_pendingManualDsConsumptions.TryDequeue(out var pds))
{
pds.Fence.Put();
}
_descriptorSetManager.Dispose(); _descriptorSetManager.Dispose();
} }
} }

View File

@ -1,18 +1,23 @@
using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
record struct ResourceLayouts(DescriptorSetLayout[] DescriptorSetLayouts, bool[] DescriptorSetLayoutsUpdateAfterBind, PipelineLayout PipelineLayout);
static class PipelineLayoutFactory static class PipelineLayoutFactory
{ {
public static unsafe (DescriptorSetLayout[], PipelineLayout) Create( public static unsafe ResourceLayouts Create(
VulkanRenderer gd, VulkanRenderer gd,
Device device, Device device,
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors, ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
bool usePushDescriptors) bool usePushDescriptors)
{ {
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count]; DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
bool[] updateAfterBindFlags = new bool[setDescriptors.Count];
bool isMoltenVk = gd.IsMoltenVk; bool isMoltenVk = gd.IsMoltenVk;
@ -32,10 +37,11 @@ namespace Ryujinx.Graphics.Vulkan
DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count]; DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count];
bool hasArray = false;
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++) for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
{ {
ResourceDescriptor descriptor = rdc.Descriptors[descIndex]; ResourceDescriptor descriptor = rdc.Descriptors[descIndex];
ResourceStages stages = descriptor.Stages; ResourceStages stages = descriptor.Stages;
if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk) if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk)
@ -52,16 +58,37 @@ namespace Ryujinx.Graphics.Vulkan
DescriptorCount = (uint)descriptor.Count, DescriptorCount = (uint)descriptor.Count,
StageFlags = stages.Convert(), StageFlags = stages.Convert(),
}; };
if (descriptor.Count > 1)
{
hasArray = true;
}
} }
fixed (DescriptorSetLayoutBinding* pLayoutBindings = layoutBindings) 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 var descriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo
{ {
SType = StructureType.DescriptorSetLayoutCreateInfo, SType = StructureType.DescriptorSetLayoutCreateInfo,
PBindings = pLayoutBindings, PBindings = pLayoutBindings,
BindingCount = (uint)layoutBindings.Length, BindingCount = (uint)layoutBindings.Length,
Flags = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None, Flags = flags,
}; };
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); 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(); gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
} }
return (layouts, layout); return new ResourceLayouts(layouts, updateAfterBindFlags, layout);
} }
} }
} }

View File

@ -71,244 +71,232 @@ namespace Ryujinx.Graphics.Vulkan
set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32); 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 public PolygonMode PolygonMode
{ {
readonly get => (PolygonMode)((Internal.Id6 >> 0) & 0x3FFFFFFF); readonly get => (PolygonMode)((Internal.Id5 >> 0) & 0x3FFFFFFF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC0000000) | ((ulong)value << 0); set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
} }
public uint StagesCount public uint StagesCount
{ {
readonly get => (byte)((Internal.Id6 >> 30) & 0xFF); readonly get => (byte)((Internal.Id5 >> 30) & 0xFF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30); set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
} }
public uint VertexAttributeDescriptionsCount public uint VertexAttributeDescriptionsCount
{ {
readonly get => (byte)((Internal.Id6 >> 38) & 0xFF); readonly get => (byte)((Internal.Id5 >> 38) & 0xFF);
set => Internal.Id6 = (Internal.Id6 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38); set => Internal.Id5 = (Internal.Id5 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
} }
public uint VertexBindingDescriptionsCount public uint VertexBindingDescriptionsCount
{ {
readonly get => (byte)((Internal.Id6 >> 46) & 0xFF); readonly get => (byte)((Internal.Id5 >> 46) & 0xFF);
set => Internal.Id6 = (Internal.Id6 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46); set => Internal.Id5 = (Internal.Id5 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
} }
public uint ViewportsCount public uint ViewportsCount
{ {
readonly get => (byte)((Internal.Id6 >> 54) & 0xFF); readonly get => (byte)((Internal.Id5 >> 54) & 0xFF);
set => Internal.Id6 = (Internal.Id6 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54); set => Internal.Id5 = (Internal.Id5 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
} }
public uint ScissorsCount public uint ScissorsCount
{ {
readonly get => (byte)((Internal.Id7 >> 0) & 0xFF); readonly get => (byte)((Internal.Id6 >> 0) & 0xFF);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
} }
public uint ColorBlendAttachmentStateCount public uint ColorBlendAttachmentStateCount
{ {
readonly get => (byte)((Internal.Id7 >> 8) & 0xFF); readonly get => (byte)((Internal.Id6 >> 8) & 0xFF);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
} }
public PrimitiveTopology Topology public PrimitiveTopology Topology
{ {
readonly get => (PrimitiveTopology)((Internal.Id7 >> 16) & 0xF); readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
} }
public LogicOp LogicOp public LogicOp LogicOp
{ {
readonly get => (LogicOp)((Internal.Id7 >> 20) & 0xF); readonly get => (LogicOp)((Internal.Id6 >> 20) & 0xF);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
} }
public CompareOp DepthCompareOp public CompareOp DepthCompareOp
{ {
readonly get => (CompareOp)((Internal.Id7 >> 24) & 0x7); readonly get => (CompareOp)((Internal.Id6 >> 24) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
} }
public StencilOp StencilFrontFailOp public StencilOp StencilFrontFailOp
{ {
readonly get => (StencilOp)((Internal.Id7 >> 27) & 0x7); readonly get => (StencilOp)((Internal.Id6 >> 27) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
} }
public StencilOp StencilFrontPassOp public StencilOp StencilFrontPassOp
{ {
readonly get => (StencilOp)((Internal.Id7 >> 30) & 0x7); readonly get => (StencilOp)((Internal.Id6 >> 30) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
} }
public StencilOp StencilFrontDepthFailOp public StencilOp StencilFrontDepthFailOp
{ {
readonly get => (StencilOp)((Internal.Id7 >> 33) & 0x7); readonly get => (StencilOp)((Internal.Id6 >> 33) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
} }
public CompareOp StencilFrontCompareOp public CompareOp StencilFrontCompareOp
{ {
readonly get => (CompareOp)((Internal.Id7 >> 36) & 0x7); readonly get => (CompareOp)((Internal.Id6 >> 36) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
} }
public StencilOp StencilBackFailOp public StencilOp StencilBackFailOp
{ {
readonly get => (StencilOp)((Internal.Id7 >> 39) & 0x7); readonly get => (StencilOp)((Internal.Id6 >> 39) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39); set => Internal.Id6 = (Internal.Id6 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
} }
public StencilOp StencilBackPassOp public StencilOp StencilBackPassOp
{ {
readonly get => (StencilOp)((Internal.Id7 >> 42) & 0x7); readonly get => (StencilOp)((Internal.Id6 >> 42) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42); set => Internal.Id6 = (Internal.Id6 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
} }
public StencilOp StencilBackDepthFailOp public StencilOp StencilBackDepthFailOp
{ {
readonly get => (StencilOp)((Internal.Id7 >> 45) & 0x7); readonly get => (StencilOp)((Internal.Id6 >> 45) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45); set => Internal.Id6 = (Internal.Id6 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
} }
public CompareOp StencilBackCompareOp public CompareOp StencilBackCompareOp
{ {
readonly get => (CompareOp)((Internal.Id7 >> 48) & 0x7); readonly get => (CompareOp)((Internal.Id6 >> 48) & 0x7);
set => Internal.Id7 = (Internal.Id7 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48); set => Internal.Id6 = (Internal.Id6 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
} }
public CullModeFlags CullMode public CullModeFlags CullMode
{ {
readonly get => (CullModeFlags)((Internal.Id7 >> 51) & 0x3); readonly get => (CullModeFlags)((Internal.Id6 >> 51) & 0x3);
set => Internal.Id7 = (Internal.Id7 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51); set => Internal.Id6 = (Internal.Id6 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
} }
public bool PrimitiveRestartEnable public bool PrimitiveRestartEnable
{ {
readonly get => ((Internal.Id7 >> 53) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 53) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53); set => Internal.Id6 = (Internal.Id6 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
} }
public bool DepthClampEnable public bool DepthClampEnable
{ {
readonly get => ((Internal.Id7 >> 54) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 54) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54); set => Internal.Id6 = (Internal.Id6 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
} }
public bool RasterizerDiscardEnable public bool RasterizerDiscardEnable
{ {
readonly get => ((Internal.Id7 >> 55) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 55) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55); set => Internal.Id6 = (Internal.Id6 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
} }
public FrontFace FrontFace public FrontFace FrontFace
{ {
readonly get => (FrontFace)((Internal.Id7 >> 56) & 0x1); readonly get => (FrontFace)((Internal.Id6 >> 56) & 0x1);
set => Internal.Id7 = (Internal.Id7 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56); set => Internal.Id6 = (Internal.Id6 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
} }
public bool DepthBiasEnable public bool DepthBiasEnable
{ {
readonly get => ((Internal.Id7 >> 57) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 57) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57); set => Internal.Id6 = (Internal.Id6 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
} }
public bool DepthTestEnable public bool DepthTestEnable
{ {
readonly get => ((Internal.Id7 >> 58) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 58) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58); set => Internal.Id6 = (Internal.Id6 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
} }
public bool DepthWriteEnable public bool DepthWriteEnable
{ {
readonly get => ((Internal.Id7 >> 59) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 59) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59); set => Internal.Id6 = (Internal.Id6 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
} }
public bool DepthBoundsTestEnable public bool DepthBoundsTestEnable
{ {
readonly get => ((Internal.Id7 >> 60) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 60) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60); set => Internal.Id6 = (Internal.Id6 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
} }
public bool StencilTestEnable public bool StencilTestEnable
{ {
readonly get => ((Internal.Id7 >> 61) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 61) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61); set => Internal.Id6 = (Internal.Id6 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
} }
public bool LogicOpEnable public bool LogicOpEnable
{ {
readonly get => ((Internal.Id7 >> 62) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 62) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62); set => Internal.Id6 = (Internal.Id6 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
} }
public bool HasDepthStencil public bool HasDepthStencil
{ {
readonly get => ((Internal.Id7 >> 63) & 0x1) != 0UL; readonly get => ((Internal.Id6 >> 63) & 0x1) != 0UL;
set => Internal.Id7 = (Internal.Id7 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63); set => Internal.Id6 = (Internal.Id6 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
} }
public uint PatchControlPoints public uint PatchControlPoints
{ {
readonly get => (uint)((Internal.Id8 >> 0) & 0xFFFFFFFF); readonly get => (uint)((Internal.Id7 >> 0) & 0xFFFFFFFF);
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFF00000000) | ((ulong)value << 0); set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
} }
public uint SamplesCount public uint SamplesCount
{ {
readonly get => (uint)((Internal.Id8 >> 32) & 0xFFFFFFFF); readonly get => (uint)((Internal.Id7 >> 32) & 0xFFFFFFFF);
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFF) | ((ulong)value << 32); set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)value << 32);
} }
public bool AlphaToCoverageEnable public bool AlphaToCoverageEnable
{ {
readonly get => ((Internal.Id9 >> 0) & 0x1) != 0UL; readonly get => ((Internal.Id8 >> 0) & 0x1) != 0UL;
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0); set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
} }
public bool AlphaToOneEnable public bool AlphaToOneEnable
{ {
readonly get => ((Internal.Id9 >> 1) & 0x1) != 0UL; readonly get => ((Internal.Id8 >> 1) & 0x1) != 0UL;
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1); set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
} }
public bool AdvancedBlendSrcPreMultiplied public bool AdvancedBlendSrcPreMultiplied
{ {
readonly get => ((Internal.Id9 >> 2) & 0x1) != 0UL; readonly get => ((Internal.Id8 >> 2) & 0x1) != 0UL;
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2); set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
} }
public bool AdvancedBlendDstPreMultiplied public bool AdvancedBlendDstPreMultiplied
{ {
readonly get => ((Internal.Id9 >> 3) & 0x1) != 0UL; readonly get => ((Internal.Id8 >> 3) & 0x1) != 0UL;
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3); set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
} }
public BlendOverlapEXT AdvancedBlendOverlap public BlendOverlapEXT AdvancedBlendOverlap
{ {
readonly get => (BlendOverlapEXT)((Internal.Id9 >> 4) & 0x3); readonly get => (BlendOverlapEXT)((Internal.Id8 >> 4) & 0x3);
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4); set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
} }
public bool DepthMode public bool DepthMode
{ {
readonly get => ((Internal.Id9 >> 6) & 0x1) != 0UL; readonly get => ((Internal.Id8 >> 6) & 0x1) != 0UL;
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6); set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
} }
public bool HasTessellationControlShader; public bool HasTessellationControlShader;
@ -408,8 +396,6 @@ namespace Ryujinx.Graphics.Vulkan
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0]) fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0])
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions2 = &_vertexAttributeDescriptions2[0]) fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions2 = &_vertexAttributeDescriptions2[0])
fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0]) fixed (VertexInputBindingDescription* pVertexBindingDescriptions = &Internal.VertexBindingDescriptions[0])
fixed (Viewport* pViewports = &Internal.Viewports[0])
fixed (Rect2D* pScissors = &Internal.Scissors[0])
fixed (PipelineColorBlendAttachmentState* pColorBlendAttachmentState = &Internal.ColorBlendAttachmentState[0]) fixed (PipelineColorBlendAttachmentState* pColorBlendAttachmentState = &Internal.ColorBlendAttachmentState[0])
{ {
var vertexInputState = new PipelineVertexInputStateCreateInfo var vertexInputState = new PipelineVertexInputStateCreateInfo
@ -472,18 +458,13 @@ namespace Ryujinx.Graphics.Vulkan
CullMode = CullMode, CullMode = CullMode,
FrontFace = FrontFace, FrontFace = FrontFace,
DepthBiasEnable = DepthBiasEnable, DepthBiasEnable = DepthBiasEnable,
DepthBiasClamp = DepthBiasClamp,
DepthBiasConstantFactor = DepthBiasConstantFactor,
DepthBiasSlopeFactor = DepthBiasSlopeFactor,
}; };
var viewportState = new PipelineViewportStateCreateInfo var viewportState = new PipelineViewportStateCreateInfo
{ {
SType = StructureType.PipelineViewportStateCreateInfo, SType = StructureType.PipelineViewportStateCreateInfo,
ViewportCount = ViewportsCount, ViewportCount = ViewportsCount,
PViewports = pViewports,
ScissorCount = ScissorsCount, ScissorCount = ScissorsCount,
PScissors = pScissors,
}; };
if (gd.Capabilities.SupportsDepthClipControl) if (gd.Capabilities.SupportsDepthClipControl)
@ -511,19 +492,13 @@ namespace Ryujinx.Graphics.Vulkan
StencilFrontFailOp, StencilFrontFailOp,
StencilFrontPassOp, StencilFrontPassOp,
StencilFrontDepthFailOp, StencilFrontDepthFailOp,
StencilFrontCompareOp, StencilFrontCompareOp);
StencilFrontCompareMask,
StencilFrontWriteMask,
StencilFrontReference);
var stencilBack = new StencilOpState( var stencilBack = new StencilOpState(
StencilBackFailOp, StencilBackFailOp,
StencilBackPassOp, StencilBackPassOp,
StencilBackDepthFailOp, StencilBackDepthFailOp,
StencilBackCompareOp, StencilBackCompareOp);
StencilBackCompareMask,
StencilBackWriteMask,
StencilBackReference);
var depthStencilState = new PipelineDepthStencilStateCreateInfo var depthStencilState = new PipelineDepthStencilStateCreateInfo
{ {
@ -531,12 +506,10 @@ namespace Ryujinx.Graphics.Vulkan
DepthTestEnable = DepthTestEnable, DepthTestEnable = DepthTestEnable,
DepthWriteEnable = DepthWriteEnable, DepthWriteEnable = DepthWriteEnable,
DepthCompareOp = DepthCompareOp, DepthCompareOp = DepthCompareOp,
DepthBoundsTestEnable = DepthBoundsTestEnable, DepthBoundsTestEnable = false,
StencilTestEnable = StencilTestEnable, StencilTestEnable = StencilTestEnable,
Front = stencilFront, Front = stencilFront,
Back = stencilBack, Back = stencilBack,
MinDepthBounds = MinDepthBounds,
MaxDepthBounds = MaxDepthBounds,
}; };
uint blendEnables = 0; uint blendEnables = 0;
@ -591,22 +564,21 @@ namespace Ryujinx.Graphics.Vulkan
} }
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState; bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
int dynamicStatesCount = supportsExtDynamicState ? 9 : 8; int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount]; DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
dynamicStates[0] = DynamicState.Viewport; dynamicStates[0] = DynamicState.Viewport;
dynamicStates[1] = DynamicState.Scissor; dynamicStates[1] = DynamicState.Scissor;
dynamicStates[2] = DynamicState.DepthBias; dynamicStates[2] = DynamicState.DepthBias;
dynamicStates[3] = DynamicState.DepthBounds; dynamicStates[3] = DynamicState.StencilCompareMask;
dynamicStates[4] = DynamicState.StencilCompareMask; dynamicStates[4] = DynamicState.StencilWriteMask;
dynamicStates[5] = DynamicState.StencilWriteMask; dynamicStates[5] = DynamicState.StencilReference;
dynamicStates[6] = DynamicState.StencilReference; dynamicStates[6] = DynamicState.BlendConstants;
dynamicStates[7] = DynamicState.BlendConstants;
if (supportsExtDynamicState) if (supportsExtDynamicState)
{ {
dynamicStates[8] = DynamicState.VertexInputBindingStrideExt; dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
} }
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
@ -632,7 +604,6 @@ namespace Ryujinx.Graphics.Vulkan
PDynamicState = &pipelineDynamicStateCreateInfo, PDynamicState = &pipelineDynamicStateCreateInfo,
Layout = PipelineLayout, Layout = PipelineLayout,
RenderPass = renderPass, RenderPass = renderPass,
BasePipelineIndex = -1,
}; };
Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle); Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle);

View File

@ -17,20 +17,17 @@ namespace Ryujinx.Graphics.Vulkan
public ulong Id4; public ulong Id4;
public ulong Id5; public ulong Id5;
public ulong Id6; public ulong Id6;
public ulong Id7; public ulong Id7;
public ulong Id8; public ulong Id8;
public ulong Id9;
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF); private readonly uint VertexAttributeDescriptionsCount => (byte)((Id5 >> 38) & 0xFF);
private readonly uint VertexBindingDescriptionsCount => (byte)((Id6 >> 46) & 0xFF); private readonly uint VertexBindingDescriptionsCount => (byte)((Id5 >> 46) & 0xFF);
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id7 >> 8) & 0xFF); private readonly uint ColorBlendAttachmentStateCount => (byte)((Id6 >> 8) & 0xFF);
private readonly bool HasDepthStencil => ((Id7 >> 63) & 0x1) != 0UL; private readonly bool HasDepthStencil => ((Id6 >> 63) & 0x1) != 0UL;
public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions; public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions;
public Array33<VertexInputBindingDescription> VertexBindingDescriptions; public Array33<VertexInputBindingDescription> VertexBindingDescriptions;
public Array16<Viewport> Viewports;
public Array16<Rect2D> Scissors;
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState; public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
public Array9<Format> AttachmentFormats; public Array9<Format> AttachmentFormats;
public uint AttachmentIntegerFormatMask; public uint AttachmentIntegerFormatMask;
@ -45,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)) || if (!Unsafe.As<ulong, Vector256<byte>>(ref Id0).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id0)) ||
!Unsafe.As<ulong, Vector256<byte>>(ref Id4).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id4)) || !Unsafe.As<ulong, Vector256<byte>>(ref Id4).Equals(Unsafe.As<ulong, Vector256<byte>>(ref other.Id4)) ||
!Unsafe.As<ulong, Vector128<byte>>(ref Id8).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id8))) !Unsafe.As<ulong, Vector128<byte>>(ref Id7).Equals(Unsafe.As<ulong, Vector128<byte>>(ref other.Id7)))
{ {
return false; return false;
} }
@ -88,8 +85,7 @@ namespace Ryujinx.Graphics.Vulkan
Id5 * 23 ^ Id5 * 23 ^
Id6 * 23 ^ Id6 * 23 ^
Id7 * 23 ^ Id7 * 23 ^
Id8 * 23 ^ Id8 * 23;
Id9 * 23;
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++) for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
{ {

View File

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

View File

@ -604,9 +604,14 @@ namespace Ryujinx.Graphics.Vulkan
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew); return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
} }
public Auto<DescriptorSetCollection> GetNewManualDescriptorSetCollection(int commandBufferIndex, int setIndex, out int cacheIndex) public Auto<DescriptorSetCollection> 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) public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex)

View File

@ -2,11 +2,10 @@ using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
class TextureArray : ITextureArray class TextureArray : ResourceArray, ITextureArray
{ {
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
@ -25,19 +24,11 @@ namespace Ryujinx.Graphics.Vulkan
private HashSet<TextureStorage> _storages; private HashSet<TextureStorage> _storages;
private DescriptorSet[] _cachedDescriptorSets;
private int _cachedCommandBufferIndex; private int _cachedCommandBufferIndex;
private int _cachedSubmissionCount; private int _cachedSubmissionCount;
private ShaderCollection _cachedDscProgram;
private int _cachedDscSetIndex;
private int _cachedDscIndex;
private readonly bool _isBuffer; private readonly bool _isBuffer;
private int _bindCount;
public TextureArray(VulkanRenderer gd, int size, bool isBuffer) public TextureArray(VulkanRenderer gd, int size, bool isBuffer)
{ {
_gd = gd; _gd = gd;
@ -113,12 +104,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_cachedCommandBufferIndex = -1; _cachedCommandBufferIndex = -1;
_storages = null; _storages = null;
_cachedDescriptorSets = null; SetDirty(_gd);
if (_bindCount != 0)
{
_gd.PipelineInternal.ForceTextureDirty();
}
} }
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags) public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
@ -211,7 +197,7 @@ namespace Ryujinx.Graphics.Vulkan
TextureView dummyTexture, TextureView dummyTexture,
SamplerHolder dummySampler) 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. // 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); 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]; DescriptorSetTemplate template = program.Templates[setIndex];
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template); DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
@ -243,24 +226,9 @@ namespace Ryujinx.Graphics.Vulkan
tu.Push(GetBufferViews(cbs)); tu.Push(GetBufferViews(cbs));
} }
var sets = dsc.GetSets();
templateUpdater.Commit(_gd, device, sets[0]); templateUpdater.Commit(_gd, device, sets[0]);
_cachedDescriptorSets = sets;
_cachedDscProgram = program;
_cachedDscSetIndex = setIndex;
return sets; return sets;
} }
public void IncrementBindCount()
{
_bindCount++;
}
public void DecrementBindCount()
{
int newBindCount = --_bindCount;
Debug.Assert(newBindCount >= 0);
}
} }
} }

View File

@ -233,7 +233,7 @@ namespace Ryujinx.UI.Windows
reader.ReadInt64(); // Padding reader.ReadInt64(); // Padding
byte[] input = new byte[stream.Length - stream.Position]; byte[] input = new byte[stream.Length - stream.Position];
stream.Read(input, 0, input.Length); stream.ReadExactly(input, 0, input.Length);
long inputOffset = 0; long inputOffset = 0;

View File

@ -616,7 +616,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
} }
ArrayPool<KSynchronizationObject>.Shared.Return(syncObjsArray); ArrayPool<KSynchronizationObject>.Shared.Return(syncObjsArray, true);
return result; return result;
} }
@ -1546,8 +1546,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
#pragma warning disable CA1822 // Mark member as static #pragma warning disable CA1822 // Mark member as static
public Result SetProcessMemoryPermission( public Result SetProcessMemoryPermission(
int handle, int handle,
[PointerSized] ulong src, ulong src,
[PointerSized] ulong size, ulong size,
KMemoryPermission permission) KMemoryPermission permission)
{ {
if (!PageAligned(src)) if (!PageAligned(src))

View File

@ -104,7 +104,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
} }
} }
ArrayPool<LinkedListNode<KThread>>.Shared.Return(syncNodesArray); ArrayPool<LinkedListNode<KThread>>.Shared.Return(syncNodesArray, true);
} }
_context.CriticalSection.Leave(); _context.CriticalSection.Leave();

View File

@ -65,7 +65,7 @@ namespace Ryujinx.UI.App.Common
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName); Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
byte[] resourceByteArray = new byte[resourceStream.Length]; byte[] resourceByteArray = new byte[resourceStream.Length];
resourceStream.Read(resourceByteArray); resourceStream.ReadExactly(resourceByteArray);
return resourceByteArray; return resourceByteArray;
} }

View File

@ -151,7 +151,7 @@ namespace Ryujinx.Ava.UI.ViewModels
reader.ReadInt64(); // Padding reader.ReadInt64(); // Padding
byte[] input = new byte[stream.Length - stream.Position]; byte[] input = new byte[stream.Length - stream.Position];
stream.Read(input, 0, input.Length); stream.ReadExactly(input, 0, input.Length);
uint inputOffset = 0; uint inputOffset = 0;