Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
2cb80f37d4 | |||
827069e784 | |||
1a919e99b2 | |||
f77bebac80 | |||
6fbf279fac | |||
344f4f52c1 | |||
eb212aa91b | |||
a6dbb2ad2b | |||
595e514f18 | |||
07435ad844 | |||
1668ba913f | |||
a830eb666b | |||
cfc75d7e78 | |||
c525d7d9a9 | |||
1a0a351a15 | |||
bd3335c143 | |||
a94445b23e | |||
0c3421973c | |||
0afa8f2c14 | |||
d25a084858 | |||
311ca3c3f1 | |||
3193ef1083 | |||
5a878ae9af | |||
1828bc949e | |||
c0f2491eae | |||
d7c6474729 | |||
1ecc8fbc3b | |||
888402ecaf | |||
971d24aef0 | |||
c41fddd25e |
@ -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.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" />
|
||||||
|
@ -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;
|
||||||
|
@ -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()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
140
src/Ryujinx.Common/Memory/MemoryOwner.cs
Normal file
140
src/Ryujinx.Common/Memory/MemoryOwner.cs
Normal 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
src/Ryujinx.Common/Memory/SpanOwner.cs
Normal file
114
src/Ryujinx.Common/Memory/SpanOwner.cs
Normal 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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
|
@ -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()));
|
||||||
|
@ -74,13 +74,15 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
public int ArrayLength { get; }
|
public int ArrayLength { get; }
|
||||||
public ResourceType Type { get; }
|
public ResourceType Type { get; }
|
||||||
public ResourceStages Stages { get; }
|
public ResourceStages Stages { get; }
|
||||||
|
public bool Write { get; }
|
||||||
|
|
||||||
public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages)
|
public ResourceUsage(int binding, int arrayLength, ResourceType type, ResourceStages stages, bool write)
|
||||||
{
|
{
|
||||||
Binding = binding;
|
Binding = binding;
|
||||||
ArrayLength = arrayLength;
|
ArrayLength = arrayLength;
|
||||||
Type = type;
|
Type = type;
|
||||||
Stages = stages;
|
Stages = stages;
|
||||||
|
Write = write;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,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);
|
||||||
|
@ -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))
|
||||||
|
@ -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";
|
||||||
|
@ -78,9 +78,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
|
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
|
||||||
|
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
|
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers);
|
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers, true);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
|
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages);
|
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -91,10 +91,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// <param name="setIndex">Resource set index where the resources are used</param>
|
/// <param name="setIndex">Resource set index where the resources are used</param>
|
||||||
/// <param name="start">First binding number</param>
|
/// <param name="start">First binding number</param>
|
||||||
/// <param name="count">Amount of bindings</param>
|
/// <param name="count">Amount of bindings</param>
|
||||||
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
|
/// <param name="write">True if the binding is written from the shader, false otherwise</param>
|
||||||
|
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count, bool write = false)
|
||||||
{
|
{
|
||||||
AddDescriptor(stages, type, setIndex, start, count);
|
AddDescriptor(stages, type, setIndex, start, count);
|
||||||
AddUsage(stages, type, setIndex, start, count);
|
AddUsage(stages, type, setIndex, start, count, write);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -216,11 +217,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
|
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
|
||||||
/// <param name="binding">Binding number where the resource will be bound</param>
|
/// <param name="binding">Binding number where the resource will be bound</param>
|
||||||
/// <param name="count">Number of resources bound at the binding location</param>
|
/// <param name="count">Number of resources bound at the binding location</param>
|
||||||
private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
|
/// <param name="write">True if the binding is written from the shader, false otherwise</param>
|
||||||
|
private void AddUsage(ResourceStages stages, ResourceType type, int setIndex, int binding, int count, bool write = false)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < count; index++)
|
for (int index = 0; index < count; index++)
|
||||||
{
|
{
|
||||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages));
|
_resourceUsages[setIndex].Add(new ResourceUsage(binding + index, 1, type, stages, write));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +240,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
buffer.Binding,
|
buffer.Binding,
|
||||||
1,
|
1,
|
||||||
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
||||||
stages));
|
stages,
|
||||||
|
buffer.Flags.HasFlag(BufferUsageFlags.Write)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +257,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
{
|
{
|
||||||
ResourceType type = GetTextureResourceType(texture, isImage);
|
ResourceType type = GetTextureResourceType(texture, isImage);
|
||||||
|
|
||||||
GetUsages(texture.Set).Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
|
GetUsages(texture.Set).Add(new ResourceUsage(
|
||||||
|
texture.Binding,
|
||||||
|
texture.ArrayLength,
|
||||||
|
type,
|
||||||
|
stages,
|
||||||
|
texture.Flags.HasFlag(TextureUsageFlags.ImageStore)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,5 +63,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,5 +48,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
|
||||||
|
@ -29,7 +29,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
lock (queueLock)
|
lock (queueLock)
|
||||||
{
|
{
|
||||||
_pool = new CommandBufferPool(_gd.Api, _device, queue, queueLock, _gd.QueueFamilyIndex, isLight: true);
|
_pool = new CommandBufferPool(
|
||||||
|
_gd.Api,
|
||||||
|
_device,
|
||||||
|
queue,
|
||||||
|
queueLock,
|
||||||
|
_gd.QueueFamilyIndex,
|
||||||
|
_gd.IsQualcommProprietary,
|
||||||
|
isLight: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
@ -8,22 +9,64 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
private const int MaxBarriersPerCall = 16;
|
private const int MaxBarriersPerCall = 16;
|
||||||
|
|
||||||
|
private const AccessFlags BaseAccess = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit;
|
||||||
|
private const AccessFlags BufferAccess = AccessFlags.IndexReadBit | AccessFlags.VertexAttributeReadBit | AccessFlags.UniformReadBit;
|
||||||
|
private const AccessFlags CommandBufferAccess = AccessFlags.IndirectCommandReadBit;
|
||||||
|
|
||||||
private readonly VulkanRenderer _gd;
|
private readonly VulkanRenderer _gd;
|
||||||
|
|
||||||
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
||||||
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
||||||
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
||||||
|
|
||||||
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
|
private readonly List<BarrierWithStageFlags<MemoryBarrier, int>> _memoryBarriers = new();
|
||||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
|
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier, int>> _bufferBarriers = new();
|
||||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
|
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage>> _imageBarriers = new();
|
||||||
private int _queuedBarrierCount;
|
private int _queuedBarrierCount;
|
||||||
|
|
||||||
|
private enum IncoherentBarrierType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Texture,
|
||||||
|
All,
|
||||||
|
CommandBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
private PipelineStageFlags _incoherentBufferWriteStages;
|
||||||
|
private PipelineStageFlags _incoherentTextureWriteStages;
|
||||||
|
private PipelineStageFlags _extraStages;
|
||||||
|
private IncoherentBarrierType _queuedIncoherentBarrier;
|
||||||
|
|
||||||
public BarrierBatch(VulkanRenderer gd)
|
public BarrierBatch(VulkanRenderer gd)
|
||||||
{
|
{
|
||||||
_gd = gd;
|
_gd = gd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd)
|
||||||
|
{
|
||||||
|
AccessFlags access = BufferAccess;
|
||||||
|
PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit;
|
||||||
|
|
||||||
|
if (gd.TransformFeedbackApi != null)
|
||||||
|
{
|
||||||
|
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||||
|
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gd.IsTBDR)
|
||||||
|
{
|
||||||
|
// Desktop GPUs can transform image barriers into memory barriers.
|
||||||
|
|
||||||
|
access |= AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.ColorAttachmentWriteBit;
|
||||||
|
access |= AccessFlags.DepthStencilAttachmentReadBit | AccessFlags.ColorAttachmentReadBit;
|
||||||
|
|
||||||
|
stages |= PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit;
|
||||||
|
stages |= PipelineStageFlags.ColorAttachmentOutputBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (access, stages);
|
||||||
|
}
|
||||||
|
|
||||||
private readonly record struct StageFlags : IEquatable<StageFlags>
|
private readonly record struct StageFlags : IEquatable<StageFlags>
|
||||||
{
|
{
|
||||||
public readonly PipelineStageFlags Source;
|
public readonly PipelineStageFlags Source;
|
||||||
@ -36,47 +79,130 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct BarrierWithStageFlags<T> where T : unmanaged
|
private readonly struct BarrierWithStageFlags<T, T2> where T : unmanaged
|
||||||
{
|
{
|
||||||
public readonly StageFlags Flags;
|
public readonly StageFlags Flags;
|
||||||
public readonly T Barrier;
|
public readonly T Barrier;
|
||||||
|
public readonly T2 Resource;
|
||||||
|
|
||||||
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
||||||
{
|
{
|
||||||
Flags = flags;
|
Flags = flags;
|
||||||
Barrier = barrier;
|
Barrier = barrier;
|
||||||
|
Resource = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
|
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier, T2 resource)
|
||||||
{
|
{
|
||||||
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
||||||
Barrier = barrier;
|
Barrier = barrier;
|
||||||
|
Resource = resource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
private void QueueBarrier<T, T2>(List<BarrierWithStageFlags<T, T2>> list, T barrier, T2 resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
|
||||||
{
|
{
|
||||||
list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
|
list.Add(new BarrierWithStageFlags<T, T2>(srcStageFlags, dstStageFlags, barrier, resource));
|
||||||
_queuedBarrierCount++;
|
_queuedBarrierCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||||
{
|
{
|
||||||
QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
|
QueueBarrier(_memoryBarriers, barrier, default, srcStageFlags, dstStageFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||||
{
|
{
|
||||||
QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
|
QueueBarrier(_bufferBarriers, barrier, default, srcStageFlags, dstStageFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
public void QueueBarrier(ImageMemoryBarrier barrier, TextureStorage resource, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
|
||||||
{
|
{
|
||||||
QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
|
QueueBarrier(_imageBarriers, barrier, resource, srcStageFlags, dstStageFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public unsafe void FlushMemoryBarrier(ShaderCollection program, bool inRenderPass)
|
||||||
{
|
{
|
||||||
|
if (_queuedIncoherentBarrier > IncoherentBarrierType.None)
|
||||||
|
{
|
||||||
|
// We should emit a memory barrier if there's a write access in the program (current program, or program since last barrier)
|
||||||
|
bool hasTextureWrite = _incoherentTextureWriteStages != PipelineStageFlags.None;
|
||||||
|
bool hasBufferWrite = _incoherentBufferWriteStages != PipelineStageFlags.None;
|
||||||
|
bool hasBufferBarrier = _queuedIncoherentBarrier > IncoherentBarrierType.Texture;
|
||||||
|
|
||||||
|
if (hasTextureWrite || (hasBufferBarrier && hasBufferWrite))
|
||||||
|
{
|
||||||
|
AccessFlags access = BaseAccess;
|
||||||
|
|
||||||
|
PipelineStageFlags stages = inRenderPass ? PipelineStageFlags.AllGraphicsBit : PipelineStageFlags.AllCommandsBit;
|
||||||
|
|
||||||
|
if (hasBufferBarrier && hasBufferWrite)
|
||||||
|
{
|
||||||
|
access |= BufferAccess;
|
||||||
|
|
||||||
|
if (_gd.TransformFeedbackApi != null)
|
||||||
|
{
|
||||||
|
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||||
|
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_queuedIncoherentBarrier == IncoherentBarrierType.CommandBuffer)
|
||||||
|
{
|
||||||
|
access |= CommandBufferAccess;
|
||||||
|
stages |= PipelineStageFlags.DrawIndirectBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryBarrier barrier = new MemoryBarrier()
|
||||||
|
{
|
||||||
|
SType = StructureType.MemoryBarrier,
|
||||||
|
SrcAccessMask = access,
|
||||||
|
DstAccessMask = access
|
||||||
|
};
|
||||||
|
|
||||||
|
QueueBarrier(barrier, stages, stages);
|
||||||
|
|
||||||
|
_incoherentTextureWriteStages = program?.IncoherentTextureWriteStages ?? PipelineStageFlags.None;
|
||||||
|
|
||||||
|
if (_queuedIncoherentBarrier > IncoherentBarrierType.Texture)
|
||||||
|
{
|
||||||
|
if (program != null)
|
||||||
|
{
|
||||||
|
_incoherentBufferWriteStages = program.IncoherentBufferWriteStages | _extraStages;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_incoherentBufferWriteStages = PipelineStageFlags.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_queuedIncoherentBarrier = IncoherentBarrierType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Flush(CommandBufferScoped cbs, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||||
|
{
|
||||||
|
Flush(cbs, null, inRenderPass, rpHolder, endRenderPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Flush(CommandBufferScoped cbs, ShaderCollection program, bool inRenderPass, RenderPassHolder rpHolder, Action endRenderPass)
|
||||||
|
{
|
||||||
|
if (program != null)
|
||||||
|
{
|
||||||
|
_incoherentBufferWriteStages |= program.IncoherentBufferWriteStages | _extraStages;
|
||||||
|
_incoherentTextureWriteStages |= program.IncoherentTextureWriteStages;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushMemoryBarrier(program, inRenderPass);
|
||||||
|
|
||||||
|
if (!inRenderPass && rpHolder != null)
|
||||||
|
{
|
||||||
|
// Render pass is about to begin. Queue any fences that normally interrupt the pass.
|
||||||
|
rpHolder.InsertForcedFences(cbs);
|
||||||
|
}
|
||||||
|
|
||||||
while (_queuedBarrierCount > 0)
|
while (_queuedBarrierCount > 0)
|
||||||
{
|
{
|
||||||
int memoryCount = 0;
|
int memoryCount = 0;
|
||||||
@ -86,20 +212,20 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
bool hasBarrier = false;
|
bool hasBarrier = false;
|
||||||
StageFlags flags = default;
|
StageFlags flags = default;
|
||||||
|
|
||||||
static void AddBarriers<T>(
|
static void AddBarriers<T, T2>(
|
||||||
Span<T> target,
|
Span<T> target,
|
||||||
ref int queuedBarrierCount,
|
ref int queuedBarrierCount,
|
||||||
ref bool hasBarrier,
|
ref bool hasBarrier,
|
||||||
ref StageFlags flags,
|
ref StageFlags flags,
|
||||||
ref int count,
|
ref int count,
|
||||||
List<BarrierWithStageFlags<T>> list) where T : unmanaged
|
List<BarrierWithStageFlags<T, T2>> list) where T : unmanaged
|
||||||
{
|
{
|
||||||
int firstMatch = -1;
|
int firstMatch = -1;
|
||||||
int end = list.Count;
|
int end = list.Count;
|
||||||
|
|
||||||
for (int i = 0; i < list.Count; i++)
|
for (int i = 0; i < list.Count; i++)
|
||||||
{
|
{
|
||||||
BarrierWithStageFlags<T> barrier = list[i];
|
BarrierWithStageFlags<T, T2> barrier = list[i];
|
||||||
|
|
||||||
if (!hasBarrier)
|
if (!hasBarrier)
|
||||||
{
|
{
|
||||||
@ -162,21 +288,60 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insideRenderPass)
|
if (inRenderPass && _imageBarriers.Count > 0)
|
||||||
{
|
{
|
||||||
// Image barriers queued in the batch are meant to be globally scoped,
|
// Image barriers queued in the batch are meant to be globally scoped,
|
||||||
// but inside a render pass they're scoped to just the range of the render pass.
|
// but inside a render pass they're scoped to just the range of the render pass.
|
||||||
|
|
||||||
// On MoltenVK, we just break the rules and always use image barrier.
|
// On MoltenVK, we just break the rules and always use image barrier.
|
||||||
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
||||||
// TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
|
// Generally, we want to avoid this from happening in the future, so flag the texture to immediately
|
||||||
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
|
// emit a barrier whenever the current render pass is bound again.
|
||||||
|
|
||||||
if (!_gd.IsMoltenVk)
|
bool anyIsNonAttachment = false;
|
||||||
|
|
||||||
|
foreach (BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage> barrier in _imageBarriers)
|
||||||
{
|
{
|
||||||
|
// If the binding is an attachment, don't add it as a forced fence.
|
||||||
|
bool isAttachment = rpHolder.ContainsAttachment(barrier.Resource);
|
||||||
|
|
||||||
|
if (!isAttachment)
|
||||||
|
{
|
||||||
|
rpHolder.AddForcedFence(barrier.Resource, barrier.Flags.Dest);
|
||||||
|
anyIsNonAttachment = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_gd.IsTBDR)
|
||||||
|
{
|
||||||
|
if (!_gd.IsMoltenVk)
|
||||||
|
{
|
||||||
|
if (!anyIsNonAttachment)
|
||||||
|
{
|
||||||
|
// This case is a feedback loop. To prevent this from causing an absolute performance disaster,
|
||||||
|
// remove the barriers entirely.
|
||||||
|
// If this is not here, there will be a lot of single draw render passes.
|
||||||
|
// TODO: explicit handling for feedback loops, likely outside this class.
|
||||||
|
|
||||||
|
_queuedBarrierCount -= _imageBarriers.Count;
|
||||||
|
_imageBarriers.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TBDR GPUs are sensitive to barriers, so we need to end the pass to ensure the data is available.
|
||||||
|
// Metal already has hazard tracking so MVK doesn't need this.
|
||||||
|
endRenderPass();
|
||||||
|
inRenderPass = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Generic pipeline memory barriers will work for desktop GPUs.
|
||||||
|
// They do require a few more access flags on the subpass dependency, though.
|
||||||
foreach (var barrier in _imageBarriers)
|
foreach (var barrier in _imageBarriers)
|
||||||
{
|
{
|
||||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
|
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier, int>(
|
||||||
barrier.Flags,
|
barrier.Flags,
|
||||||
new MemoryBarrier()
|
new MemoryBarrier()
|
||||||
{
|
{
|
||||||
@ -190,6 +355,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inRenderPass && _memoryBarriers.Count > 0)
|
||||||
|
{
|
||||||
|
PipelineStageFlags allFlags = PipelineStageFlags.None;
|
||||||
|
|
||||||
|
foreach (var barrier in _memoryBarriers)
|
||||||
|
{
|
||||||
|
allFlags |= barrier.Flags.Dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allFlags.HasFlag(PipelineStageFlags.DrawIndirectBit) || !_gd.SupportsRenderPassBarrier(allFlags))
|
||||||
|
{
|
||||||
|
endRenderPass();
|
||||||
|
inRenderPass = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
|
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
|
||||||
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
||||||
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
||||||
@ -198,14 +379,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
PipelineStageFlags srcStageFlags = flags.Source;
|
PipelineStageFlags srcStageFlags = flags.Source;
|
||||||
|
|
||||||
if (insideRenderPass)
|
if (inRenderPass)
|
||||||
{
|
{
|
||||||
// Inside a render pass, barrier stages can only be from rasterization.
|
// Inside a render pass, barrier stages can only be from rasterization.
|
||||||
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
_gd.Api.CmdPipelineBarrier(
|
_gd.Api.CmdPipelineBarrier(
|
||||||
cb,
|
cbs.CommandBuffer,
|
||||||
srcStageFlags,
|
srcStageFlags,
|
||||||
flags.Dest,
|
flags.Dest,
|
||||||
0,
|
0,
|
||||||
@ -219,6 +400,41 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void QueueIncoherentBarrier(IncoherentBarrierType type)
|
||||||
|
{
|
||||||
|
if (type > _queuedIncoherentBarrier)
|
||||||
|
{
|
||||||
|
_queuedIncoherentBarrier = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueTextureBarrier()
|
||||||
|
{
|
||||||
|
QueueIncoherentBarrier(IncoherentBarrierType.Texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueMemoryBarrier()
|
||||||
|
{
|
||||||
|
QueueIncoherentBarrier(IncoherentBarrierType.All);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueCommandBufferBarrier()
|
||||||
|
{
|
||||||
|
QueueIncoherentBarrier(IncoherentBarrierType.CommandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableTfbBarriers(bool enable)
|
||||||
|
{
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
_extraStages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_extraStages &= ~PipelineStageFlags.TransformFeedbackBitExt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_memoryBarrierBatch.Dispose();
|
_memoryBarrierBatch.Dispose();
|
||||||
|
@ -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();
|
||||||
|
@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
private readonly Queue _queue;
|
private readonly Queue _queue;
|
||||||
private readonly object _queueLock;
|
private readonly object _queueLock;
|
||||||
|
private readonly bool _concurrentFenceWaitUnsupported;
|
||||||
private readonly CommandPool _pool;
|
private readonly CommandPool _pool;
|
||||||
private readonly Thread _owner;
|
private readonly Thread _owner;
|
||||||
|
|
||||||
@ -30,11 +31,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public int SubmissionCount;
|
public int SubmissionCount;
|
||||||
public CommandBuffer CommandBuffer;
|
public CommandBuffer CommandBuffer;
|
||||||
public FenceHolder Fence;
|
public FenceHolder Fence;
|
||||||
public SemaphoreHolder Semaphore;
|
|
||||||
|
|
||||||
public List<IAuto> Dependants;
|
public List<IAuto> Dependants;
|
||||||
public List<MultiFenceHolder> Waitables;
|
public List<MultiFenceHolder> Waitables;
|
||||||
public HashSet<SemaphoreHolder> Dependencies;
|
|
||||||
|
|
||||||
public void Initialize(Vk api, Device device, CommandPool pool)
|
public void Initialize(Vk api, Device device, CommandPool pool)
|
||||||
{
|
{
|
||||||
@ -50,7 +49,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
Dependants = new List<IAuto>();
|
Dependants = new List<IAuto>();
|
||||||
Waitables = new List<MultiFenceHolder>();
|
Waitables = new List<MultiFenceHolder>();
|
||||||
Dependencies = new HashSet<SemaphoreHolder>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,12 +59,20 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private int _queuedCount;
|
private int _queuedCount;
|
||||||
private int _inUseCount;
|
private int _inUseCount;
|
||||||
|
|
||||||
public unsafe CommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex, bool isLight = false)
|
public unsafe CommandBufferPool(
|
||||||
|
Vk api,
|
||||||
|
Device device,
|
||||||
|
Queue queue,
|
||||||
|
object queueLock,
|
||||||
|
uint queueFamilyIndex,
|
||||||
|
bool concurrentFenceWaitUnsupported,
|
||||||
|
bool isLight = false)
|
||||||
{
|
{
|
||||||
_api = api;
|
_api = api;
|
||||||
_device = device;
|
_device = device;
|
||||||
_queue = queue;
|
_queue = queue;
|
||||||
_queueLock = queueLock;
|
_queueLock = queueLock;
|
||||||
|
_concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
|
||||||
_owner = Thread.CurrentThread;
|
_owner = Thread.CurrentThread;
|
||||||
|
|
||||||
var commandPoolCreateInfo = new CommandPoolCreateInfo
|
var commandPoolCreateInfo = new CommandPoolCreateInfo
|
||||||
@ -134,14 +140,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddDependency(int cbIndex, CommandBufferScoped dependencyCbs)
|
|
||||||
{
|
|
||||||
Debug.Assert(_commandBuffers[cbIndex].InUse);
|
|
||||||
var semaphoreHolder = _commandBuffers[dependencyCbs.CommandBufferIndex].Semaphore;
|
|
||||||
semaphoreHolder.Get();
|
|
||||||
_commandBuffers[cbIndex].Dependencies.Add(semaphoreHolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
||||||
{
|
{
|
||||||
ref var entry = ref _commandBuffers[cbIndex];
|
ref var entry = ref _commandBuffers[cbIndex];
|
||||||
@ -345,19 +343,13 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
waitable.RemoveBufferUses(cbIndex);
|
waitable.RemoveBufferUses(cbIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var dependency in entry.Dependencies)
|
|
||||||
{
|
|
||||||
dependency.Put();
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Dependants.Clear();
|
entry.Dependants.Clear();
|
||||||
entry.Waitables.Clear();
|
entry.Waitables.Clear();
|
||||||
entry.Dependencies.Clear();
|
|
||||||
entry.Fence?.Dispose();
|
entry.Fence?.Dispose();
|
||||||
|
|
||||||
if (refreshFence)
|
if (refreshFence)
|
||||||
{
|
{
|
||||||
entry.Fence = new FenceHolder(_api, _device);
|
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -26,11 +26,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_pool.AddWaitable(CommandBufferIndex, waitable);
|
_pool.AddWaitable(CommandBufferIndex, waitable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddDependency(CommandBufferScoped dependencyCbs)
|
|
||||||
{
|
|
||||||
_pool.AddDependency(CommandBufferIndex, dependencyCbs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FenceHolder GetFence()
|
public FenceHolder GetFence()
|
||||||
{
|
{
|
||||||
return _pool.GetFence(CommandBufferIndex);
|
return _pool.GetFence(CommandBufferIndex);
|
||||||
|
@ -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)]
|
||||||
|
@ -59,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
var scalingResourceLayout = new ResourceLayoutBuilder()
|
var scalingResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
var sharpeningResourceLayout = new ResourceLayoutBuilder()
|
var sharpeningResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
var resourceLayout = new ResourceLayoutBuilder()
|
var resourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||||
|
|
||||||
|
@ -81,20 +81,20 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
var edgeResourceLayout = new ResourceLayoutBuilder()
|
var edgeResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
var blendResourceLayout = new ResourceLayoutBuilder()
|
var blendResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
var neighbourResourceLayout = new ResourceLayoutBuilder()
|
var neighbourResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||||
|
|
||||||
|
@ -10,12 +10,15 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
private Fence _fence;
|
private Fence _fence;
|
||||||
private int _referenceCount;
|
private int _referenceCount;
|
||||||
|
private int _lock;
|
||||||
|
private readonly bool _concurrentWaitUnsupported;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public unsafe FenceHolder(Vk api, Device device)
|
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported)
|
||||||
{
|
{
|
||||||
_api = api;
|
_api = api;
|
||||||
_device = device;
|
_device = device;
|
||||||
|
_concurrentWaitUnsupported = concurrentWaitUnsupported;
|
||||||
|
|
||||||
var fenceCreateInfo = new FenceCreateInfo
|
var fenceCreateInfo = new FenceCreateInfo
|
||||||
{
|
{
|
||||||
@ -47,6 +50,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||||
|
|
||||||
|
if (_concurrentWaitUnsupported)
|
||||||
|
{
|
||||||
|
AcquireLock();
|
||||||
|
}
|
||||||
|
|
||||||
fence = _fence;
|
fence = _fence;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -57,6 +65,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _fence;
|
return _fence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PutLock()
|
||||||
|
{
|
||||||
|
Put();
|
||||||
|
|
||||||
|
if (_concurrentWaitUnsupported)
|
||||||
|
{
|
||||||
|
ReleaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Put()
|
public void Put()
|
||||||
{
|
{
|
||||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
||||||
@ -66,24 +84,67 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AcquireLock()
|
||||||
|
{
|
||||||
|
while (!TryAcquireLock())
|
||||||
|
{
|
||||||
|
Thread.SpinWait(32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryAcquireLock()
|
||||||
|
{
|
||||||
|
return Interlocked.Exchange(ref _lock, 1) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseLock()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _lock, 0);
|
||||||
|
}
|
||||||
|
|
||||||
public void Wait()
|
public void Wait()
|
||||||
{
|
{
|
||||||
Span<Fence> fences = stackalloc Fence[]
|
if (_concurrentWaitUnsupported)
|
||||||
{
|
{
|
||||||
_fence,
|
AcquireLock();
|
||||||
};
|
|
||||||
|
|
||||||
FenceHelper.WaitAllIndefinitely(_api, _device, fences);
|
try
|
||||||
|
{
|
||||||
|
FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence });
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReleaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FenceHelper.WaitAllIndefinitely(_api, _device, stackalloc Fence[] { _fence });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSignaled()
|
public bool IsSignaled()
|
||||||
{
|
{
|
||||||
Span<Fence> fences = stackalloc Fence[]
|
if (_concurrentWaitUnsupported)
|
||||||
{
|
{
|
||||||
_fence,
|
if (!TryAcquireLock())
|
||||||
};
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return FenceHelper.AllSignaled(_api, _device, fences);
|
try
|
||||||
|
{
|
||||||
|
return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence });
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ReleaseLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return FenceHelper.AllSignaled(_api, _device, stackalloc Fence[] { _fence });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -286,10 +286,23 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
|
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
|
||||||
|
|
||||||
gd.Barriers.Flush(cbs.CommandBuffer, false, null);
|
gd.Barriers.Flush(cbs, false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
public void AddStoreOpUsage()
|
||||||
|
{
|
||||||
|
if (_colors != null)
|
||||||
|
{
|
||||||
|
foreach (var color in _colors)
|
||||||
|
{
|
||||||
|
color.Storage?.AddStoreOpUsage(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_depthStencil?.Storage?.AddStoreOpUsage(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
Device device,
|
Device device,
|
||||||
CommandBufferScoped cbs)
|
CommandBufferScoped cbs)
|
||||||
|
@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var strideChangeResourceLayout = new ResourceLayoutBuilder()
|
var strideChangeResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||||
|
|
||||||
_programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
|
_programStrideChange = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var colorCopyResourceLayout = new ResourceLayoutBuilder()
|
var colorCopyResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 0)
|
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||||
|
|
||||||
_programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[]
|
_programColorCopyShortening = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
@ -155,7 +155,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
|
var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||||
|
|
||||||
_programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
|
_programConvertD32S8ToD24S8 = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
@ -165,7 +165,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
|
var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2).Build();
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true).Build();
|
||||||
|
|
||||||
_programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
|
_programConvertIndexBuffer = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
{
|
{
|
||||||
@ -175,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
|
var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
|
||||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 1)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2)
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 2, true)
|
||||||
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
|
.Add(ResourceStages.Compute, ResourceType.StorageBuffer, 3).Build();
|
||||||
|
|
||||||
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
|
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
{
|
{
|
||||||
@ -194,18 +196,23 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
bool signaled = true;
|
bool signaled = true;
|
||||||
|
|
||||||
if (hasTimeout)
|
try
|
||||||
{
|
{
|
||||||
signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout);
|
if (hasTimeout)
|
||||||
|
{
|
||||||
|
signaled = FenceHelper.AllSignaled(api, device, fences[..fenceCount], timeout);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
FenceHelper.WaitAllIndefinitely(api, device, fences[..fenceCount]);
|
for (int i = 0; i < fenceCount; i++)
|
||||||
}
|
{
|
||||||
|
fenceHolders[i].PutLock();
|
||||||
for (int i = 0; i < fenceCount; i++)
|
}
|
||||||
{
|
|
||||||
fenceHolders[i].Put();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return signaled;
|
return signaled;
|
||||||
|
@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
protected FramebufferParams FramebufferParams;
|
protected FramebufferParams FramebufferParams;
|
||||||
private Auto<DisposableFramebuffer> _framebuffer;
|
private Auto<DisposableFramebuffer> _framebuffer;
|
||||||
|
private RenderPassHolder _rpHolder;
|
||||||
private Auto<DisposableRenderPass> _renderPass;
|
private Auto<DisposableRenderPass> _renderPass;
|
||||||
private RenderPassHolder _nullRenderPass;
|
private RenderPassHolder _nullRenderPass;
|
||||||
private int _writtenAttachmentCount;
|
private int _writtenAttachmentCount;
|
||||||
@ -85,8 +86,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private bool _tfActive;
|
private bool _tfActive;
|
||||||
|
|
||||||
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
||||||
|
|
||||||
private ulong _drawCountSinceBarrier;
|
|
||||||
public ulong DrawCount { get; private set; }
|
public ulong DrawCount { get; private set; }
|
||||||
public bool RenderPassActive { get; private set; }
|
public bool RenderPassActive { get; private set; }
|
||||||
|
|
||||||
@ -105,7 +104,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];
|
||||||
@ -135,48 +134,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public unsafe void Barrier()
|
public unsafe void Barrier()
|
||||||
{
|
{
|
||||||
if (_drawCountSinceBarrier != DrawCount)
|
Gd.Barriers.QueueMemoryBarrier();
|
||||||
{
|
|
||||||
_drawCountSinceBarrier = DrawCount;
|
|
||||||
|
|
||||||
// Barriers are not supported inside a render pass on Apple GPUs.
|
|
||||||
// As a workaround, end the render pass.
|
|
||||||
if (Gd.Vendor == Vendor.Apple)
|
|
||||||
{
|
|
||||||
EndRenderPass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryBarrier memoryBarrier = new()
|
|
||||||
{
|
|
||||||
SType = StructureType.MemoryBarrier,
|
|
||||||
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
||||||
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
||||||
};
|
|
||||||
|
|
||||||
PipelineStageFlags pipelineStageFlags = PipelineStageFlags.VertexShaderBit | PipelineStageFlags.FragmentShaderBit;
|
|
||||||
|
|
||||||
if (Gd.Capabilities.SupportsGeometryShader)
|
|
||||||
{
|
|
||||||
pipelineStageFlags |= PipelineStageFlags.GeometryShaderBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Gd.Capabilities.SupportsTessellationShader)
|
|
||||||
{
|
|
||||||
pipelineStageFlags |= PipelineStageFlags.TessellationControlShaderBit | PipelineStageFlags.TessellationEvaluationShaderBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gd.Api.CmdPipelineBarrier(
|
|
||||||
CommandBuffer,
|
|
||||||
pipelineStageFlags,
|
|
||||||
pipelineStageFlags,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
memoryBarrier,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ComputeBarrier()
|
public void ComputeBarrier()
|
||||||
@ -203,6 +161,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void BeginTransformFeedback(PrimitiveTopology topology)
|
public void BeginTransformFeedback(PrimitiveTopology topology)
|
||||||
{
|
{
|
||||||
|
Gd.Barriers.EnableTfbBarriers(true);
|
||||||
_tfEnabled = true;
|
_tfEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +208,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
CreateRenderPass();
|
CreateRenderPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
BeginRenderPass();
|
BeginRenderPass();
|
||||||
|
|
||||||
@ -287,7 +246,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
CreateRenderPass();
|
CreateRenderPass();
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
BeginRenderPass();
|
BeginRenderPass();
|
||||||
|
|
||||||
@ -299,24 +258,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public unsafe void CommandBufferBarrier()
|
public unsafe void CommandBufferBarrier()
|
||||||
{
|
{
|
||||||
MemoryBarrier memoryBarrier = new()
|
Gd.Barriers.QueueCommandBufferBarrier();
|
||||||
{
|
|
||||||
SType = StructureType.MemoryBarrier,
|
|
||||||
SrcAccessMask = BufferHolder.DefaultAccessFlags,
|
|
||||||
DstAccessMask = AccessFlags.IndirectCommandReadBit,
|
|
||||||
};
|
|
||||||
|
|
||||||
Gd.Api.CmdPipelineBarrier(
|
|
||||||
CommandBuffer,
|
|
||||||
PipelineStageFlags.AllCommandsBit,
|
|
||||||
PipelineStageFlags.DrawIndirectBit,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
memoryBarrier,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
||||||
@ -722,6 +664,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void EndTransformFeedback()
|
public void EndTransformFeedback()
|
||||||
{
|
{
|
||||||
|
Gd.Barriers.EnableTfbBarriers(false);
|
||||||
PauseTransformFeedbackInternal();
|
PauseTransformFeedbackInternal();
|
||||||
_tfEnabled = false;
|
_tfEnabled = false;
|
||||||
}
|
}
|
||||||
@ -1020,6 +963,13 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_newState.RasterizerDiscardEnable = discard;
|
_newState.RasterizerDiscardEnable = discard;
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
|
|
||||||
|
if (!discard && Gd.IsQualcommProprietary)
|
||||||
|
{
|
||||||
|
// On Adreno, enabling rasterizer discard somehow corrupts the viewport state.
|
||||||
|
// Force it to be updated on next use to work around this bug.
|
||||||
|
DynamicState.ForceAllDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
|
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
|
||||||
@ -1401,24 +1351,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public unsafe void TextureBarrier()
|
public unsafe void TextureBarrier()
|
||||||
{
|
{
|
||||||
MemoryBarrier memoryBarrier = new()
|
Gd.Barriers.QueueTextureBarrier();
|
||||||
{
|
|
||||||
SType = StructureType.MemoryBarrier,
|
|
||||||
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
||||||
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
||||||
};
|
|
||||||
|
|
||||||
Gd.Api.CmdPipelineBarrier(
|
|
||||||
CommandBuffer,
|
|
||||||
PipelineStageFlags.FragmentShaderBit,
|
|
||||||
PipelineStageFlags.FragmentShaderBit,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
memoryBarrier,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TextureBarrierTiled()
|
public void TextureBarrierTiled()
|
||||||
@ -1525,12 +1458,15 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
// Use the null framebuffer.
|
// Use the null framebuffer.
|
||||||
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
|
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
|
||||||
|
|
||||||
|
_rpHolder = _nullRenderPass;
|
||||||
_renderPass = _nullRenderPass.GetRenderPass();
|
_renderPass = _nullRenderPass.GetRenderPass();
|
||||||
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
|
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
|
(_rpHolder, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
|
||||||
|
|
||||||
|
_renderPass = _rpHolder.GetRenderPass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1557,7 +1493,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
|
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
|
||||||
}
|
}
|
||||||
@ -1622,7 +1558,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
Gd.Barriers.Flush(Cbs, _program, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||||
|
|
||||||
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
|
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
|
||||||
|
|
||||||
@ -1701,6 +1637,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
if (RenderPassActive)
|
if (RenderPassActive)
|
||||||
{
|
{
|
||||||
|
FramebufferParams.AddStoreOpUsage();
|
||||||
|
|
||||||
PauseTransformFeedbackInternal();
|
PauseTransformFeedbackInternal();
|
||||||
Gd.Api.CmdEndRenderPass(CommandBuffer);
|
Gd.Api.CmdEndRenderPass(CommandBuffer);
|
||||||
SignalRenderPassEnd();
|
SignalRenderPassEnd();
|
||||||
|
@ -9,13 +9,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
static class PipelineConverter
|
static class PipelineConverter
|
||||||
{
|
{
|
||||||
private const AccessFlags SubpassAccessMask =
|
|
||||||
AccessFlags.MemoryReadBit |
|
|
||||||
AccessFlags.MemoryWriteBit |
|
|
||||||
AccessFlags.ShaderReadBit |
|
|
||||||
AccessFlags.ColorAttachmentWriteBit |
|
|
||||||
AccessFlags.DepthStencilAttachmentWriteBit;
|
|
||||||
|
|
||||||
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
|
public static unsafe DisposableRenderPass ToRenderPass(this ProgramPipelineState state, VulkanRenderer gd, Device device)
|
||||||
{
|
{
|
||||||
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
||||||
@ -108,7 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subpassDependency = CreateSubpassDependency();
|
var subpassDependency = CreateSubpassDependency(gd);
|
||||||
|
|
||||||
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
||||||
{
|
{
|
||||||
@ -129,29 +122,33 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SubpassDependency CreateSubpassDependency()
|
public static SubpassDependency CreateSubpassDependency(VulkanRenderer gd)
|
||||||
{
|
{
|
||||||
|
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||||
|
|
||||||
return new SubpassDependency(
|
return new SubpassDependency(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
PipelineStageFlags.AllGraphicsBit,
|
stages,
|
||||||
PipelineStageFlags.AllGraphicsBit,
|
stages,
|
||||||
SubpassAccessMask,
|
access,
|
||||||
SubpassAccessMask,
|
access,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe static SubpassDependency2 CreateSubpassDependency2()
|
public unsafe static SubpassDependency2 CreateSubpassDependency2(VulkanRenderer gd)
|
||||||
{
|
{
|
||||||
|
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||||
|
|
||||||
return new SubpassDependency2(
|
return new SubpassDependency2(
|
||||||
StructureType.SubpassDependency2,
|
StructureType.SubpassDependency2,
|
||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
PipelineStageFlags.AllGraphicsBit,
|
stages,
|
||||||
PipelineStageFlags.AllGraphicsBit,
|
stages,
|
||||||
SubpassAccessMask,
|
access,
|
||||||
SubpassAccessMask,
|
access,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,9 +177,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 +202,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;
|
||||||
|
|
||||||
|
@ -47,10 +47,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (componentMask != 0xf)
|
if (componentMask != 0xf || Gd.IsQualcommProprietary)
|
||||||
{
|
{
|
||||||
// We can't use CmdClearAttachments if not writing all components,
|
// We can't use CmdClearAttachments if not writing all components,
|
||||||
// because on Vulkan, the pipeline state does not affect clears.
|
// because on Vulkan, the pipeline state does not affect clears.
|
||||||
|
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||||
var dstTexture = FramebufferParams.GetColorView(index);
|
var dstTexture = FramebufferParams.GetColorView(index);
|
||||||
if (dstTexture == null)
|
if (dstTexture == null)
|
||||||
{
|
{
|
||||||
@ -87,10 +88,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stencilMask != 0 && stencilMask != 0xff)
|
if ((stencilMask != 0 && stencilMask != 0xff) || Gd.IsQualcommProprietary)
|
||||||
{
|
{
|
||||||
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
|
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
|
||||||
// because on Vulkan, the pipeline state does not affect clears.
|
// because on Vulkan, the pipeline state does not affect clears.
|
||||||
|
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||||
var dstTexture = FramebufferParams.GetDepthStencilView();
|
var dstTexture = FramebufferParams.GetDepthStencilView();
|
||||||
if (dstTexture == null)
|
if (dstTexture == null)
|
||||||
{
|
{
|
||||||
@ -255,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PreloadCbs = null;
|
PreloadCbs = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
|
Gd.Barriers.Flush(Cbs, false, null, null);
|
||||||
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||||
Gd.RegisterFlush();
|
Gd.RegisterFlush();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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++)
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
@ -29,10 +31,13 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly record struct ForcedFence(TextureStorage Texture, PipelineStageFlags StageFlags);
|
||||||
|
|
||||||
private readonly TextureView[] _textures;
|
private readonly TextureView[] _textures;
|
||||||
private readonly Auto<DisposableRenderPass> _renderPass;
|
private readonly Auto<DisposableRenderPass> _renderPass;
|
||||||
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
|
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
|
||||||
private readonly RenderPassCacheKey _key;
|
private readonly RenderPassCacheKey _key;
|
||||||
|
private readonly List<ForcedFence> _forcedFences;
|
||||||
|
|
||||||
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
|
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
|
||||||
{
|
{
|
||||||
@ -105,7 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var subpassDependency = PipelineConverter.CreateSubpassDependency();
|
var subpassDependency = PipelineConverter.CreateSubpassDependency(gd);
|
||||||
|
|
||||||
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
||||||
{
|
{
|
||||||
@ -138,6 +143,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
_textures = textures;
|
_textures = textures;
|
||||||
_key = key;
|
_key = key;
|
||||||
|
|
||||||
|
_forcedFences = new List<ForcedFence>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
|
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
|
||||||
@ -159,6 +166,37 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _renderPass;
|
return _renderPass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddForcedFence(TextureStorage storage, PipelineStageFlags stageFlags)
|
||||||
|
{
|
||||||
|
if (!_forcedFences.Any(fence => fence.Texture == storage))
|
||||||
|
{
|
||||||
|
_forcedFences.Add(new ForcedFence(storage, stageFlags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertForcedFences(CommandBufferScoped cbs)
|
||||||
|
{
|
||||||
|
if (_forcedFences.Count > 0)
|
||||||
|
{
|
||||||
|
_forcedFences.RemoveAll((entry) =>
|
||||||
|
{
|
||||||
|
if (entry.Texture.Disposed)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsAttachment(TextureStorage storage)
|
||||||
|
{
|
||||||
|
return _textures.Any(view => view.Storage == storage);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Dispose all framebuffers.
|
// Dispose all framebuffers.
|
||||||
|
74
src/Ryujinx.Graphics.Vulkan/ResourceArray.cs
Normal file
74
src/Ryujinx.Graphics.Vulkan/ResourceArray.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding)
|
public ResourceLayoutBuilder Add(ResourceStages stages, ResourceType type, int binding, bool write = false)
|
||||||
{
|
{
|
||||||
int setIndex = type switch
|
int setIndex = type switch
|
||||||
{
|
{
|
||||||
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
};
|
};
|
||||||
|
|
||||||
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
|
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, 1, type, stages));
|
||||||
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages));
|
_resourceUsages[setIndex].Add(new ResourceUsage(binding, 1, type, stages, write));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
using Silk.NET.Vulkan;
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using VkSemaphore = Silk.NET.Vulkan.Semaphore;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
|
||||||
{
|
|
||||||
class SemaphoreHolder : IDisposable
|
|
||||||
{
|
|
||||||
private readonly Vk _api;
|
|
||||||
private readonly Device _device;
|
|
||||||
private VkSemaphore _semaphore;
|
|
||||||
private int _referenceCount;
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
public unsafe SemaphoreHolder(Vk api, Device device)
|
|
||||||
{
|
|
||||||
_api = api;
|
|
||||||
_device = device;
|
|
||||||
|
|
||||||
var semaphoreCreateInfo = new SemaphoreCreateInfo
|
|
||||||
{
|
|
||||||
SType = StructureType.SemaphoreCreateInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
api.CreateSemaphore(device, in semaphoreCreateInfo, null, out _semaphore).ThrowOnError();
|
|
||||||
|
|
||||||
_referenceCount = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VkSemaphore GetUnsafe()
|
|
||||||
{
|
|
||||||
return _semaphore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VkSemaphore Get()
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref _referenceCount);
|
|
||||||
return _semaphore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void Put()
|
|
||||||
{
|
|
||||||
if (Interlocked.Decrement(ref _referenceCount) == 0)
|
|
||||||
{
|
|
||||||
_api.DestroySemaphore(_device, _semaphore, null);
|
|
||||||
_semaphore = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (!_disposed)
|
|
||||||
{
|
|
||||||
Put();
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,8 +23,13 @@ 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 PipelineStageFlags IncoherentBufferWriteStages { get; }
|
||||||
|
public PipelineStageFlags IncoherentTextureWriteStages { get; }
|
||||||
|
|
||||||
public ResourceBindingSegment[][] ClearSegments { get; }
|
public ResourceBindingSegment[][] ClearSegments { get; }
|
||||||
public ResourceBindingSegment[][] BindingSegments { get; }
|
public ResourceBindingSegment[][] BindingSegments { get; }
|
||||||
public DescriptorSetTemplate[] Templates { get; }
|
public DescriptorSetTemplate[] Templates { get; }
|
||||||
@ -127,8 +132,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);
|
||||||
|
(IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
|
||||||
|
|
||||||
|
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
|
||||||
|
UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
|
||||||
|
|
||||||
_compileTask = Task.CompletedTask;
|
_compileTask = Task.CompletedTask;
|
||||||
_firstBackgroundUse = false;
|
_firstBackgroundUse = false;
|
||||||
@ -280,8 +289,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 +306,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 ||
|
||||||
@ -365,6 +381,73 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return templates;
|
return templates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PipelineStageFlags GetPipelineStages(ResourceStages stages)
|
||||||
|
{
|
||||||
|
PipelineStageFlags result = 0;
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.Compute) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.ComputeShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.Vertex) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.VertexShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.Fragment) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.FragmentShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.Geometry) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.GeometryShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.TessellationControl) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.TessellationControlShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((stages & ResourceStages.TessellationEvaluation) != 0)
|
||||||
|
{
|
||||||
|
result |= PipelineStageFlags.TessellationEvaluationShaderBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection<ResourceUsageCollection> setUsages)
|
||||||
|
{
|
||||||
|
PipelineStageFlags buffer = PipelineStageFlags.None;
|
||||||
|
PipelineStageFlags texture = PipelineStageFlags.None;
|
||||||
|
|
||||||
|
foreach (var set in setUsages)
|
||||||
|
{
|
||||||
|
foreach (var range in set.Usages)
|
||||||
|
{
|
||||||
|
if (range.Write)
|
||||||
|
{
|
||||||
|
PipelineStageFlags stages = GetPipelineStages(range.Stages);
|
||||||
|
|
||||||
|
switch (range.Type)
|
||||||
|
{
|
||||||
|
case ResourceType.Image:
|
||||||
|
texture |= stages;
|
||||||
|
break;
|
||||||
|
case ResourceType.StorageBuffer:
|
||||||
|
case ResourceType.BufferImage:
|
||||||
|
buffer |= stages;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (buffer, texture);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task BackgroundCompilation()
|
private async Task BackgroundCompilation()
|
||||||
{
|
{
|
||||||
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
||||||
@ -604,9 +687,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)
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ImageLayout.General,
|
ImageLayout.General,
|
||||||
ImageLayout.General);
|
ImageLayout.General);
|
||||||
|
|
||||||
var subpassDependency = PipelineConverter.CreateSubpassDependency2();
|
var subpassDependency = PipelineConverter.CreateSubpassDependency2(gd);
|
||||||
|
|
||||||
fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs)
|
fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs)
|
||||||
{
|
{
|
||||||
|
@ -38,6 +38,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public TextureCreateInfo Info => _info;
|
public TextureCreateInfo Info => _info;
|
||||||
|
|
||||||
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
private readonly Image _image;
|
private readonly Image _image;
|
||||||
private readonly Auto<DisposableImage> _imageAuto;
|
private readonly Auto<DisposableImage> _imageAuto;
|
||||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
private readonly Auto<MemoryAllocation> _allocationAuto;
|
||||||
@ -80,7 +82,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;
|
||||||
@ -433,6 +435,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
|
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddStoreOpUsage(bool depthStencil)
|
||||||
|
{
|
||||||
|
_lastModificationStage = depthStencil ?
|
||||||
|
PipelineStageFlags.LateFragmentTestsBit :
|
||||||
|
PipelineStageFlags.ColorAttachmentOutputBit;
|
||||||
|
|
||||||
|
_lastModificationAccess = depthStencil ?
|
||||||
|
AccessFlags.DepthStencilAttachmentWriteBit :
|
||||||
|
AccessFlags.ColorAttachmentWriteBit;
|
||||||
|
}
|
||||||
|
|
||||||
public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
|
public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
|
||||||
{
|
{
|
||||||
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
|
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
|
||||||
@ -458,7 +471,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_info.GetLayers(),
|
_info.GetLayers(),
|
||||||
_info.Levels);
|
_info.Levels);
|
||||||
|
|
||||||
_gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
|
_gd.Barriers.QueueBarrier(barrier, this, srcStageFlags, dstStageFlags);
|
||||||
|
|
||||||
_lastReadStage = PipelineStageFlags.None;
|
_lastReadStage = PipelineStageFlags.None;
|
||||||
_lastReadAccess = AccessFlags.None;
|
_lastReadAccess = AccessFlags.None;
|
||||||
@ -491,7 +504,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_info.GetLayers(),
|
_info.GetLayers(),
|
||||||
_info.Levels);
|
_info.Levels);
|
||||||
|
|
||||||
_gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
|
_gd.Barriers.QueueBarrier(barrier, this, _lastModificationStage, dstStageFlags);
|
||||||
|
|
||||||
_lastModificationAccess = AccessFlags.None;
|
_lastModificationAccess = AccessFlags.None;
|
||||||
}
|
}
|
||||||
@ -514,6 +527,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Disposed = true;
|
||||||
|
|
||||||
if (_aliasedStorages != null)
|
if (_aliasedStorages != null)
|
||||||
{
|
{
|
||||||
foreach (var storage in _aliasedStorages.Values)
|
foreach (var storage in _aliasedStorages.Values)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
@ -993,7 +993,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
Device device,
|
Device device,
|
||||||
CommandBufferScoped cbs,
|
CommandBufferScoped cbs,
|
||||||
@ -1006,7 +1006,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
rpHolder = new RenderPassHolder(gd, device, key, fb);
|
rpHolder = new RenderPassHolder(gd, device, key, fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (rpHolder.GetRenderPass(), rpHolder.GetFramebuffer(gd, cbs, fb));
|
return (rpHolder, rpHolder.GetFramebuffer(gd, cbs, fb));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass)
|
public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass)
|
||||||
|
@ -69,27 +69,32 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
DriverId.AmdProprietary => "AMD",
|
DriverId.AmdProprietary => "AMD",
|
||||||
DriverId.AmdOpenSource => "AMD (Open)",
|
DriverId.AmdOpenSource => "AMD (Open)",
|
||||||
DriverId.ArmProprietary => "ARM",
|
|
||||||
DriverId.BroadcomProprietary => "Broadcom",
|
|
||||||
DriverId.CoreaviProprietary => "CoreAVI",
|
|
||||||
DriverId.GgpProprietary => "GGP",
|
|
||||||
DriverId.GoogleSwiftshader => "SwiftShader",
|
|
||||||
DriverId.ImaginationProprietary => "Imagination",
|
|
||||||
DriverId.IntelOpenSourceMesa => "Intel (Open)",
|
|
||||||
DriverId.IntelProprietaryWindows => "Intel",
|
|
||||||
DriverId.JuiceProprietary => "Juice",
|
|
||||||
DriverId.MesaDozen => "Dozen",
|
|
||||||
DriverId.MesaLlvmpipe => "LLVMpipe",
|
|
||||||
DriverId.MesaPanvk => "PanVK",
|
|
||||||
DriverId.MesaRadv => "RADV",
|
DriverId.MesaRadv => "RADV",
|
||||||
|
DriverId.NvidiaProprietary => "NVIDIA",
|
||||||
|
DriverId.IntelProprietaryWindows => "Intel",
|
||||||
|
DriverId.IntelOpenSourceMesa => "Intel (Open)",
|
||||||
|
DriverId.ImaginationProprietary => "Imagination",
|
||||||
|
DriverId.QualcommProprietary => "Qualcomm",
|
||||||
|
DriverId.ArmProprietary => "ARM",
|
||||||
|
DriverId.GoogleSwiftshader => "SwiftShader",
|
||||||
|
DriverId.GgpProprietary => "GGP",
|
||||||
|
DriverId.BroadcomProprietary => "Broadcom",
|
||||||
|
DriverId.MesaLlvmpipe => "LLVMpipe",
|
||||||
|
DriverId.Moltenvk => "MoltenVK",
|
||||||
|
DriverId.CoreaviProprietary => "CoreAVI",
|
||||||
|
DriverId.JuiceProprietary => "Juice",
|
||||||
|
DriverId.VerisiliconProprietary => "Verisilicon",
|
||||||
DriverId.MesaTurnip => "Turnip",
|
DriverId.MesaTurnip => "Turnip",
|
||||||
DriverId.MesaV3DV => "V3DV",
|
DriverId.MesaV3DV => "V3DV",
|
||||||
DriverId.MesaVenus => "Venus",
|
DriverId.MesaPanvk => "PanVK",
|
||||||
DriverId.Moltenvk => "MoltenVK",
|
|
||||||
DriverId.NvidiaProprietary => "NVIDIA",
|
|
||||||
DriverId.QualcommProprietary => "Qualcomm",
|
|
||||||
DriverId.SamsungProprietary => "Samsung",
|
DriverId.SamsungProprietary => "Samsung",
|
||||||
DriverId.VerisiliconProprietary => "Verisilicon",
|
DriverId.MesaVenus => "Venus",
|
||||||
|
DriverId.MesaDozen => "Dozen",
|
||||||
|
|
||||||
|
// TODO: Use real enum when we have an up to date Silk.NET.
|
||||||
|
(DriverId)24 => "NVK",
|
||||||
|
(DriverId)25 => "Imagination (Open)",
|
||||||
|
(DriverId)26 => "Honeykrisp",
|
||||||
_ => id.ToString(),
|
_ => id.ToString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -87,9 +87,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
internal bool IsAmdGcn { get; private set; }
|
internal bool IsAmdGcn { get; private set; }
|
||||||
internal bool IsNvidiaPreTuring { get; private set; }
|
internal bool IsNvidiaPreTuring { get; private set; }
|
||||||
internal bool IsIntelArc { get; private set; }
|
internal bool IsIntelArc { get; private set; }
|
||||||
|
internal bool IsQualcommProprietary { get; private set; }
|
||||||
internal bool IsMoltenVk { get; private set; }
|
internal bool IsMoltenVk { get; private set; }
|
||||||
internal bool IsTBDR { get; private set; }
|
internal bool IsTBDR { get; private set; }
|
||||||
internal bool IsSharedMemory { get; private set; }
|
internal bool IsSharedMemory { get; private set; }
|
||||||
|
|
||||||
public string GpuVendor { get; private set; }
|
public string GpuVendor { get; private set; }
|
||||||
public string GpuDriver { get; private set; }
|
public string GpuDriver { get; private set; }
|
||||||
public string GpuRenderer { get; private set; }
|
public string GpuRenderer { get; private set; }
|
||||||
@ -344,7 +346,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
IsNvidiaPreTuring = gpuNumber < 2000;
|
IsNvidiaPreTuring = gpuNumber < 2000;
|
||||||
}
|
}
|
||||||
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX"))
|
else if (GpuRenderer.Contains("TITAN") && !GpuRenderer.Contains("RTX"))
|
||||||
{
|
{
|
||||||
IsNvidiaPreTuring = true;
|
IsNvidiaPreTuring = true;
|
||||||
}
|
}
|
||||||
@ -354,6 +356,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
|
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary;
|
||||||
|
|
||||||
ulong minResourceAlignment = Math.Max(
|
ulong minResourceAlignment = Math.Max(
|
||||||
Math.Max(
|
Math.Max(
|
||||||
properties.Limits.MinStorageBufferOffsetAlignment,
|
properties.Limits.MinStorageBufferOffsetAlignment,
|
||||||
@ -411,7 +415,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
||||||
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
|
HostMemoryAllocator = new HostMemoryAllocator(MemoryAllocator, Api, hostMemoryApi, _device);
|
||||||
|
|
||||||
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex, IsQualcommProprietary);
|
||||||
|
|
||||||
PipelineLayoutCache = new PipelineLayoutCache();
|
PipelineLayoutCache = new PipelineLayoutCache();
|
||||||
|
|
||||||
@ -688,7 +692,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
GpuVendor,
|
GpuVendor,
|
||||||
memoryType: memoryType,
|
memoryType: memoryType,
|
||||||
hasFrontFacingBug: IsIntelWindows,
|
hasFrontFacingBug: IsIntelWindows,
|
||||||
hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
|
hasVectorIndexingBug: IsQualcommProprietary,
|
||||||
needsFragmentOutputSpecialization: IsMoltenVk,
|
needsFragmentOutputSpecialization: IsMoltenVk,
|
||||||
reduceShaderPrecision: IsMoltenVk,
|
reduceShaderPrecision: IsMoltenVk,
|
||||||
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
|
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
|
||||||
@ -935,6 +939,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ScreenCaptured?.Invoke(this, bitmap);
|
ScreenCaptured?.Invoke(this, bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SupportsRenderPassBarrier(PipelineStageFlags flags)
|
||||||
|
{
|
||||||
|
return !(IsMoltenVk || IsQualcommProprietary);
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void Dispose()
|
public unsafe void Dispose()
|
||||||
{
|
{
|
||||||
if (!_initialized)
|
if (!_initialized)
|
||||||
|
@ -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)
|
||||||
|
@ -7,6 +7,7 @@ using Ryujinx.Common.SystemInterop;
|
|||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.SDL2.Common;
|
using Ryujinx.SDL2.Common;
|
||||||
using Ryujinx.UI;
|
using Ryujinx.UI;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common;
|
using Ryujinx.UI.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
@ -322,7 +323,35 @@ namespace Ryujinx
|
|||||||
|
|
||||||
if (CommandLineState.LaunchPathArg != null)
|
if (CommandLineState.LaunchPathArg != null)
|
||||||
{
|
{
|
||||||
mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
|
if (mainWindow.ApplicationLibrary.TryGetApplicationsFromFile(CommandLineState.LaunchPathArg, out List<ApplicationData> applications))
|
||||||
|
{
|
||||||
|
ApplicationData applicationData;
|
||||||
|
|
||||||
|
if (CommandLineState.LaunchApplicationId != null)
|
||||||
|
{
|
||||||
|
applicationData = applications.Find(application => application.IdString == CommandLineState.LaunchApplicationId);
|
||||||
|
|
||||||
|
if (applicationData != null)
|
||||||
|
{
|
||||||
|
mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{CommandLineState.LaunchApplicationId}' in '{CommandLineState.LaunchPathArg}'.");
|
||||||
|
UserErrorDialog.CreateUserErrorDialog(UserError.ApplicationNotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationData = applications[0];
|
||||||
|
mainWindow.RunApplication(applicationData, CommandLineState.StartFullscreenArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{CommandLineState.LaunchPathArg}'.");
|
||||||
|
UserErrorDialog.CreateUserErrorDialog(UserError.ApplicationNotFound);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
||||||
|
@ -37,7 +37,9 @@ using Ryujinx.UI.Windows;
|
|||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using SPB.Graphics.Vulkan;
|
using SPB.Graphics.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -60,7 +62,6 @@ namespace Ryujinx.UI
|
|||||||
|
|
||||||
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
||||||
|
|
||||||
private readonly ApplicationLibrary _applicationLibrary;
|
|
||||||
private readonly GtkHostUIHandler _uiHandler;
|
private readonly GtkHostUIHandler _uiHandler;
|
||||||
private readonly AutoResetEvent _deviceExitStatus;
|
private readonly AutoResetEvent _deviceExitStatus;
|
||||||
private readonly ListStore _tableStore;
|
private readonly ListStore _tableStore;
|
||||||
@ -69,11 +70,12 @@ namespace Ryujinx.UI
|
|||||||
private bool _gameLoaded;
|
private bool _gameLoaded;
|
||||||
private bool _ending;
|
private bool _ending;
|
||||||
|
|
||||||
private string _currentEmulatedGamePath = null;
|
private ApplicationData _currentApplicationData = null;
|
||||||
|
|
||||||
private string _lastScannedAmiiboId = "";
|
private string _lastScannedAmiiboId = "";
|
||||||
private bool _lastScannedAmiiboShowAll = false;
|
private bool _lastScannedAmiiboShowAll = false;
|
||||||
|
|
||||||
|
public readonly ApplicationLibrary ApplicationLibrary;
|
||||||
public RendererWidgetBase RendererWidget;
|
public RendererWidgetBase RendererWidget;
|
||||||
public InputManager InputManager;
|
public InputManager InputManager;
|
||||||
|
|
||||||
@ -180,8 +182,12 @@ namespace Ryujinx.UI
|
|||||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
|
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_userChannelPersistence = new UserChannelPersistence();
|
||||||
|
|
||||||
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
// Instantiate GUI objects.
|
// Instantiate GUI objects.
|
||||||
_applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
|
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
|
||||||
_uiHandler = new GtkHostUIHandler(this);
|
_uiHandler = new GtkHostUIHandler(this);
|
||||||
_deviceExitStatus = new AutoResetEvent(false);
|
_deviceExitStatus = new AutoResetEvent(false);
|
||||||
|
|
||||||
@ -190,8 +196,8 @@ namespace Ryujinx.UI
|
|||||||
FocusInEvent += MainWindow_FocusInEvent;
|
FocusInEvent += MainWindow_FocusInEvent;
|
||||||
FocusOutEvent += MainWindow_FocusOutEvent;
|
FocusOutEvent += MainWindow_FocusOutEvent;
|
||||||
|
|
||||||
_applicationLibrary.ApplicationAdded += Application_Added;
|
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||||
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||||
|
|
||||||
_fileMenu.StateChanged += FileMenu_StateChanged;
|
_fileMenu.StateChanged += FileMenu_StateChanged;
|
||||||
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
||||||
@ -732,7 +738,7 @@ namespace Ryujinx.UI
|
|||||||
|
|
||||||
Thread applicationLibraryThread = new(() =>
|
Thread applicationLibraryThread = new(() =>
|
||||||
{
|
{
|
||||||
_applicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
|
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs, ConfigurationState.Instance.System.Language);
|
||||||
|
|
||||||
_updatingGameTable = false;
|
_updatingGameTable = false;
|
||||||
})
|
})
|
||||||
@ -783,7 +789,7 @@ namespace Ryujinx.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool LoadApplication(string path, bool isFirmwareTitle)
|
private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle)
|
||||||
{
|
{
|
||||||
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
||||||
|
|
||||||
@ -857,7 +863,7 @@ namespace Ryujinx.UI
|
|||||||
case ".xci":
|
case ".xci":
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||||
|
|
||||||
return _emulationContext.LoadXci(path);
|
return _emulationContext.LoadXci(path, applicationId);
|
||||||
case ".nca":
|
case ".nca":
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||||
|
|
||||||
@ -866,7 +872,7 @@ namespace Ryujinx.UI
|
|||||||
case ".pfs0":
|
case ".pfs0":
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||||
|
|
||||||
return _emulationContext.LoadNsp(path);
|
return _emulationContext.LoadNsp(path, applicationId);
|
||||||
default:
|
default:
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
||||||
try
|
try
|
||||||
@ -887,7 +893,7 @@ namespace Ryujinx.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunApplication(string path, bool startFullscreen = false)
|
public void RunApplication(ApplicationData application, bool startFullscreen = false)
|
||||||
{
|
{
|
||||||
if (_gameLoaded)
|
if (_gameLoaded)
|
||||||
{
|
{
|
||||||
@ -909,14 +915,14 @@ namespace Ryujinx.UI
|
|||||||
|
|
||||||
bool isFirmwareTitle = false;
|
bool isFirmwareTitle = false;
|
||||||
|
|
||||||
if (path.StartsWith("@SystemContent"))
|
if (application.Path.StartsWith("@SystemContent"))
|
||||||
{
|
{
|
||||||
path = VirtualFileSystem.SwitchPathToSystemPath(path);
|
application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
|
||||||
|
|
||||||
isFirmwareTitle = true;
|
isFirmwareTitle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LoadApplication(path, isFirmwareTitle))
|
if (!LoadApplication(application.Path, application.Id, isFirmwareTitle))
|
||||||
{
|
{
|
||||||
_emulationContext.Dispose();
|
_emulationContext.Dispose();
|
||||||
SwitchToGameTable();
|
SwitchToGameTable();
|
||||||
@ -926,7 +932,7 @@ namespace Ryujinx.UI
|
|||||||
|
|
||||||
SetupProgressUIHandlers();
|
SetupProgressUIHandlers();
|
||||||
|
|
||||||
_currentEmulatedGamePath = path;
|
_currentApplicationData = application;
|
||||||
|
|
||||||
_deviceExitStatus.Reset();
|
_deviceExitStatus.Reset();
|
||||||
|
|
||||||
@ -1165,7 +1171,7 @@ namespace Ryujinx.UI
|
|||||||
_tableStore.AppendValues(
|
_tableStore.AppendValues(
|
||||||
args.AppData.Favorite,
|
args.AppData.Favorite,
|
||||||
new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
|
new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
|
||||||
$"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}",
|
$"{args.AppData.Name}\n{args.AppData.IdString.ToUpper()}",
|
||||||
args.AppData.Developer,
|
args.AppData.Developer,
|
||||||
args.AppData.Version,
|
args.AppData.Version,
|
||||||
args.AppData.TimePlayedString,
|
args.AppData.TimePlayedString,
|
||||||
@ -1253,9 +1259,22 @@ namespace Ryujinx.UI
|
|||||||
{
|
{
|
||||||
_gameTableSelection.GetSelected(out TreeIter treeIter);
|
_gameTableSelection.GetSelected(out TreeIter treeIter);
|
||||||
|
|
||||||
string path = (string)_tableStore.GetValue(treeIter, 9);
|
ApplicationData application = new()
|
||||||
|
{
|
||||||
|
Favorite = (bool)_tableStore.GetValue(treeIter, 0),
|
||||||
|
Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
|
||||||
|
Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
|
||||||
|
Developer = (string)_tableStore.GetValue(treeIter, 3),
|
||||||
|
Version = (string)_tableStore.GetValue(treeIter, 4),
|
||||||
|
TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
|
||||||
|
LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
|
||||||
|
FileExtension = (string)_tableStore.GetValue(treeIter, 7),
|
||||||
|
FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
|
||||||
|
Path = (string)_tableStore.GetValue(treeIter, 9),
|
||||||
|
ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
|
||||||
|
};
|
||||||
|
|
||||||
RunApplication(path);
|
RunApplication(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
|
private void VSyncStatus_Clicked(object sender, ButtonReleaseEventArgs args)
|
||||||
@ -1313,13 +1332,22 @@ namespace Ryujinx.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString();
|
ApplicationData application = new()
|
||||||
string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0];
|
{
|
||||||
string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
|
Favorite = (bool)_tableStore.GetValue(treeIter, 0),
|
||||||
|
Name = ((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[0],
|
||||||
|
Id = ulong.Parse(((string)_tableStore.GetValue(treeIter, 2)).Split('\n')[1], NumberStyles.HexNumber),
|
||||||
|
Developer = (string)_tableStore.GetValue(treeIter, 3),
|
||||||
|
Version = (string)_tableStore.GetValue(treeIter, 4),
|
||||||
|
TimePlayed = ValueFormatUtils.ParseTimeSpan((string)_tableStore.GetValue(treeIter, 5)),
|
||||||
|
LastPlayed = ValueFormatUtils.ParseDateTime((string)_tableStore.GetValue(treeIter, 6)),
|
||||||
|
FileExtension = (string)_tableStore.GetValue(treeIter, 7),
|
||||||
|
FileSize = ValueFormatUtils.ParseFileSize((string)_tableStore.GetValue(treeIter, 8)),
|
||||||
|
Path = (string)_tableStore.GetValue(treeIter, 9),
|
||||||
|
ControlHolder = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10),
|
||||||
|
};
|
||||||
|
|
||||||
BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
|
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application);
|
||||||
|
|
||||||
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Load_Application_File(object sender, EventArgs args)
|
private void Load_Application_File(object sender, EventArgs args)
|
||||||
@ -1341,7 +1369,15 @@ namespace Ryujinx.UI
|
|||||||
|
|
||||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||||
{
|
{
|
||||||
RunApplication(fileChooser.Filename);
|
if (ApplicationLibrary.TryGetApplicationsFromFile(fileChooser.Filename,
|
||||||
|
out List<ApplicationData> applications))
|
||||||
|
{
|
||||||
|
RunApplication(applications[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GtkDialog.CreateErrorDialog("No applications found in selected file.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1351,7 +1387,13 @@ namespace Ryujinx.UI
|
|||||||
|
|
||||||
if (fileChooser.Run() == (int)ResponseType.Accept)
|
if (fileChooser.Run() == (int)ResponseType.Accept)
|
||||||
{
|
{
|
||||||
RunApplication(fileChooser.Filename);
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Name = System.IO.Path.GetFileNameWithoutExtension(fileChooser.Filename),
|
||||||
|
Path = fileChooser.Filename,
|
||||||
|
};
|
||||||
|
|
||||||
|
RunApplication(applicationData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1366,7 +1408,14 @@ namespace Ryujinx.UI
|
|||||||
{
|
{
|
||||||
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
|
string contentPath = _contentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||||
|
|
||||||
RunApplication(contentPath);
|
ApplicationData applicationData = new()
|
||||||
|
{
|
||||||
|
Name = "miiEdit",
|
||||||
|
Id = 0x0100000000001009ul,
|
||||||
|
Path = contentPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
RunApplication(applicationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Open_Ryu_Folder(object sender, EventArgs args)
|
private void Open_Ryu_Folder(object sender, EventArgs args)
|
||||||
@ -1646,13 +1695,13 @@ namespace Ryujinx.UI
|
|||||||
{
|
{
|
||||||
_userChannelPersistence.ShouldRestart = false;
|
_userChannelPersistence.ShouldRestart = false;
|
||||||
|
|
||||||
RunApplication(_currentEmulatedGamePath);
|
RunApplication(_currentApplicationData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// otherwise, clear state.
|
// otherwise, clear state.
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_userChannelPersistence = new UserChannelPersistence();
|
||||||
_currentEmulatedGamePath = null;
|
_currentApplicationData = null;
|
||||||
_actionMenu.Sensitive = false;
|
_actionMenu.Sensitive = false;
|
||||||
_firmwareInstallFile.Sensitive = true;
|
_firmwareInstallFile.Sensitive = true;
|
||||||
_firmwareInstallDirectory.Sensitive = true;
|
_firmwareInstallDirectory.Sensitive = true;
|
||||||
@ -1714,7 +1763,7 @@ namespace Ryujinx.UI
|
|||||||
_emulationContext.Processes.ActiveApplication.ProgramId,
|
_emulationContext.Processes.ActiveApplication.ProgramId,
|
||||||
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
|
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
|
||||||
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
|
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
|
||||||
_currentEmulatedGamePath);
|
_currentApplicationData.Path);
|
||||||
|
|
||||||
window.Destroyed += CheatWindow_Destroyed;
|
window.Destroyed += CheatWindow_Destroyed;
|
||||||
window.Show();
|
window.Show();
|
||||||
|
@ -16,6 +16,8 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
@ -23,7 +25,6 @@ using Ryujinx.UI.Windows;
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -36,17 +37,13 @@ namespace Ryujinx.UI.Widgets
|
|||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly AccountManager _accountManager;
|
private readonly AccountManager _accountManager;
|
||||||
private readonly HorizonClient _horizonClient;
|
private readonly HorizonClient _horizonClient;
|
||||||
private readonly BlitStruct<ApplicationControlProperty> _controlData;
|
|
||||||
|
|
||||||
private readonly string _titleFilePath;
|
private readonly ApplicationData _applicationData;
|
||||||
private readonly string _titleName;
|
|
||||||
private readonly string _titleIdText;
|
|
||||||
private readonly ulong _titleId;
|
|
||||||
|
|
||||||
private MessageDialog _dialog;
|
private MessageDialog _dialog;
|
||||||
private bool _cancel;
|
private bool _cancel;
|
||||||
|
|
||||||
public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
|
public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, ApplicationData applicationData)
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
|
|
||||||
@ -55,23 +52,22 @@ namespace Ryujinx.UI.Widgets
|
|||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_accountManager = accountManager;
|
_accountManager = accountManager;
|
||||||
_horizonClient = horizonClient;
|
_horizonClient = horizonClient;
|
||||||
_titleFilePath = titleFilePath;
|
_applicationData = applicationData;
|
||||||
_titleName = titleName;
|
|
||||||
_titleIdText = titleId;
|
|
||||||
_controlData = controlData;
|
|
||||||
|
|
||||||
if (!ulong.TryParse(_titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out _titleId))
|
if (!_applicationData.ControlHolder.ByteSpan.IsZeros())
|
||||||
{
|
{
|
||||||
GtkDialog.CreateErrorDialog("The selected game did not have a valid Title Id");
|
_openSaveUserDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||||
|
_openSaveDeviceDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||||
return;
|
_openSaveBcatDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_openSaveUserDirMenuItem.Sensitive = false;
|
||||||
|
_openSaveDeviceDirMenuItem.Sensitive = false;
|
||||||
|
_openSaveBcatDirMenuItem.Sensitive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
|
string fileExt = System.IO.Path.GetExtension(_applicationData.Path).ToLower();
|
||||||
_openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
|
|
||||||
_openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
|
|
||||||
|
|
||||||
string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
|
|
||||||
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
|
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
|
||||||
|
|
||||||
_extractRomFsMenuItem.Sensitive = hasNca;
|
_extractRomFsMenuItem.Sensitive = hasNca;
|
||||||
@ -137,7 +133,7 @@ namespace Ryujinx.UI.Widgets
|
|||||||
|
|
||||||
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
|
private void OpenSaveDir(in SaveDataFilter saveDataFilter)
|
||||||
{
|
{
|
||||||
if (!TryFindSaveData(_titleName, _titleId, _controlData, in saveDataFilter, out ulong saveDataId))
|
if (!TryFindSaveData(_applicationData.Name, _applicationData.Id, _applicationData.ControlHolder, in saveDataFilter, out ulong saveDataId))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -190,7 +186,7 @@ namespace Ryujinx.UI.Widgets
|
|||||||
{
|
{
|
||||||
Title = "Ryujinx - NCA Section Extractor",
|
Title = "Ryujinx - NCA Section Extractor",
|
||||||
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"),
|
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"),
|
||||||
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
|
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_applicationData.Path)}...",
|
||||||
WindowPosition = WindowPosition.Center,
|
WindowPosition = WindowPosition.Center,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -202,29 +198,16 @@ namespace Ryujinx.UI.Widgets
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
using FileStream file = new(_titleFilePath, FileMode.Open, FileAccess.Read);
|
using FileStream file = new(_applicationData.Path, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
Nca mainNca = null;
|
Nca mainNca = null;
|
||||||
Nca patchNca = null;
|
Nca patchNca = null;
|
||||||
|
|
||||||
if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
|
if ((System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nsp") ||
|
||||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
|
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".pfs0") ||
|
||||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
|
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".xci"))
|
||||||
{
|
{
|
||||||
IFileSystem pfs;
|
IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(_applicationData.Path, _virtualFileSystem);
|
||||||
|
|
||||||
if (System.IO.Path.GetExtension(_titleFilePath) == ".xci")
|
|
||||||
{
|
|
||||||
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
|
||||||
|
|
||||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var pfsTemp = new PartitionFileSystem();
|
|
||||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
|
||||||
pfs = pfsTemp;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
@ -249,7 +232,7 @@ namespace Ryujinx.UI.Widgets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nca")
|
else if (System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nca")
|
||||||
{
|
{
|
||||||
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
mainNca = new Nca(_virtualFileSystem.KeySet, file.AsStorage());
|
||||||
}
|
}
|
||||||
@ -266,7 +249,11 @@ namespace Ryujinx.UI.Widgets
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
|
(Nca updatePatchNca, _) = mainNca.GetUpdateData(_virtualFileSystem, checkLevel, programIndex, out _);
|
||||||
|
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
@ -460,44 +447,44 @@ namespace Ryujinx.UI.Widgets
|
|||||||
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
|
private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
var userId = new LibHac.Fs.UserId((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
|
||||||
var saveDataFilter = SaveDataFilter.Make(_titleId, saveType: default, userId, saveDataId: default, index: default);
|
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, saveType: default, userId, saveDataId: default, index: default);
|
||||||
|
|
||||||
OpenSaveDir(in saveDataFilter);
|
OpenSaveDir(in saveDataFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
|
private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Device, userId: default, saveDataId: default, index: default);
|
||||||
|
|
||||||
OpenSaveDir(in saveDataFilter);
|
OpenSaveDir(in saveDataFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
|
private void OpenSaveBcatDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
var saveDataFilter = SaveDataFilter.Make(_titleId, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
var saveDataFilter = SaveDataFilter.Make(_applicationData.Id, SaveDataType.Bcat, userId: default, saveDataId: default, index: default);
|
||||||
|
|
||||||
OpenSaveDir(in saveDataFilter);
|
OpenSaveDir(in saveDataFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
|
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
new TitleUpdateWindow(_parent, _virtualFileSystem, _titleIdText, _titleName).Show();
|
new TitleUpdateWindow(_parent, _virtualFileSystem, _applicationData).Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ManageDlc_Clicked(object sender, EventArgs args)
|
private void ManageDlc_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
|
new DlcWindow(_virtualFileSystem, _applicationData.IdString, _applicationData).Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ManageCheats_Clicked(object sender, EventArgs args)
|
private void ManageCheats_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
new CheatWindow(_virtualFileSystem, _titleId, _titleName, _titleFilePath).Show();
|
new CheatWindow(_virtualFileSystem, _applicationData.Id, _applicationData.Name, _applicationData.Path).Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string modsBasePath = ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText);
|
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _applicationData.IdString);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
@ -505,7 +492,7 @@ namespace Ryujinx.UI.Widgets
|
|||||||
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
|
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText);
|
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _applicationData.IdString);
|
||||||
|
|
||||||
OpenHelper.OpenFolder(titleModsPath);
|
OpenHelper.OpenFolder(titleModsPath);
|
||||||
}
|
}
|
||||||
@ -527,7 +514,7 @@ namespace Ryujinx.UI.Widgets
|
|||||||
|
|
||||||
private void OpenPtcDir_Clicked(object sender, EventArgs args)
|
private void OpenPtcDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu");
|
string ptcDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu");
|
||||||
|
|
||||||
string mainPath = System.IO.Path.Combine(ptcDir, "0");
|
string mainPath = System.IO.Path.Combine(ptcDir, "0");
|
||||||
string backupPath = System.IO.Path.Combine(ptcDir, "1");
|
string backupPath = System.IO.Path.Combine(ptcDir, "1");
|
||||||
@ -544,7 +531,7 @@ namespace Ryujinx.UI.Widgets
|
|||||||
|
|
||||||
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
|
private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader");
|
string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader");
|
||||||
|
|
||||||
if (!Directory.Exists(shaderCacheDir))
|
if (!Directory.Exists(shaderCacheDir))
|
||||||
{
|
{
|
||||||
@ -556,10 +543,10 @@ namespace Ryujinx.UI.Widgets
|
|||||||
|
|
||||||
private void PurgePtcCache_Clicked(object sender, EventArgs args)
|
private void PurgePtcCache_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
|
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "0"));
|
||||||
DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
|
DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "1"));
|
||||||
|
|
||||||
MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
|
MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to queue a PPTC rebuild on the next boot of:\n\n<b>{_applicationData.Name}</b>\n\nAre you sure you want to proceed?");
|
||||||
|
|
||||||
List<FileInfo> cacheFiles = new();
|
List<FileInfo> cacheFiles = new();
|
||||||
|
|
||||||
@ -593,9 +580,9 @@ namespace Ryujinx.UI.Widgets
|
|||||||
|
|
||||||
private void PurgeShaderCache_Clicked(object sender, EventArgs args)
|
private void PurgeShaderCache_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "shader"));
|
DirectoryInfo shaderCacheDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "shader"));
|
||||||
|
|
||||||
using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?");
|
using MessageDialog warningDialog = GtkDialog.CreateConfirmationDialog("Warning", $"You are about to delete the shader cache for :\n\n<b>{_applicationData.Name}</b>\n\nAre you sure you want to proceed?");
|
||||||
|
|
||||||
List<DirectoryInfo> oldCacheDirectories = new();
|
List<DirectoryInfo> oldCacheDirectories = new();
|
||||||
List<FileInfo> newCacheFiles = new();
|
List<FileInfo> newCacheFiles = new();
|
||||||
@ -637,8 +624,11 @@ namespace Ryujinx.UI.Widgets
|
|||||||
|
|
||||||
private void CreateShortcut_Clicked(object sender, EventArgs args)
|
private void CreateShortcut_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem, checkLevel).GetApplicationIcon(_applicationData.Path, ConfigurationState.Instance.System.Language, _applicationData.Id);
|
||||||
|
ShortcutHelper.CreateAppShortcut(_applicationData.Path, _applicationData.Name, _applicationData.IdString, appIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
using Gtk;
|
using Gtk;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -27,8 +29,13 @@ namespace Ryujinx.UI.Windows
|
|||||||
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
|
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
|
||||||
{
|
{
|
||||||
builder.Autoconnect(this);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
||||||
_buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}";
|
_buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetBuildId(virtualFileSystem, checkLevel, titlePath)}";
|
||||||
|
|
||||||
string modsBasePath = ModLoader.GetModsBasePath();
|
string modsBasePath = ModLoader.GetModsBasePath();
|
||||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16"));
|
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16"));
|
||||||
|
@ -2,17 +2,21 @@ using Gtk;
|
|||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Widgets;
|
using Ryujinx.UI.Widgets;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using GUI = Gtk.Builder.ObjectAttribute;
|
using GUI = Gtk.Builder.ObjectAttribute;
|
||||||
|
|
||||||
namespace Ryujinx.UI.Windows
|
namespace Ryujinx.UI.Windows
|
||||||
@ -20,7 +24,7 @@ namespace Ryujinx.UI.Windows
|
|||||||
public class DlcWindow : Window
|
public class DlcWindow : Window
|
||||||
{
|
{
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly string _titleId;
|
private readonly string _applicationId;
|
||||||
private readonly string _dlcJsonPath;
|
private readonly string _dlcJsonPath;
|
||||||
private readonly List<DownloadableContentContainer> _dlcContainerList;
|
private readonly List<DownloadableContentContainer> _dlcContainerList;
|
||||||
|
|
||||||
@ -32,16 +36,16 @@ namespace Ryujinx.UI.Windows
|
|||||||
[GUI] TreeSelection _dlcTreeSelection;
|
[GUI] TreeSelection _dlcTreeSelection;
|
||||||
#pragma warning restore CS0649, IDE0044
|
#pragma warning restore CS0649, IDE0044
|
||||||
|
|
||||||
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
|
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, applicationData) { }
|
||||||
|
|
||||||
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
|
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string applicationId, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_dlcWindow"))
|
||||||
{
|
{
|
||||||
builder.Autoconnect(this);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
_titleId = titleId;
|
_applicationId = applicationId;
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
|
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationId, "dlc.json");
|
||||||
_baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
|
_baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationId.ToUpper()}]";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -72,7 +76,7 @@ namespace Ryujinx.UI.Windows
|
|||||||
};
|
};
|
||||||
|
|
||||||
_dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
|
_dlcTreeView.AppendColumn("Enabled", enableToggle, "active", 0);
|
||||||
_dlcTreeView.AppendColumn("TitleId", new CellRendererText(), "text", 1);
|
_dlcTreeView.AppendColumn("ApplicationId", new CellRendererText(), "text", 1);
|
||||||
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
|
_dlcTreeView.AppendColumn("Path", new CellRendererText(), "text", 2);
|
||||||
|
|
||||||
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
|
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
|
||||||
@ -86,18 +90,18 @@ namespace Ryujinx.UI.Windows
|
|||||||
bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
|
bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
|
||||||
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath);
|
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(areAllContentPacksEnabled, "", dlcContainer.ContainerPath);
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(dlcContainer.ContainerPath);
|
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(dlcContainer.ContainerPath, _virtualFileSystem, false);
|
||||||
|
|
||||||
PartitionFileSystem pfs = new();
|
if (partitionFileSystem == null)
|
||||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
{
|
||||||
|
continue;
|
||||||
_virtualFileSystem.ImportTickets(pfs);
|
}
|
||||||
|
|
||||||
foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
|
foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
|
||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, dlcNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath);
|
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), dlcContainer.ContainerPath);
|
||||||
|
|
||||||
if (nca != null)
|
if (nca != null)
|
||||||
@ -112,6 +116,9 @@ namespace Ryujinx.UI.Windows
|
|||||||
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}");
|
TreeIter parentIter = ((TreeStore)_dlcTreeView.Model).AppendValues(false, "", $"(MISSING) {dlcContainer.ContainerPath}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Try to load downloadable contents from PFS last to preserve enabled state.
|
||||||
|
AddDlc(applicationData.Path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
|
||||||
@ -128,6 +135,52 @@ namespace Ryujinx.UI.Windows
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddDlc(string path, bool ignoreNotFound = false)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path) || _dlcContainerList.Any(x => x.ContainerPath == path))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using IFileSystem partitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
|
||||||
|
|
||||||
|
bool containsDlc = false;
|
||||||
|
|
||||||
|
TreeIter? parentIter = null;
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
|
||||||
|
|
||||||
|
if (nca == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
|
{
|
||||||
|
if (nca.GetProgramIdBase() != (ulong.Parse(_applicationId, NumberStyles.HexNumber) & ~0x1FFFUL))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", path);
|
||||||
|
|
||||||
|
((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
||||||
|
containsDlc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!containsDlc && !ignoreNotFound)
|
||||||
|
{
|
||||||
|
GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void AddButton_Clicked(object sender, EventArgs args)
|
private void AddButton_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel")
|
FileChooserNative fileChooser = new("Select DLC files", this, FileChooserAction.Open, "Add", "Cancel")
|
||||||
@ -147,52 +200,7 @@ namespace Ryujinx.UI.Windows
|
|||||||
{
|
{
|
||||||
foreach (string containerPath in fileChooser.Filenames)
|
foreach (string containerPath in fileChooser.Filenames)
|
||||||
{
|
{
|
||||||
if (!File.Exists(containerPath))
|
AddDlc(containerPath);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(containerPath);
|
|
||||||
|
|
||||||
PartitionFileSystem pfs = new();
|
|
||||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
|
||||||
bool containsDlc = false;
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(pfs);
|
|
||||||
|
|
||||||
TreeIter? parentIter = null;
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), containerPath);
|
|
||||||
|
|
||||||
if (nca == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
|
||||||
{
|
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000).ToString("x16") != _titleId)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
parentIter ??= ((TreeStore)_dlcTreeView.Model).AppendValues(true, "", containerPath);
|
|
||||||
|
|
||||||
((TreeStore)_dlcTreeView.Model).AppendValues(parentIter.Value, true, nca.Header.TitleId.ToString("X16"), fileEntry.FullPath);
|
|
||||||
containsDlc = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!containsDlc)
|
|
||||||
{
|
|
||||||
GtkDialog.CreateErrorDialog("The specified file does not contain DLC for the selected title!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,14 +2,17 @@ using Gtk;
|
|||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
using LibHac.Ncm;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Widgets;
|
using Ryujinx.UI.Widgets;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -24,7 +27,7 @@ namespace Ryujinx.UI.Windows
|
|||||||
{
|
{
|
||||||
private readonly MainWindow _parent;
|
private readonly MainWindow _parent;
|
||||||
private readonly VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private readonly string _titleId;
|
private readonly ApplicationData _applicationData;
|
||||||
private readonly string _updateJsonPath;
|
private readonly string _updateJsonPath;
|
||||||
|
|
||||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
@ -38,17 +41,17 @@ namespace Ryujinx.UI.Windows
|
|||||||
[GUI] RadioButton _noUpdateRadioButton;
|
[GUI] RadioButton _noUpdateRadioButton;
|
||||||
#pragma warning restore CS0649, IDE0044
|
#pragma warning restore CS0649, IDE0044
|
||||||
|
|
||||||
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
|
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, applicationData) { }
|
||||||
|
|
||||||
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
|
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, ApplicationData applicationData) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
|
|
||||||
builder.Autoconnect(this);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
_titleId = titleId;
|
_applicationData = applicationData;
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "updates.json");
|
_updateJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, applicationData.IdString, "updates.json");
|
||||||
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
|
_radioButtonToPathDictionary = new Dictionary<RadioButton, string>();
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -64,7 +67,10 @@ namespace Ryujinx.UI.Windows
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_baseTitleInfoLabel.Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]";
|
_baseTitleInfoLabel.Text = $"Updates Available for {applicationData.Name} [{applicationData.IdString}]";
|
||||||
|
|
||||||
|
// Try to get updates from PFS first
|
||||||
|
AddUpdate(_applicationData.Path, true);
|
||||||
|
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
foreach (string path in _titleUpdateWindowData.Paths)
|
||||||
{
|
{
|
||||||
@ -84,46 +90,68 @@ namespace Ryujinx.UI.Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
private void AddUpdate(string path, bool ignoreNotFound = false)
|
||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (!File.Exists(path) || _radioButtonToPathDictionary.ContainsValue(path))
|
||||||
{
|
{
|
||||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PartitionFileSystem nsp = new();
|
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
|
: IntegrityCheckLevel.None;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(path, _virtualFileSystem);
|
||||||
|
|
||||||
|
Dictionary<ulong, ContentMetaData> updates = pfs.GetContentData(ContentMetaType.Patch, _virtualFileSystem, checkLevel);
|
||||||
|
|
||||||
|
Nca patchNca = null;
|
||||||
|
Nca controlNca = null;
|
||||||
|
|
||||||
|
if (updates.TryGetValue(_applicationData.Id, out ContentMetaData update))
|
||||||
{
|
{
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
|
patchNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Program);
|
||||||
|
controlNca = update.GetNcaByType(_virtualFileSystem.KeySet, LibHac.Ncm.ContentType.Control);
|
||||||
|
}
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
if (controlNca != null && patchNca != null)
|
||||||
|
{
|
||||||
|
ApplicationControlProperty controlData = new();
|
||||||
|
|
||||||
|
using var nacpFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||||
|
|
||||||
|
string radioLabel = $"Version {controlData.DisplayVersionString.ToString()} - {path}";
|
||||||
|
|
||||||
|
if (System.IO.Path.GetExtension(path).ToLower() == ".xci")
|
||||||
{
|
{
|
||||||
ApplicationControlProperty controlData = new();
|
radioLabel = "Bundled: " + radioLabel;
|
||||||
|
|
||||||
using var nacpFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
|
||||||
|
|
||||||
RadioButton radioButton = new($"Version {controlData.DisplayVersionString.ToString()} - {path}");
|
|
||||||
radioButton.JoinGroup(_noUpdateRadioButton);
|
|
||||||
|
|
||||||
_availableUpdatesBox.Add(radioButton);
|
|
||||||
_radioButtonToPathDictionary.Add(radioButton, path);
|
|
||||||
|
|
||||||
radioButton.Show();
|
|
||||||
radioButton.Active = true;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
RadioButton radioButton = new(radioLabel);
|
||||||
|
radioButton.JoinGroup(_noUpdateRadioButton);
|
||||||
|
|
||||||
|
_availableUpdatesBox.Add(radioButton);
|
||||||
|
_radioButtonToPathDictionary.Add(radioButton, path);
|
||||||
|
|
||||||
|
radioButton.Show();
|
||||||
|
radioButton.Active = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ignoreNotFound)
|
||||||
{
|
{
|
||||||
GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
|
GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
}
|
||||||
{
|
catch (Exception exception)
|
||||||
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
{
|
||||||
}
|
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ using Ryujinx.Common.Utilities;
|
|||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.HOS.Services.Ssl;
|
using Ryujinx.HLE.HOS.Services.Ssl;
|
||||||
using Ryujinx.HLE.HOS.Services.Time;
|
using Ryujinx.HLE.HOS.Services.Time;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -184,41 +185,6 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fs must contain AOC nca files in its root
|
|
||||||
public void AddAocData(IFileSystem fs, string containerPath, ulong aocBaseId, IntegrityCheckLevel integrityCheckLevel)
|
|
||||||
{
|
|
||||||
_virtualFileSystem.ImportTickets(fs);
|
|
||||||
|
|
||||||
foreach (var ncaPath in fs.EnumerateEntries("*.cnmt.nca", SearchOptions.Default))
|
|
||||||
{
|
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
|
||||||
if (nca.Header.ContentType != NcaContentType.Meta)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"{ncaPath} is not a valid metadata file");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
|
||||||
using var cnmtFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
|
|
||||||
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower();
|
|
||||||
|
|
||||||
AddAocItem(cnmt.TitleId, containerPath, $"/{ncaId}.nca", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool mergedToContainer = false)
|
public void AddAocItem(ulong titleId, string containerPath, string ncaPath, bool mergedToContainer = false)
|
||||||
{
|
{
|
||||||
// TODO: Check Aoc version.
|
// TODO: Check Aoc version.
|
||||||
@ -232,11 +198,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
if (!mergedToContainer)
|
if (!mergedToContainer)
|
||||||
{
|
{
|
||||||
using FileStream fileStream = File.OpenRead(containerPath);
|
using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerPath, _virtualFileSystem);
|
||||||
using PartitionFileSystem partitionFileSystem = new();
|
|
||||||
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
61
src/Ryujinx.HLE/FileSystem/ContentMetaData.cs
Normal file
61
src/Ryujinx.HLE/FileSystem/ContentMetaData.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using LibHac.Common.Keys;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Tools.Ncm;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.FileSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Thin wrapper around <see cref="Cnmt"/>
|
||||||
|
/// </summary>
|
||||||
|
public class ContentMetaData
|
||||||
|
{
|
||||||
|
private readonly IFileSystem _pfs;
|
||||||
|
private readonly Cnmt _cnmt;
|
||||||
|
|
||||||
|
public ulong Id => _cnmt.TitleId;
|
||||||
|
public TitleVersion Version => _cnmt.TitleVersion;
|
||||||
|
public ContentMetaType Type => _cnmt.Type;
|
||||||
|
public ulong ApplicationId => _cnmt.ApplicationTitleId;
|
||||||
|
public ulong PatchId => _cnmt.PatchTitleId;
|
||||||
|
public TitleVersion RequiredSystemVersion => _cnmt.MinimumSystemVersion;
|
||||||
|
public TitleVersion RequiredApplicationVersion => _cnmt.MinimumApplicationVersion;
|
||||||
|
public byte[] Digest => _cnmt.Hash;
|
||||||
|
|
||||||
|
public ulong ProgramBaseId => Id & ~0x1FFFUL;
|
||||||
|
public bool IsSystemTitle => _cnmt.Type < ContentMetaType.Application;
|
||||||
|
|
||||||
|
public ContentMetaData(IFileSystem pfs, Cnmt cnmt)
|
||||||
|
{
|
||||||
|
_pfs = pfs;
|
||||||
|
_cnmt = cnmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Nca GetNcaByType(KeySet keySet, ContentType type, int programIndex = 0)
|
||||||
|
{
|
||||||
|
// TODO: Replace this with a check for IdOffset as soon as LibHac supports it:
|
||||||
|
// && entry.IdOffset == programIndex
|
||||||
|
|
||||||
|
foreach (var entry in _cnmt.ContentEntries)
|
||||||
|
{
|
||||||
|
if (entry.Type != type)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ncaId = BitConverter.ToString(entry.NcaId).Replace("-", null).ToLower();
|
||||||
|
Nca nca = _pfs.GetNca(keySet, $"/{ncaId}.nca");
|
||||||
|
|
||||||
|
if (nca.GetProgramIndex() == programIndex)
|
||||||
|
{
|
||||||
|
return nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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))
|
||||||
|
@ -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();
|
||||||
|
@ -474,9 +474,9 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
{
|
{
|
||||||
const int MessageSize = 0x100;
|
const int MessageSize = 0x100;
|
||||||
|
|
||||||
using IMemoryOwner<byte> reqDataOwner = ByteMemoryPool.Rent(MessageSize);
|
using SpanOwner<byte> reqDataOwner = SpanOwner<byte>.Rent(MessageSize);
|
||||||
|
|
||||||
Span<byte> reqDataSpan = reqDataOwner.Memory.Span;
|
Span<byte> reqDataSpan = reqDataOwner.Span;
|
||||||
|
|
||||||
_selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan);
|
_selfProcess.CpuMemory.Read(_selfThread.TlsAddress, reqDataSpan);
|
||||||
|
|
||||||
|
@ -85,9 +85,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
|
|
||||||
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
|
ReadOnlySpan<byte> inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize);
|
||||||
|
|
||||||
using IMemoryOwner<byte> outputParcelOwner = ByteMemoryPool.RentCleared(replySize);
|
using SpanOwner<byte> outputParcelOwner = SpanOwner<byte>.RentCleared(checked((int)replySize));
|
||||||
|
|
||||||
Span<byte> outputParcel = outputParcelOwner.Memory.Span;
|
Span<byte> outputParcel = outputParcelOwner.Span;
|
||||||
|
|
||||||
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
|
ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel);
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ using Ryujinx.Common.Memory;
|
|||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@ -13,7 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
{
|
{
|
||||||
sealed class Parcel : IDisposable
|
sealed class Parcel : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IMemoryOwner<byte> _rawDataOwner;
|
private readonly MemoryOwner<byte> _rawDataOwner;
|
||||||
|
|
||||||
private Span<byte> Raw => _rawDataOwner.Memory.Span;
|
private Span<byte> Raw => _rawDataOwner.Memory.Span;
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
|
|
||||||
public Parcel(ReadOnlySpan<byte> data)
|
public Parcel(ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
_rawDataOwner = ByteMemoryPool.RentCopy(data);
|
_rawDataOwner = MemoryOwner<byte>.RentCopy(data);
|
||||||
|
|
||||||
_payloadPosition = 0;
|
_payloadPosition = 0;
|
||||||
_objectPosition = 0;
|
_objectPosition = 0;
|
||||||
@ -40,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
|||||||
{
|
{
|
||||||
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
|
uint headerSize = (uint)Unsafe.SizeOf<ParcelHeader>();
|
||||||
|
|
||||||
_rawDataOwner = ByteMemoryPool.RentCleared(BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4));
|
_rawDataOwner = MemoryOwner<byte>.RentCleared(checked((int)BitUtils.AlignUp<uint>(headerSize + payloadSize + objectsSize, 4)));
|
||||||
|
|
||||||
Header.PayloadSize = payloadSize;
|
Header.PayloadSize = payloadSize;
|
||||||
Header.ObjectsSize = objectsSize;
|
Header.ObjectsSize = objectsSize;
|
||||||
|
@ -3,7 +3,6 @@ using LibHac.FsSystem;
|
|||||||
using LibHac.Loader;
|
using LibHac.Loader;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using Ryujinx.HLE.HOS;
|
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Processes
|
namespace Ryujinx.HLE.Loaders.Processes
|
||||||
|
@ -7,16 +7,25 @@ using LibHac.Ncm;
|
|||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Tools.Ncm;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
|
using Ryujinx.HLE.Utilities;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||||
|
using ContentType = LibHac.Ncm.ContentType;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
{
|
{
|
||||||
static class NcaExtensions
|
public static class NcaExtensions
|
||||||
{
|
{
|
||||||
|
private static readonly TitleUpdateMetadataJsonSerializerContext _applicationSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
|
|
||||||
public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
|
public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca)
|
||||||
{
|
{
|
||||||
// Extract RomFs and ExeFs from NCA.
|
// Extract RomFs and ExeFs from NCA.
|
||||||
@ -47,7 +56,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
nacpData = controlNca.GetNacp(device);
|
nacpData = controlNca.GetNacp(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update.
|
/* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" non-existent update.
|
||||||
|
|
||||||
// Load program 0 control NCA as we are going to need it for display version.
|
// Load program 0 control NCA as we are going to need it for display version.
|
||||||
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
(_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
||||||
@ -86,6 +95,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
return processResult;
|
return processResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ulong GetProgramIdBase(this Nca nca)
|
||||||
|
{
|
||||||
|
return nca.Header.TitleId & ~0x1FFFUL;
|
||||||
|
}
|
||||||
|
|
||||||
public static int GetProgramIndex(this Nca nca)
|
public static int GetProgramIndex(this Nca nca)
|
||||||
{
|
{
|
||||||
return (int)(nca.Header.TitleId & 0xF);
|
return (int)(nca.Header.TitleId & 0xF);
|
||||||
@ -96,6 +110,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
return nca.Header.ContentType == NcaContentType.Program;
|
return nca.Header.ContentType == NcaContentType.Program;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsMain(this Nca nca)
|
||||||
|
{
|
||||||
|
return nca.IsProgram() && !nca.IsPatch();
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsPatch(this Nca nca)
|
public static bool IsPatch(this Nca nca)
|
||||||
{
|
{
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
@ -108,6 +127,43 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
return nca.Header.ContentType == NcaContentType.Control;
|
return nca.Header.ContentType == NcaContentType.Control;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (Nca, Nca) GetUpdateData(this Nca mainNca, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel, int programIndex, out string updatePath)
|
||||||
|
{
|
||||||
|
updatePath = null;
|
||||||
|
|
||||||
|
// Load Update NCAs.
|
||||||
|
Nca updatePatchNca = null;
|
||||||
|
Nca updateControlNca = null;
|
||||||
|
|
||||||
|
// Clear the program index part.
|
||||||
|
ulong titleIdBase = mainNca.GetProgramIdBase();
|
||||||
|
|
||||||
|
// Load update information if exists.
|
||||||
|
string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "updates.json");
|
||||||
|
if (File.Exists(titleUpdateMetadataPath))
|
||||||
|
{
|
||||||
|
updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _applicationSerializerContext.TitleUpdateMetadata).Selected;
|
||||||
|
if (File.Exists(updatePath))
|
||||||
|
{
|
||||||
|
IFileSystem updatePartitionFileSystem = PartitionFileSystemUtils.OpenApplicationFileSystem(updatePath, fileSystem);
|
||||||
|
|
||||||
|
foreach ((ulong applicationTitleId, ContentMetaData content) in updatePartitionFileSystem.GetContentData(ContentMetaType.Patch, fileSystem, checkLevel))
|
||||||
|
{
|
||||||
|
if ((applicationTitleId & ~0x1FFFUL) != titleIdBase)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePatchNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Program, programIndex);
|
||||||
|
updateControlNca = content.GetNcaByType(fileSystem.KeySet, ContentType.Control, programIndex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (updatePatchNca, updateControlNca);
|
||||||
|
}
|
||||||
|
|
||||||
public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
|
public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null)
|
||||||
{
|
{
|
||||||
IFileSystem exeFs = null;
|
IFileSystem exeFs = null;
|
||||||
@ -172,5 +228,31 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
|
|
||||||
return nacpData;
|
return nacpData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Cnmt GetCnmt(this Nca cnmtNca, IntegrityCheckLevel checkLevel, ContentMetaType metaType)
|
||||||
|
{
|
||||||
|
string path = $"/{metaType}_{cnmtNca.Header.TitleId:x16}.cnmt";
|
||||||
|
using var cnmtFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Result result = cnmtNca.OpenFileSystem(0, checkLevel)
|
||||||
|
.OpenFile(ref cnmtFile.Ref, path.ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
|
if (result.IsSuccess())
|
||||||
|
{
|
||||||
|
return new Cnmt(cnmtFile.Release().AsStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HorizonResultException ex)
|
||||||
|
{
|
||||||
|
if (!ResultFs.PathNotFound.Includes(ex.ResultValue))
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Failed get CNMT for '{cnmtNca.Header.TitleId:x16}' from NCA: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,58 @@
|
|||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
|
using LibHac.Common.Keys;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Ncm;
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Tools.Ncm;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using ContentType = LibHac.Ncm.ContentType;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||||
{
|
{
|
||||||
public static class PartitionFileSystemExtensions
|
public static class PartitionFileSystemExtensions
|
||||||
{
|
{
|
||||||
private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
private static readonly DownloadableContentJsonSerializerContext _contentSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||||
private static readonly TitleUpdateMetadataJsonSerializerContext _titleSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
|
||||||
|
|
||||||
internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, out string errorMessage)
|
public static Dictionary<ulong, ContentMetaData> GetContentData(this IFileSystem partitionFileSystem,
|
||||||
|
ContentMetaType contentType, VirtualFileSystem fileSystem, IntegrityCheckLevel checkLevel)
|
||||||
|
{
|
||||||
|
fileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
var programs = new Dictionary<ulong, ContentMetaData>();
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.cnmt.nca"))
|
||||||
|
{
|
||||||
|
Cnmt cnmt = partitionFileSystem.GetNca(fileSystem.KeySet, fileEntry.FullPath).GetCnmt(checkLevel, contentType);
|
||||||
|
|
||||||
|
if (cnmt == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentMetaData content = new(partitionFileSystem, cnmt);
|
||||||
|
|
||||||
|
if (content.Type != contentType)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
programs.TryAdd(content.ApplicationId, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return programs;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static (bool, ProcessResult) TryLoad<TMetaData, TFormat, THeader, TEntry>(this PartitionFileSystemCore<TMetaData, TFormat, THeader, TEntry> partitionFileSystem, Switch device, string path, ulong applicationId, out string errorMessage)
|
||||||
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
|
where TMetaData : PartitionFileSystemMetaCore<TFormat, THeader, TEntry>, new()
|
||||||
where TFormat : IPartitionFileSystemFormat
|
where TFormat : IPartitionFileSystemFormat
|
||||||
where THeader : unmanaged, IPartitionFileSystemHeader
|
where THeader : unmanaged, IPartitionFileSystemHeader
|
||||||
@ -35,31 +67,22 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem);
|
Dictionary<ulong, ContentMetaData> applications = partitionFileSystem.GetContentData(ContentMetaType.Application, device.FileSystem, device.System.FsIntegrityCheckLevel);
|
||||||
|
|
||||||
// TODO: To support multi-games container, this should use CNMT NCA instead.
|
if (applicationId == 0)
|
||||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
{
|
||||||
Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
|
foreach ((ulong _, ContentMetaData content) in applications)
|
||||||
|
|
||||||
if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
|
|
||||||
{
|
{
|
||||||
continue;
|
mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
|
||||||
}
|
controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
|
||||||
|
break;
|
||||||
if (nca.IsPatch())
|
|
||||||
{
|
|
||||||
patchNca = nca;
|
|
||||||
}
|
|
||||||
else if (nca.IsProgram())
|
|
||||||
{
|
|
||||||
mainNca = nca;
|
|
||||||
}
|
|
||||||
else if (nca.IsControl())
|
|
||||||
{
|
|
||||||
controlNca = nca;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (applications.TryGetValue(applicationId, out ContentMetaData content))
|
||||||
|
{
|
||||||
|
mainNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Program, device.Configuration.UserChannelPersistence.Index);
|
||||||
|
controlNca = content.GetNcaByType(device.FileSystem.KeySet, ContentType.Control, device.Configuration.UserChannelPersistence.Index);
|
||||||
|
}
|
||||||
|
|
||||||
ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
|
ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure();
|
||||||
}
|
}
|
||||||
@ -79,54 +102,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
return (false, ProcessResult.Failed);
|
return (false, ProcessResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Update NCAs.
|
(Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
|
||||||
Nca updatePatchNca = null;
|
|
||||||
Nca updateControlNca = null;
|
|
||||||
|
|
||||||
if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
|
|
||||||
{
|
|
||||||
// Clear the program index part.
|
|
||||||
titleIdBase &= ~0xFUL;
|
|
||||||
|
|
||||||
// Load update information if exists.
|
|
||||||
string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
|
|
||||||
if (File.Exists(titleUpdateMetadataPath))
|
|
||||||
{
|
|
||||||
string updatePath = JsonHelper.DeserializeFromFile(titleUpdateMetadataPath, _titleSerializerContext.TitleUpdateMetadata).Selected;
|
|
||||||
if (File.Exists(updatePath))
|
|
||||||
{
|
|
||||||
PartitionFileSystem updatePartitionFileSystem = new();
|
|
||||||
updatePartitionFileSystem.Initialize(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage()).ThrowIfFailure();
|
|
||||||
|
|
||||||
device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem);
|
|
||||||
|
|
||||||
// TODO: This should use CNMT NCA instead.
|
|
||||||
foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath);
|
|
||||||
|
|
||||||
if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16"))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.IsProgram())
|
|
||||||
{
|
|
||||||
updatePatchNca = nca;
|
|
||||||
}
|
|
||||||
else if (nca.IsControl())
|
|
||||||
{
|
|
||||||
updateControlNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
@ -138,10 +114,8 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
controlNca = updateControlNca;
|
controlNca = updateControlNca;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load contained DownloadableContents.
|
|
||||||
// TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
|
// TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
|
||||||
device.Configuration.ContentManager.ClearAocData();
|
device.Configuration.ContentManager.ClearAocData();
|
||||||
device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel);
|
|
||||||
|
|
||||||
// Load DownloadableContents.
|
// Load DownloadableContents.
|
||||||
string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
|
||||||
@ -153,9 +127,12 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
{
|
{
|
||||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||||
{
|
{
|
||||||
if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled)
|
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||||
{
|
{
|
||||||
device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
|
if (downloadableContentNca.Enabled)
|
||||||
|
{
|
||||||
|
device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -168,18 +145,18 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
|||||||
return (true, mainNca.Load(device, patchNca, controlNca));
|
return (true, mainNca.Load(device, patchNca, controlNca));
|
||||||
}
|
}
|
||||||
|
|
||||||
errorMessage = "Unable to load: Could not find Main NCA";
|
errorMessage = $"Unable to load: Could not find Main NCA for title \"{applicationId:X16}\"";
|
||||||
|
|
||||||
return (false, ProcessResult.Failed);
|
return (false, ProcessResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path)
|
public static Nca GetNca(this IFileSystem fileSystem, KeySet keySet, string path)
|
||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage());
|
return new Nca(keySet, ncaFile.Release().AsStorage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
_processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
|
_processesByPid = new ConcurrentDictionary<ulong, ProcessResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadXci(string path)
|
public bool LoadXci(string path, ulong applicationId)
|
||||||
{
|
{
|
||||||
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
|
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
|
||||||
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
|
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
|
||||||
@ -44,7 +44,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
(bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, out string errorMessage);
|
(bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, applicationId, out string errorMessage);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
@ -66,13 +66,13 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadNsp(string path)
|
public bool LoadNsp(string path, ulong applicationId)
|
||||||
{
|
{
|
||||||
FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||||
PartitionFileSystem partitionFileSystem = new();
|
PartitionFileSystem partitionFileSystem = new();
|
||||||
partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
|
partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||||
|
|
||||||
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage);
|
(bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, applicationId, out string errorMessage);
|
||||||
|
|
||||||
if (processResult.ProcessId == 0)
|
if (processResult.ProcessId == 0)
|
||||||
{
|
{
|
||||||
|
@ -43,15 +43,14 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
|
Nca nca = partitionFileSystem.GetNca(device.FileSystem.KeySet, fileEntry.FullPath);
|
||||||
|
|
||||||
if (!nca.IsProgram() && nca.IsPatch())
|
if (!nca.IsProgram())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong currentProgramId = nca.Header.TitleId;
|
ulong currentMainProgramId = nca.GetProgramIdBase();
|
||||||
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
|
|
||||||
|
|
||||||
if (applicationId == 0 && currentMainProgramId != 0)
|
if (applicationId == 0 && currentMainProgramId != 0)
|
||||||
{
|
{
|
||||||
@ -68,7 +67,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasIndex[(int)(currentProgramId & 0xF)] = true;
|
hasIndex[nca.GetProgramIndex()] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (programCount == 0)
|
if (programCount == 0)
|
||||||
|
@ -73,9 +73,9 @@ namespace Ryujinx.HLE
|
|||||||
return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
|
return Processes.LoadUnpackedNca(exeFsDir, romFsFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadXci(string xciFile)
|
public bool LoadXci(string xciFile, ulong applicationId = 0)
|
||||||
{
|
{
|
||||||
return Processes.LoadXci(xciFile);
|
return Processes.LoadXci(xciFile, applicationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadNca(string ncaFile)
|
public bool LoadNca(string ncaFile)
|
||||||
@ -83,9 +83,9 @@ namespace Ryujinx.HLE
|
|||||||
return Processes.LoadNca(ncaFile);
|
return Processes.LoadNca(ncaFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadNsp(string nspFile)
|
public bool LoadNsp(string nspFile, ulong applicationId = 0)
|
||||||
{
|
{
|
||||||
return Processes.LoadNsp(nspFile);
|
return Processes.LoadNsp(nspFile, applicationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadProgram(string fileName)
|
public bool LoadProgram(string fileName)
|
||||||
|
45
src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs
Normal file
45
src/Ryujinx.HLE/Utilities/PartitionFileSystemUtils.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using LibHac;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.Utilities
|
||||||
|
{
|
||||||
|
public static class PartitionFileSystemUtils
|
||||||
|
{
|
||||||
|
public static IFileSystem OpenApplicationFileSystem(string path, VirtualFileSystem fileSystem, bool throwOnFailure = true)
|
||||||
|
{
|
||||||
|
FileStream file = File.OpenRead(path);
|
||||||
|
|
||||||
|
IFileSystem partitionFileSystem;
|
||||||
|
|
||||||
|
if (Path.GetExtension(path).ToLower() == ".xci")
|
||||||
|
{
|
||||||
|
partitionFileSystem = new Xci(fileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pfsTemp = new PartitionFileSystem();
|
||||||
|
Result initResult = pfsTemp.Initialize(file.AsStorage());
|
||||||
|
|
||||||
|
if (throwOnFailure)
|
||||||
|
{
|
||||||
|
initResult.ThrowIfFailure();
|
||||||
|
}
|
||||||
|
else if (initResult.IsFailure())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
partitionFileSystem = pfsTemp;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
return partitionFileSystem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,11 @@ namespace Ryujinx.Horizon.Ngc.Ipc
|
|||||||
}
|
}
|
||||||
|
|
||||||
[CmifCommand(1)]
|
[CmifCommand(1)]
|
||||||
public Result Check(out uint checkMask, ReadOnlySpan<byte> text, uint regionMask, ProfanityFilterOption option)
|
public Result Check(
|
||||||
|
out uint checkMask,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> text,
|
||||||
|
uint regionMask,
|
||||||
|
ProfanityFilterOption option)
|
||||||
{
|
{
|
||||||
lock (_profanityFilter)
|
lock (_profanityFilter)
|
||||||
{
|
{
|
||||||
|
@ -9,9 +9,11 @@ using LibHac.Tools.FsSystem;
|
|||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ryujinx.UI.App.Common
|
namespace Ryujinx.UI.App.Common
|
||||||
{
|
{
|
||||||
@ -19,10 +21,10 @@ namespace Ryujinx.UI.App.Common
|
|||||||
{
|
{
|
||||||
public bool Favorite { get; set; }
|
public bool Favorite { get; set; }
|
||||||
public byte[] Icon { get; set; }
|
public byte[] Icon { get; set; }
|
||||||
public string TitleName { get; set; }
|
public string Name { get; set; } = "Unknown";
|
||||||
public string TitleId { get; set; }
|
public ulong Id { get; set; }
|
||||||
public string Developer { get; set; }
|
public string Developer { get; set; } = "Unknown";
|
||||||
public string Version { get; set; }
|
public string Version { get; set; } = "0";
|
||||||
public TimeSpan TimePlayed { get; set; }
|
public TimeSpan TimePlayed { get; set; }
|
||||||
public DateTime? LastPlayed { get; set; }
|
public DateTime? LastPlayed { get; set; }
|
||||||
public string FileExtension { get; set; }
|
public string FileExtension { get; set; }
|
||||||
@ -36,7 +38,11 @@ namespace Ryujinx.UI.App.Common
|
|||||||
|
|
||||||
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
|
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
|
||||||
|
|
||||||
public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
|
[JsonIgnore] public string IdString => Id.ToString("x16");
|
||||||
|
|
||||||
|
[JsonIgnore] public ulong IdBase => Id & ~0x1FFFUL;
|
||||||
|
|
||||||
|
public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
|
||||||
{
|
{
|
||||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
@ -105,7 +111,7 @@ namespace Ryujinx.UI.App.Common
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
(Nca updatePatchNca, _) = mainNca.GetUpdateData(virtualFileSystem, checkLevel, 0, out string _);
|
||||||
|
|
||||||
if (updatePatchNca != null)
|
if (updatePatchNca != null)
|
||||||
{
|
{
|
||||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user