Compare commits

..

13 Commits

Author SHA1 Message Date
gdkchan
cfc75d7e78 Disable descriptor set template updates for buffer textures on Adreno (#7002)
* Do not use template updates for buffer textures and buffer images

* No need to do it for images

* Simply buffer texture existence check

* Pipeline is now unused on DescriptorSetUpdater
2024-07-07 19:19:55 -03:00
gdkchan
c525d7d9a9 Force Vulkan swapchain re-creation when window size changes (#7003) 2024-07-07 19:02:11 -03:00
sunshineinabox
1a0a351a15 Resolve some Vulkan validation errors (#6915)
* Fix some validation errors

* Whitespace correction

* Resolve some runtime validation errors.

* Whitespace

* Properly fix usage realted validation error by setting Extended Usage image creation flag.

* Only if supported

* Remove checking extension for features that are core functionality of Vulkan 1.2
2024-06-26 09:21:44 -03:00
TSRBerry
bd3335c143 Make sure the string is long enough before performing basic trim (#6982) 2024-06-26 11:27:23 +02:00
dependabot[bot]
a94445b23e nuget: bump Microsoft.IdentityModel.JsonWebTokens from 7.6.0 to 7.6.2 (#6965)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 7.6.0 to 7.6.2.
- [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/7.6.2/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/7.6.0...7.6.2)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  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>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
2024-06-26 10:45:51 +02:00
Rafa
0c3421973c SetProcessMemoryPermission address and size are always 64-bit (#6977) 2024-06-25 09:40:53 +02:00
gdkchan
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
gdkchan
d25a084858 JIT: Ensure entry block has no predecessors on RegisterUsage pass (#6951) 2024-06-19 09:25:47 -03:00
jhorv
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
gdkchan
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
jhorv
5a878ae9af replace ByteMemoryPool use with MemoryOwner<byte> and SpanOwner<byte> (#6911) 2024-06-15 23:00:13 +02:00
dependabot[bot]
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
gdkchan
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
52 changed files with 727 additions and 202 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.6.0" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.2" />
<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

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

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

@@ -124,7 +124,7 @@ namespace Ryujinx.Common.Memory
if (array is not null) if (array is not null)
{ {
ArrayPool<T>.Shared.Return(array); ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
} }
} }

View File

@@ -108,7 +108,7 @@ namespace Ryujinx.Common.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose() public void Dispose()
{ {
ArrayPool<T>.Shared.Return(_array); 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

@@ -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 = 6852; 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

@@ -141,16 +141,16 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return true; return true;
} }
private static bool IsBindlessAccessAllowed(Operand nvHandle) private static bool IsBindlessAccessAllowed(Operand bindlessHandle)
{ {
if (nvHandle.Type == OperandType.ConstantBuffer) if (bindlessHandle.Type == OperandType.ConstantBuffer)
{ {
// Bindless access with handles from constant buffer is allowed. // Bindless access with handles from constant buffer is allowed.
return true; return true;
} }
if (nvHandle.AsgOp is not Operation handleOp || if (bindlessHandle.AsgOp is not Operation handleOp ||
handleOp.Inst != Instruction.Load || handleOp.Inst != Instruction.Load ||
(handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer)) (handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
{ {
@@ -300,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

@@ -103,12 +103,19 @@ namespace Ryujinx.Graphics.Vulkan
usage |= BufferUsageFlags.IndirectBufferBit; usage |= BufferUsageFlags.IndirectBufferBit;
} }
var externalMemoryBuffer = new ExternalMemoryBufferCreateInfo
{
SType = StructureType.ExternalMemoryBufferCreateInfo,
HandleTypes = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
};
var bufferCreateInfo = new BufferCreateInfo var bufferCreateInfo = new BufferCreateInfo
{ {
SType = StructureType.BufferCreateInfo, SType = StructureType.BufferCreateInfo,
Size = (ulong)size, Size = (ulong)size,
Usage = usage, Usage = usage,
SharingMode = SharingMode.Exclusive, SharingMode = SharingMode.Exclusive,
PNext = &externalMemoryBuffer,
}; };
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError(); gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();

View File

@@ -73,7 +73,6 @@ namespace Ryujinx.Graphics.Vulkan
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
private readonly PipelineBase _pipeline;
private ShaderCollection _program; private ShaderCollection _program;
private readonly BufferRef[] _uniformBufferRefs; private readonly BufferRef[] _uniformBufferRefs;
@@ -125,11 +124,10 @@ namespace Ryujinx.Graphics.Vulkan
private readonly TextureView _dummyTexture; private readonly TextureView _dummyTexture;
private readonly SamplerHolder _dummySampler; private readonly SamplerHolder _dummySampler;
public DescriptorSetUpdater(VulkanRenderer gd, Device device, PipelineBase pipeline) public DescriptorSetUpdater(VulkanRenderer gd, Device device)
{ {
_gd = gd; _gd = gd;
_device = device; _device = device;
_pipeline = pipeline;
// Some of the bindings counts needs to be multiplied by 2 because we have buffer and // Some of the bindings counts needs to be multiplied by 2 because we have buffer and
// regular textures/images interleaved on the same descriptor set. // regular textures/images interleaved on the same descriptor set.
@@ -291,8 +289,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 +310,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);
} }
} }
} }
@@ -651,7 +682,14 @@ namespace Ryujinx.Graphics.Vulkan
if (_dirty.HasFlag(DirtyFlags.Texture)) if (_dirty.HasFlag(DirtyFlags.Texture))
{ {
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp); if (program.UpdateTexturesWithoutTemplate)
{
UpdateAndBindTexturesWithoutTemplate(cbs, program, pbp);
}
else
{
UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
}
} }
if (_dirty.HasFlag(DirtyFlags.Image)) if (_dirty.HasFlag(DirtyFlags.Image))
@@ -885,31 +923,84 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty); _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
} }
private unsafe void UpdateBuffers( private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
CommandBufferScoped cbs,
PipelineBindPoint pbp,
int baseBinding,
ReadOnlySpan<DescriptorBufferInfo> bufferInfo,
DescriptorType type)
{ {
if (bufferInfo.Length == 0) int setIndex = PipelineBase.TextureSetIndex;
var bindingSegments = program.BindingSegments[setIndex];
if (bindingSegments.Length == 0)
{ {
return; return;
} }
fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo) if (_updateDescriptorCacheCbIndex)
{ {
var writeDescriptorSet = new WriteDescriptorSet _updateDescriptorCacheCbIndex = false;
{ program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
SType = StructureType.WriteDescriptorSet,
DstBinding = (uint)baseBinding,
DescriptorType = type,
DescriptorCount = (uint)bufferInfo.Length,
PBufferInfo = pBufferInfo,
};
_gd.PushDescriptorApi.CmdPushDescriptorSet(cbs.CommandBuffer, pbp, _program.PipelineLayout, 0, 1, &writeDescriptorSet);
} }
var dsc = program.GetNewDescriptorSetCollection(setIndex, out _).Get(cbs);
foreach (ResourceBindingSegment segment in bindingSegments)
{
int binding = segment.Binding;
int count = segment.Count;
if (!segment.IsArray)
{
if (segment.Type != ResourceType.BufferTexture)
{
Span<DescriptorImageInfo> textures = _textures;
for (int i = 0; i < count; i++)
{
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
}
if (texture.Sampler.Handle == 0)
{
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
}
}
dsc.UpdateImages(0, binding, textures[..count], DescriptorType.CombinedImageSampler);
}
else
{
Span<BufferView> bufferTextures = _bufferTextures;
for (int i = 0; i < count; i++)
{
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs, false) ?? default;
}
dsc.UpdateBufferImages(0, binding, bufferTextures[..count], DescriptorType.UniformTexelBuffer);
}
}
else
{
if (segment.Type != ResourceType.BufferTexture)
{
dsc.UpdateImages(0, binding, _textureArrayRefs[binding].Array.GetImageInfos(_gd, cbs, _dummyTexture, _dummySampler), DescriptorType.CombinedImageSampler);
}
else
{
dsc.UpdateBufferImages(0, binding, _textureArrayRefs[binding].Array.GetBufferViews(cbs), DescriptorType.UniformTexelBuffer);
}
}
}
var sets = dsc.GetSets();
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

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

@@ -105,7 +105,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError(); gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device, this); _descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
_vertexBufferUpdater = new VertexBufferUpdater(gd); _vertexBufferUpdater = new VertexBufferUpdater(gd);
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers]; _transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];

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

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

@@ -23,6 +23,8 @@ namespace Ryujinx.Graphics.Vulkan
public bool IsCompute { get; } public bool IsCompute { get; }
public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0; public bool HasTessellationControlShader => (Stages & (1u << 3)) != 0;
public bool UpdateTexturesWithoutTemplate { get; }
public uint Stages { get; } public uint Stages { get; }
public ResourceBindingSegment[][] ClearSegments { get; } public ResourceBindingSegment[][] ClearSegments { get; }
@@ -127,9 +129,12 @@ namespace Ryujinx.Graphics.Vulkan
Stages = stages; Stages = stages;
ClearSegments = BuildClearSegments(sets); ClearSegments = BuildClearSegments(sets);
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages); BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
Templates = BuildTemplates(usePushDescriptors); Templates = BuildTemplates(usePushDescriptors);
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
UpdateTexturesWithoutTemplate = gd.Vendor == Vendor.Qualcomm && usesBufferTextures;
_compileTask = Task.CompletedTask; _compileTask = Task.CompletedTask;
_firstBackgroundUse = false; _firstBackgroundUse = false;
} }
@@ -280,8 +285,10 @@ namespace Ryujinx.Graphics.Vulkan
return segments; return segments;
} }
private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages) private static ResourceBindingSegment[][] BuildBindingSegments(ReadOnlyCollection<ResourceUsageCollection> setUsages, out bool usesBufferTextures)
{ {
usesBufferTextures = false;
ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][]; ResourceBindingSegment[][] segments = new ResourceBindingSegment[setUsages.Count][];
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++) for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
@@ -295,6 +302,11 @@ namespace Ryujinx.Graphics.Vulkan
{ {
ResourceUsage usage = setUsages[setIndex].Usages[index]; ResourceUsage usage = setUsages[setIndex].Usages[index];
if (usage.Type == ResourceType.BufferTexture)
{
usesBufferTextures = true;
}
if (currentUsage.Binding + currentCount != usage.Binding || if (currentUsage.Binding + currentCount != usage.Binding ||
currentUsage.Type != usage.Type || currentUsage.Type != usage.Type ||
currentUsage.Stages != usage.Stages || currentUsage.Stages != usage.Stages ||
@@ -604,9 +616,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

@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample); var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
var flags = ImageCreateFlags.CreateMutableFormatBit; var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
// This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube. // This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray; bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;

View File

@@ -100,7 +100,7 @@ namespace Ryujinx.Graphics.Vulkan
unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags) unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags)
{ {
var usage = new ImageViewUsageCreateInfo var imageViewUsage = new ImageViewUsageCreateInfo
{ {
SType = StructureType.ImageViewUsageCreateInfo, SType = StructureType.ImageViewUsageCreateInfo,
Usage = usageFlags, Usage = usageFlags,
@@ -114,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
Format = format, Format = format,
Components = cm, Components = cm,
SubresourceRange = sr, SubresourceRange = sr,
PNext = &usage, PNext = &imageViewUsage,
}; };
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError(); gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
@@ -123,7 +123,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit; ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit;
if (info.Format.IsImageCompatible()) if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample()))
{ {
shaderUsage |= ImageUsageFlags.StorageBit; shaderUsage |= ImageUsageFlags.StorageBit;
} }
@@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
else else
{ {
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth); subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, 1, (uint)firstLayer, (uint)info.Depth);
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage); _imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage);
} }

View File

@@ -42,6 +42,8 @@ namespace Ryujinx.Graphics.Vulkan
"VK_EXT_depth_clip_control", "VK_EXT_depth_clip_control",
"VK_KHR_portability_subset", // As per spec, we should enable this if present. "VK_KHR_portability_subset", // As per spec, we should enable this if present.
"VK_EXT_4444_formats", "VK_EXT_4444_formats",
"VK_KHR_8bit_storage",
"VK_KHR_maintenance2",
}; };
private static readonly string[] _requiredExtensions = { private static readonly string[] _requiredExtensions = {
@@ -355,6 +357,14 @@ namespace Ryujinx.Graphics.Vulkan
features2.PNext = &supportedFeaturesDepthClipControl; features2.PNext = &supportedFeaturesDepthClipControl;
} }
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
{
SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = features2.PNext,
};
features2.PNext = &supportedPhysicalDeviceVulkan12Features;
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2); api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
var supportedFeatures = features2.Features; var supportedFeatures = features2.Features;
@@ -382,6 +392,7 @@ namespace Ryujinx.Graphics.Vulkan
TessellationShader = supportedFeatures.TessellationShader, TessellationShader = supportedFeatures.TessellationShader,
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics, VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
RobustBufferAccess = useRobustBufferAccess, RobustBufferAccess = useRobustBufferAccess,
SampleRateShading = supportedFeatures.SampleRateShading,
}; };
void* pExtendedFeatures = null; void* pExtendedFeatures = null;
@@ -451,9 +462,11 @@ namespace Ryujinx.Graphics.Vulkan
{ {
SType = StructureType.PhysicalDeviceVulkan12Features, SType = StructureType.PhysicalDeviceVulkan12Features,
PNext = pExtendedFeatures, PNext = pExtendedFeatures,
DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"), DescriptorIndexing = supportedPhysicalDeviceVulkan12Features.DescriptorIndexing,
DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName), DrawIndirectCount = supportedPhysicalDeviceVulkan12Features.DrawIndirectCount,
UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout"), UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout,
UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess,
StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess,
}; };
pExtendedFeatures = &featuresVk12; pExtendedFeatures = &featuresVk12;

View File

@@ -623,7 +623,8 @@ namespace Ryujinx.Graphics.Vulkan
public override void SetSize(int width, int height) public override void SetSize(int width, int height)
{ {
// Not needed as we can get the size from the surface. // We don't need to use width and height as we can get the size from the surface.
_swapchainIsDirty = true;
} }
public override void ChangeVSyncMode(bool vsyncEnabled) public override void ChangeVSyncMode(bool vsyncEnabled)

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

@@ -104,8 +104,13 @@ namespace Ryujinx.UI.Common
// Find the length to trim the string to guarantee we have space for the trailing ellipsis. // Find the length to trim the string to guarantee we have space for the trailing ellipsis.
int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis); int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis);
// Basic trim to best case scenario of 1 byte characters. // Make sure the string is long enough to perform the basic trim.
input = input[..trimLimit]; // Amount of bytes != Length of the string
if (input.Length > trimLimit)
{
// Basic trim to best case scenario of 1 byte characters.
input = input[..trimLimit];
}
while (Encoding.UTF8.GetByteCount(input) > trimLimit) while (Encoding.UTF8.GetByteCount(input) > trimLimit)
{ {