Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
@ -20,9 +20,9 @@
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<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.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="NetCoreServer" Version="8.0.7" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
|
@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
long originalPosition = _stream.Position;
|
||||
|
||||
_stream.Seek(0, SeekOrigin.Begin);
|
||||
_stream.Read(code, 0, code.Length);
|
||||
_stream.ReadExactly(code, 0, code.Length);
|
||||
_stream.Seek(originalPosition, SeekOrigin.Begin);
|
||||
|
||||
RelocInfo relocInfo;
|
||||
|
@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
}
|
||||
}
|
||||
|
||||
int selectedReg = GetHighestValueIndex(freePositions);
|
||||
// If this is a copy destination variable, we prefer the register used for the copy source.
|
||||
// If the register is available, then the copy can be eliminated later as both source
|
||||
// and destination will use the same register.
|
||||
int selectedReg;
|
||||
|
||||
if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
|
||||
{
|
||||
selectedReg = preferredReg;
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedReg = GetHighestValueIndex(freePositions);
|
||||
}
|
||||
|
||||
int selectedNextUse = freePositions[selectedReg];
|
||||
|
||||
// Intervals starts and ends at odd positions, unless they span an entire
|
||||
@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetHighestValueIndex(Span<int> span)
|
||||
private static int GetHighestValueIndex(ReadOnlySpan<int> span)
|
||||
{
|
||||
int highest = int.MinValue;
|
||||
|
||||
@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
// The "visited" state is stored in the MSB of the local's value.
|
||||
const ulong VisitedMask = 1ul << 63;
|
||||
|
||||
bool IsVisited(Operand local)
|
||||
static bool IsVisited(Operand local)
|
||||
{
|
||||
return (local.GetValueUnsafe() & VisitedMask) != 0;
|
||||
}
|
||||
|
||||
void SetVisited(Operand local)
|
||||
static void SetVisited(Operand local)
|
||||
{
|
||||
local.GetValueUnsafe() |= VisitedMask;
|
||||
}
|
||||
@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
{
|
||||
dest.NumberLocal(_intervals.Count);
|
||||
|
||||
_intervals.Add(new LiveInterval(dest));
|
||||
LiveInterval interval = new LiveInterval(dest);
|
||||
_intervals.Add(interval);
|
||||
|
||||
SetVisited(dest);
|
||||
|
||||
// If this is a copy (or copy-like operation), set the copy source interval as well.
|
||||
// This is used for register preferencing later on, which allows the copy to be eliminated
|
||||
// in some cases.
|
||||
if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
if (source.Kind == OperandKind.LocalVariable &&
|
||||
source.GetLocalNumber() > 0 &&
|
||||
(node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
|
||||
{
|
||||
interval.SetCopySource(_intervals[source.GetLocalNumber()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
public LiveRange CurrRange;
|
||||
|
||||
public LiveInterval Parent;
|
||||
public LiveInterval CopySource;
|
||||
|
||||
public UseList Uses;
|
||||
public LiveIntervalList Children;
|
||||
@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
private ref LiveRange CurrRange => ref _data->CurrRange;
|
||||
private ref LiveRange PrevRange => ref _data->PrevRange;
|
||||
private ref LiveInterval Parent => ref _data->Parent;
|
||||
private ref LiveInterval CopySource => ref _data->CopySource;
|
||||
private ref UseList Uses => ref _data->Uses;
|
||||
private ref LiveIntervalList Children => ref _data->Children;
|
||||
|
||||
@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
Register = register;
|
||||
}
|
||||
|
||||
public void SetCopySource(LiveInterval copySource)
|
||||
{
|
||||
CopySource = copySource;
|
||||
}
|
||||
|
||||
public bool TryGetCopySourceRegister(out int copySourceRegIndex)
|
||||
{
|
||||
if (CopySource._data != null)
|
||||
{
|
||||
copySourceRegIndex = CopySource.Register.Index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
copySourceRegIndex = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
PrevRange = default;
|
||||
|
@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
|
||||
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
|
||||
|
||||
_stream.Read(buffer);
|
||||
_stream.ReadExactly(buffer);
|
||||
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
|
||||
|
||||
codeStream.Write(buffer);
|
||||
|
@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
|
||||
private int[] _postOrderMap;
|
||||
|
||||
public int LocalsCount { get; private set; }
|
||||
public BasicBlock Entry { get; }
|
||||
public BasicBlock Entry { get; private set; }
|
||||
public IntrusiveList<BasicBlock> Blocks { get; }
|
||||
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
|
||||
public int[] PostOrderMap => _postOrderMap;
|
||||
@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
|
||||
return result;
|
||||
}
|
||||
|
||||
public void UpdateEntry(BasicBlock newEntry)
|
||||
{
|
||||
newEntry.AddSuccessor(Entry);
|
||||
|
||||
Entry = newEntry;
|
||||
Blocks.AddFirst(newEntry);
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
RemoveUnreachableBlocks(Blocks);
|
||||
|
@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
|
||||
|
||||
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
|
||||
{
|
||||
if (cfg.Entry.Predecessors.Count != 0)
|
||||
{
|
||||
// We expect the entry block to have no predecessors.
|
||||
// This is required because we have a implicit context load at the start of the function,
|
||||
// but if there is a jump to the start of the function, the context load would trash the modified values.
|
||||
// Here we insert a new entry block that will jump to the existing entry block.
|
||||
BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
|
||||
|
||||
cfg.UpdateEntry(newEntry);
|
||||
}
|
||||
|
||||
// Compute local register inputs and outputs used inside blocks.
|
||||
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
|
||||
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
|
||||
@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
// The only block without any predecessor should be the entry block.
|
||||
// It always needs a context load as it is the first block to run.
|
||||
if (block.Predecessors.Count == 0 || hasContextLoad)
|
||||
if (block == cfg.Entry || hasContextLoad)
|
||||
{
|
||||
long vecMask = globalInputs[block.Index].VecMask;
|
||||
long intMask = globalInputs[block.Index].IntMask;
|
||||
|
@ -89,9 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
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);
|
||||
|
||||
|
@ -122,9 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
|
||||
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);
|
||||
|
||||
|
@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Backends.Common
|
||||
|
||||
private readonly object _lock = new();
|
||||
|
||||
private IMemoryOwner<byte> _bufferOwner;
|
||||
private MemoryOwner<byte> _bufferOwner;
|
||||
private Memory<byte> _buffer;
|
||||
private int _size;
|
||||
private int _headOffset;
|
||||
@ -24,7 +24,7 @@ namespace Ryujinx.Audio.Backends.Common
|
||||
|
||||
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
|
||||
{
|
||||
_bufferOwner = ByteMemoryPool.RentCleared(initialCapacity);
|
||||
_bufferOwner = MemoryOwner<byte>.RentCleared(initialCapacity);
|
||||
_buffer = _bufferOwner.Memory;
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Backends.Common
|
||||
|
||||
private void SetCapacityLocked(int capacity)
|
||||
{
|
||||
IMemoryOwner<byte> newBufferOwner = ByteMemoryPool.RentCleared(capacity);
|
||||
MemoryOwner<byte> newBufferOwner = MemoryOwner<byte>.RentCleared(capacity);
|
||||
Memory<byte> newBuffer = newBufferOwner.Memory;
|
||||
|
||||
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
|
||||
{
|
||||
public interface IImageArray
|
||||
public interface IImageArray : IDisposable
|
||||
{
|
||||
void SetFormats(int index, Format[] imageFormats);
|
||||
void SetImages(int index, ITexture[] images);
|
||||
|
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public interface ITextureArray
|
||||
public interface ITextureArray : IDisposable
|
||||
{
|
||||
void SetSamplers(int index, ISampler[] samplers);
|
||||
void SetTextures(int index, ITexture[] textures);
|
||||
|
@ -66,6 +66,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
|
||||
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
|
||||
|
||||
Register<ImageArrayDisposeCommand>(CommandType.ImageArrayDispose);
|
||||
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
|
||||
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
|
||||
|
||||
@ -88,6 +89,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
|
||||
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
|
||||
|
||||
Register<TextureArrayDisposeCommand>(CommandType.TextureArrayDispose);
|
||||
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
|
||||
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);
|
||||
|
||||
|
@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
CounterEventDispose,
|
||||
CounterEventFlush,
|
||||
|
||||
ImageArrayDispose,
|
||||
ImageArraySetFormats,
|
||||
ImageArraySetImages,
|
||||
|
||||
@ -48,6 +49,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
TextureSetDataSliceRegion,
|
||||
TextureSetStorage,
|
||||
|
||||
TextureArrayDispose,
|
||||
TextureArraySetSamplers,
|
||||
TextureArraySetTextures,
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_renderer.New<ImageArrayDisposeCommand>().Set(Ref(this));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetFormats(int index, Format[] 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);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_renderer.New<TextureArrayDisposeCommand>().Set(Ref(this));
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void SetSamplers(int index, ISampler[] samplers)
|
||||
{
|
||||
_renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray()));
|
||||
|
@ -74,13 +74,15 @@ namespace Ryujinx.Graphics.GAL
|
||||
public int ArrayLength { get; }
|
||||
public ResourceType Type { 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;
|
||||
ArrayLength = arrayLength;
|
||||
Type = type;
|
||||
Stages = stages;
|
||||
Write = write;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
|
@ -1113,6 +1113,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
nextNode = nextNode.Next;
|
||||
_cacheFromBuffer.Remove(toRemove.Value.Key);
|
||||
_lruCache.Remove(toRemove);
|
||||
|
||||
if (toRemove.Value.Key.IsImage)
|
||||
{
|
||||
toRemove.Value.ImageArray.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
toRemove.Value.TextureArray.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1124,11 +1133,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
List<CacheEntryFromPoolKey> keysToRemove = null;
|
||||
|
||||
foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys)
|
||||
foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool)
|
||||
{
|
||||
if (key.MatchesPool(pool))
|
||||
{
|
||||
(keysToRemove ??= new()).Add(key);
|
||||
|
||||
if (key.IsImage)
|
||||
{
|
||||
entry.ImageArray.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.TextureArray.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
switch (algorithm)
|
||||
{
|
||||
case CompressionAlgorithm.None:
|
||||
stream.Read(data);
|
||||
stream.ReadExactly(data);
|
||||
break;
|
||||
case CompressionAlgorithm.Deflate:
|
||||
stream = new DeflateStream(stream, CompressionMode.Decompress, true);
|
||||
|
@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
|
||||
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
|
||||
dataFileStream.Read(cb1Data);
|
||||
dataFileStream.ReadExactly(cb1Data);
|
||||
BinarySerializer.ReadCompressed(dataFileStream, guestCode);
|
||||
|
||||
_cache[index] = (guestCode, cb1Data);
|
||||
@ -279,7 +279,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
|
||||
byte[] cachedCode = new byte[entry.CodeSize];
|
||||
byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
|
||||
dataFileStream.Read(cachedCb1Data);
|
||||
dataFileStream.ReadExactly(cachedCb1Data);
|
||||
BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
|
||||
|
||||
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
|
||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 6852;
|
||||
private const uint CodeGenVersion = 6921;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@ -78,9 +78,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
|
||||
|
||||
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.BufferImage, imageSetIndex, 0, rrc.ReservedImages);
|
||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -91,10 +91,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <param name="setIndex">Resource set index where the resources are used</param>
|
||||
/// <param name="start">First binding number</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);
|
||||
AddUsage(stages, type, setIndex, start, count);
|
||||
AddUsage(stages, type, setIndex, start, count, write);
|
||||
}
|
||||
|
||||
/// <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="binding">Binding number where the resource will be bound</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++)
|
||||
{
|
||||
_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,
|
||||
1,
|
||||
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);
|
||||
|
||||
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;
|
||||
TargetApi = parameters.TargetApi;
|
||||
|
||||
AddCapability(Capability.Shader);
|
||||
AddCapability(Capability.Float64);
|
||||
|
||||
SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
|
||||
|
||||
Delegates = new SpirvDelegates(this);
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
CodeGenContext context = new(info, parameters, instPool, integerPool);
|
||||
|
||||
context.AddCapability(Capability.Shader);
|
||||
|
||||
context.SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
|
||||
|
||||
context.AddCapability(Capability.GroupNonUniformBallot);
|
||||
context.AddCapability(Capability.GroupNonUniformShuffle);
|
||||
context.AddCapability(Capability.GroupNonUniformVote);
|
||||
@ -51,6 +55,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
context.AddCapability(Capability.ImageQuery);
|
||||
context.AddCapability(Capability.SampledBuffer);
|
||||
|
||||
if (parameters.HostCapabilities.SupportsShaderFloat64)
|
||||
{
|
||||
context.AddCapability(Capability.Float64);
|
||||
}
|
||||
|
||||
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
|
||||
{
|
||||
context.AddCapability(Capability.TransformFeedback);
|
||||
@ -58,7 +67,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
if (parameters.Definitions.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
|
||||
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)) ||
|
||||
context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.PrimitiveId)))
|
||||
{
|
||||
context.AddCapability(Capability.Geometry);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
|
||||
if (op.BVal)
|
||||
{
|
||||
context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
|
||||
context.Copy(dest, context.ConditionalSelect(res, ConstF(1), ConstF(0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -156,6 +156,26 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsComparison(this Instruction inst)
|
||||
{
|
||||
switch (inst & Instruction.Mask)
|
||||
{
|
||||
case Instruction.CompareEqual:
|
||||
case Instruction.CompareGreater:
|
||||
case Instruction.CompareGreaterOrEqual:
|
||||
case Instruction.CompareGreaterOrEqualU32:
|
||||
case Instruction.CompareGreaterU32:
|
||||
case Instruction.CompareLess:
|
||||
case Instruction.CompareLessOrEqual:
|
||||
case Instruction.CompareLessOrEqualU32:
|
||||
case Instruction.CompareLessU32:
|
||||
case Instruction.CompareNotEqual:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsTextureQuery(this Instruction inst)
|
||||
{
|
||||
inst &= Instruction.Mask;
|
||||
|
@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
public readonly bool SupportsShaderBallot;
|
||||
public readonly bool SupportsShaderBarrierDivergence;
|
||||
public readonly bool SupportsShaderFloat64;
|
||||
public readonly bool SupportsTextureShadowLod;
|
||||
public readonly bool SupportsViewportMask;
|
||||
|
||||
@ -18,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
bool supportsShaderBallot,
|
||||
bool supportsShaderBarrierDivergence,
|
||||
bool supportsShaderFloat64,
|
||||
bool supportsTextureShadowLod,
|
||||
bool supportsViewportMask)
|
||||
{
|
||||
@ -27,6 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
SupportsShaderBallot = supportsShaderBallot;
|
||||
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||
SupportsViewportMask = supportsViewportMask;
|
||||
}
|
||||
|
@ -141,16 +141,16 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsBindlessAccessAllowed(Operand nvHandle)
|
||||
private static bool IsBindlessAccessAllowed(Operand bindlessHandle)
|
||||
{
|
||||
if (nvHandle.Type == OperandType.ConstantBuffer)
|
||||
if (bindlessHandle.Type == OperandType.ConstantBuffer)
|
||||
{
|
||||
// Bindless access with handles from constant buffer is allowed.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nvHandle.AsgOp is not Operation handleOp ||
|
||||
if (bindlessHandle.AsgOp is not Operation handleOp ||
|
||||
handleOp.Inst != Instruction.Load ||
|
||||
(handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
|
||||
{
|
||||
@ -300,7 +300,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
resourceManager,
|
||||
gpuAccessor,
|
||||
texOp,
|
||||
TextureHandle.PackOffsets(src0.GetCbufOffset(), ((src1.Value >> 20) & 0xfff), handleType),
|
||||
TextureHandle.PackOffsets(src0.GetCbufOffset(), (src1.Value >> 20) & 0xfff, handleType),
|
||||
TextureHandle.PackSlots(src0.GetCbufSlot(), 0),
|
||||
rewriteSamplerType,
|
||||
isImage: false);
|
||||
|
@ -126,7 +126,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
continue;
|
||||
}
|
||||
|
||||
if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
|
||||
Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
|
||||
|
||||
if (bindlessHandle.AsgOp is not Operation handleAsgOp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -137,8 +139,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
|
||||
if (handleAsgOp.Inst == Instruction.BitwiseOr)
|
||||
{
|
||||
Operand src0 = handleAsgOp.GetSource(0);
|
||||
Operand src1 = handleAsgOp.GetSource(1);
|
||||
Operand src0 = Utils.FindLastOperation(handleAsgOp.GetSource(0), block);
|
||||
Operand src1 = Utils.FindLastOperation(handleAsgOp.GetSource(1), block);
|
||||
|
||||
if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation)
|
||||
{
|
||||
|
@ -152,18 +152,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
{
|
||||
// If all phi sources are the same, we can propagate it and remove the phi.
|
||||
|
||||
Operand firstSrc = phi.GetSource(0);
|
||||
|
||||
for (int index = 1; index < phi.SourcesCount; index++)
|
||||
if (!Utils.AreAllSourcesTheSameOperand(phi))
|
||||
{
|
||||
if (!IsSameOperand(firstSrc, phi.GetSource(index)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// All sources are equal, we can propagate the value.
|
||||
|
||||
Operand firstSrc = phi.GetSource(0);
|
||||
Operand dest = phi.Dest;
|
||||
|
||||
INode[] uses = dest.UseOps.ToArray();
|
||||
@ -182,17 +178,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsSameOperand(Operand x, Operand y)
|
||||
{
|
||||
if (x.Type != y.Type || x.Value != y.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Handle Load operations with the same storage and the same constant parameters.
|
||||
return x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
|
||||
}
|
||||
|
||||
private static bool PropagatePack(Operation packOp)
|
||||
{
|
||||
// Propagate pack source operands to uses by unpack
|
||||
|
@ -31,6 +31,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
TryEliminateBitwiseOr(operation);
|
||||
break;
|
||||
|
||||
case Instruction.CompareNotEqual:
|
||||
TryEliminateCompareNotEqual(operation);
|
||||
break;
|
||||
|
||||
case Instruction.ConditionalSelect:
|
||||
TryEliminateConditionalSelect(operation);
|
||||
break;
|
||||
@ -174,6 +178,32 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryEliminateCompareNotEqual(Operation operation)
|
||||
{
|
||||
// Comparison instruction returns 0 if the result is false, and -1 if true.
|
||||
// Doing a not equal zero comparison on the result is redundant, so we can just copy the first result in this case.
|
||||
|
||||
Operand lhs = operation.GetSource(0);
|
||||
Operand rhs = operation.GetSource(1);
|
||||
|
||||
if (lhs.Type == OperandType.Constant)
|
||||
{
|
||||
(lhs, rhs) = (rhs, lhs);
|
||||
}
|
||||
|
||||
if (rhs.Type != OperandType.Constant || rhs.Value != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (lhs.AsgOp is not Operation compareOp || !compareOp.Inst.IsComparison())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
operation.TurnIntoCopy(lhs);
|
||||
}
|
||||
|
||||
private static void TryEliminateConditionalSelect(Operation operation)
|
||||
{
|
||||
Operand cond = operation.GetSource(0);
|
||||
|
@ -34,6 +34,50 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
return elemIndexSrc.Type == OperandType.Constant && elemIndexSrc.Value == elemIndex;
|
||||
}
|
||||
|
||||
private static bool IsSameOperand(Operand x, Operand y)
|
||||
{
|
||||
if (x.Type != y.Type || x.Value != y.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Handle Load operations with the same storage and the same constant parameters.
|
||||
return x == y || x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
|
||||
}
|
||||
|
||||
private static bool AreAllSourcesEqual(INode node, INode otherNode)
|
||||
{
|
||||
if (node.SourcesCount != otherNode.SourcesCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < node.SourcesCount; index++)
|
||||
{
|
||||
if (!IsSameOperand(node.GetSource(index), otherNode.GetSource(index)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool AreAllSourcesTheSameOperand(INode node)
|
||||
{
|
||||
Operand firstSrc = node.GetSource(0);
|
||||
|
||||
for (int index = 1; index < node.SourcesCount; index++)
|
||||
{
|
||||
if (!IsSameOperand(firstSrc, node.GetSource(index)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Operation FindBranchSource(BasicBlock block)
|
||||
{
|
||||
foreach (BasicBlock sourceBlock in block.Predecessors)
|
||||
@ -55,6 +99,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
return inst == Instruction.BranchIfFalse || inst == Instruction.BranchIfTrue;
|
||||
}
|
||||
|
||||
private static bool IsSameCondition(Operand currentCondition, Operand queryCondition)
|
||||
{
|
||||
if (currentCondition == queryCondition)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return currentCondition.AsgOp is Operation currentOperation &&
|
||||
queryCondition.AsgOp is Operation queryOperation &&
|
||||
currentOperation.Inst == queryOperation.Inst &&
|
||||
AreAllSourcesEqual(currentOperation, queryOperation);
|
||||
}
|
||||
|
||||
private static bool BlockConditionsMatch(BasicBlock currentBlock, BasicBlock queryBlock)
|
||||
{
|
||||
// Check if all the conditions for the query block are satisfied by the current block.
|
||||
@ -70,10 +127,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
|
||||
return currentBranch != null && queryBranch != null &&
|
||||
currentBranch.Inst == queryBranch.Inst &&
|
||||
currentCondition == queryCondition;
|
||||
IsSameCondition(currentCondition, queryCondition);
|
||||
}
|
||||
|
||||
public static Operand FindLastOperation(Operand source, BasicBlock block)
|
||||
public static Operand FindLastOperation(Operand source, BasicBlock block, bool recurse = true)
|
||||
{
|
||||
if (source.AsgOp is PhiNode phiNode)
|
||||
{
|
||||
@ -84,10 +141,23 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
|
||||
{
|
||||
BasicBlock phiBlock = phiNode.GetBlock(i);
|
||||
Operand phiSource = phiNode.GetSource(i);
|
||||
|
||||
if (BlockConditionsMatch(block, phiBlock))
|
||||
{
|
||||
return phiNode.GetSource(i);
|
||||
return phiSource;
|
||||
}
|
||||
else if (recurse && phiSource.AsgOp is PhiNode)
|
||||
{
|
||||
// Phi source is another phi.
|
||||
// Let's check if that phi has a block that matches our condition.
|
||||
|
||||
Operand match = FindLastOperation(phiSource, block, false);
|
||||
|
||||
if (match != phiSource)
|
||||
{
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -363,6 +363,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(),
|
||||
GpuAccessor.QueryHostSupportsShaderBallot(),
|
||||
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
|
||||
GpuAccessor.QueryHostSupportsShaderFloat64(),
|
||||
GpuAccessor.QueryHostSupportsTextureShadowLod(),
|
||||
GpuAccessor.QueryHostSupportsViewportMask());
|
||||
|
||||
|
@ -29,7 +29,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@ -8,22 +9,64 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
private const int MaxBarriersPerCall = 16;
|
||||
|
||||
private const AccessFlags BaseAccess = AccessFlags.ShaderReadBit | AccessFlags.ShaderWriteBit;
|
||||
private const AccessFlags BufferAccess = AccessFlags.IndexReadBit | AccessFlags.VertexAttributeReadBit | AccessFlags.UniformReadBit;
|
||||
private const AccessFlags CommandBufferAccess = AccessFlags.IndirectCommandReadBit;
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
|
||||
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
|
||||
|
||||
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<MemoryBarrier, int>> _memoryBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier, int>> _bufferBarriers = new();
|
||||
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier, TextureStorage>> _imageBarriers = new();
|
||||
private int _queuedBarrierCount;
|
||||
|
||||
private enum IncoherentBarrierType
|
||||
{
|
||||
None,
|
||||
Texture,
|
||||
All,
|
||||
CommandBuffer
|
||||
}
|
||||
|
||||
private PipelineStageFlags _incoherentBufferWriteStages;
|
||||
private PipelineStageFlags _incoherentTextureWriteStages;
|
||||
private PipelineStageFlags _extraStages;
|
||||
private IncoherentBarrierType _queuedIncoherentBarrier;
|
||||
|
||||
public BarrierBatch(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
}
|
||||
|
||||
public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd)
|
||||
{
|
||||
AccessFlags access = BufferAccess;
|
||||
PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit;
|
||||
|
||||
if (gd.TransformFeedbackApi != null)
|
||||
{
|
||||
access |= AccessFlags.TransformFeedbackWriteBitExt;
|
||||
stages |= PipelineStageFlags.TransformFeedbackBitExt;
|
||||
}
|
||||
|
||||
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>
|
||||
{
|
||||
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 T Barrier;
|
||||
public readonly T2 Resource;
|
||||
|
||||
public BarrierWithStageFlags(StageFlags flags, T barrier)
|
||||
{
|
||||
Flags = flags;
|
||||
Barrier = barrier;
|
||||
Resource = default;
|
||||
}
|
||||
|
||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
|
||||
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier, T2 resource)
|
||||
{
|
||||
Flags = new StageFlags(srcStageFlags, dstStageFlags);
|
||||
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++;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
int memoryCount = 0;
|
||||
@ -86,20 +212,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bool hasBarrier = false;
|
||||
StageFlags flags = default;
|
||||
|
||||
static void AddBarriers<T>(
|
||||
static void AddBarriers<T, T2>(
|
||||
Span<T> target,
|
||||
ref int queuedBarrierCount,
|
||||
ref bool hasBarrier,
|
||||
ref StageFlags flags,
|
||||
ref int count,
|
||||
List<BarrierWithStageFlags<T>> list) where T : unmanaged
|
||||
List<BarrierWithStageFlags<T, T2>> list) where T : unmanaged
|
||||
{
|
||||
int firstMatch = -1;
|
||||
int end = list.Count;
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
BarrierWithStageFlags<T> barrier = list[i];
|
||||
BarrierWithStageFlags<T, T2> barrier = list[i];
|
||||
|
||||
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,
|
||||
// but inside a render pass they're scoped to just the range of the render pass.
|
||||
|
||||
// On MoltenVK, we just break the rules and always use image barrier.
|
||||
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
|
||||
// TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
|
||||
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
|
||||
// Generally, we want to avoid this from happening in the future, so flag the texture to immediately
|
||||
// emit a barrier whenever the current render pass is bound again.
|
||||
|
||||
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)
|
||||
{
|
||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
|
||||
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier, int>(
|
||||
barrier.Flags,
|
||||
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(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
|
||||
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
|
||||
@ -198,14 +379,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
PipelineStageFlags srcStageFlags = flags.Source;
|
||||
|
||||
if (insideRenderPass)
|
||||
if (inRenderPass)
|
||||
{
|
||||
// Inside a render pass, barrier stages can only be from rasterization.
|
||||
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
cb,
|
||||
cbs.CommandBuffer,
|
||||
srcStageFlags,
|
||||
flags.Dest,
|
||||
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()
|
||||
{
|
||||
_memoryBarrierBatch.Dispose();
|
||||
|
@ -103,12 +103,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
usage |= BufferUsageFlags.IndirectBufferBit;
|
||||
}
|
||||
|
||||
var externalMemoryBuffer = new ExternalMemoryBufferCreateInfo
|
||||
{
|
||||
SType = StructureType.ExternalMemoryBufferCreateInfo,
|
||||
HandleTypes = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
|
||||
};
|
||||
|
||||
var bufferCreateInfo = new BufferCreateInfo
|
||||
{
|
||||
SType = StructureType.BufferCreateInfo,
|
||||
Size = (ulong)size,
|
||||
Usage = usage,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
PNext = &externalMemoryBuffer,
|
||||
};
|
||||
|
||||
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
||||
|
@ -18,6 +18,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Device _device;
|
||||
private readonly Queue _queue;
|
||||
private readonly object _queueLock;
|
||||
private readonly bool _concurrentFenceWaitUnsupported;
|
||||
private readonly CommandPool _pool;
|
||||
private readonly Thread _owner;
|
||||
|
||||
@ -30,11 +31,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public int SubmissionCount;
|
||||
public CommandBuffer CommandBuffer;
|
||||
public FenceHolder Fence;
|
||||
public SemaphoreHolder Semaphore;
|
||||
|
||||
public List<IAuto> Dependants;
|
||||
public List<MultiFenceHolder> Waitables;
|
||||
public HashSet<SemaphoreHolder> Dependencies;
|
||||
|
||||
public void Initialize(Vk api, Device device, CommandPool pool)
|
||||
{
|
||||
@ -50,7 +49,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
Dependants = new List<IAuto>();
|
||||
Waitables = new List<MultiFenceHolder>();
|
||||
Dependencies = new HashSet<SemaphoreHolder>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,12 +59,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private int _queuedCount;
|
||||
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;
|
||||
_device = device;
|
||||
_queue = queue;
|
||||
_queueLock = queueLock;
|
||||
_concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
|
||||
_owner = Thread.CurrentThread;
|
||||
|
||||
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)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[cbIndex];
|
||||
@ -345,19 +343,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
waitable.RemoveBufferUses(cbIndex);
|
||||
}
|
||||
|
||||
foreach (var dependency in entry.Dependencies)
|
||||
{
|
||||
dependency.Put();
|
||||
}
|
||||
|
||||
entry.Dependants.Clear();
|
||||
entry.Waitables.Clear();
|
||||
entry.Dependencies.Clear();
|
||||
entry.Fence?.Dispose();
|
||||
|
||||
if (refreshFence)
|
||||
{
|
||||
entry.Fence = new FenceHolder(_api, _device);
|
||||
entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -26,11 +26,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_pool.AddWaitable(CommandBufferIndex, waitable);
|
||||
}
|
||||
|
||||
public void AddDependency(CommandBufferScoped dependencyCbs)
|
||||
{
|
||||
_pool.AddDependency(CommandBufferIndex, dependencyCbs);
|
||||
}
|
||||
|
||||
public FenceHolder GetFence()
|
||||
{
|
||||
return _pool.GetFence(CommandBufferIndex);
|
||||
|
@ -73,7 +73,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private readonly VulkanRenderer _gd;
|
||||
private readonly Device _device;
|
||||
private readonly PipelineBase _pipeline;
|
||||
private ShaderCollection _program;
|
||||
|
||||
private readonly BufferRef[] _uniformBufferRefs;
|
||||
@ -125,11 +124,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly TextureView _dummyTexture;
|
||||
private readonly SamplerHolder _dummySampler;
|
||||
|
||||
public DescriptorSetUpdater(VulkanRenderer gd, Device device, PipelineBase pipeline)
|
||||
public DescriptorSetUpdater(VulkanRenderer gd, Device device)
|
||||
{
|
||||
_gd = gd;
|
||||
_device = device;
|
||||
_pipeline = pipeline;
|
||||
|
||||
// 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.
|
||||
@ -291,8 +289,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else
|
||||
{
|
||||
PipelineStageFlags stageFlags = _textureArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags();
|
||||
_textureArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||
ref var arrayRef = ref _textureArrayRefs[segment.Binding];
|
||||
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
||||
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,8 +310,40 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else
|
||||
{
|
||||
PipelineStageFlags stageFlags = _imageArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags();
|
||||
_imageArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||
ref var arrayRef = ref _imageArrayRefs[segment.Binding];
|
||||
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
||||
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < _program.BindingSegments.Length; setIndex++)
|
||||
{
|
||||
var bindingSegments = _program.BindingSegments[setIndex];
|
||||
|
||||
if (bindingSegments.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceBindingSegment segment = bindingSegments[0];
|
||||
|
||||
if (segment.IsArray)
|
||||
{
|
||||
if (segment.Type == ResourceType.Texture ||
|
||||
segment.Type == ResourceType.Sampler ||
|
||||
segment.Type == ResourceType.TextureAndSampler ||
|
||||
segment.Type == ResourceType.BufferTexture)
|
||||
{
|
||||
ref var arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
|
||||
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
||||
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||
}
|
||||
else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage)
|
||||
{
|
||||
ref var arrayRef = ref _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
|
||||
PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
|
||||
arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -651,7 +682,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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))
|
||||
@ -885,31 +923,84 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan<uint>.Empty);
|
||||
}
|
||||
|
||||
private unsafe void UpdateBuffers(
|
||||
CommandBufferScoped cbs,
|
||||
PipelineBindPoint pbp,
|
||||
int baseBinding,
|
||||
ReadOnlySpan<DescriptorBufferInfo> bufferInfo,
|
||||
DescriptorType type)
|
||||
private void UpdateAndBindTexturesWithoutTemplate(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
|
||||
{
|
||||
if (bufferInfo.Length == 0)
|
||||
int setIndex = PipelineBase.TextureSetIndex;
|
||||
var bindingSegments = program.BindingSegments[setIndex];
|
||||
|
||||
if (bindingSegments.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
|
||||
if (_updateDescriptorCacheCbIndex)
|
||||
{
|
||||
var writeDescriptorSet = new WriteDescriptorSet
|
||||
{
|
||||
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);
|
||||
_updateDescriptorCacheCbIndex = false;
|
||||
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
|
||||
}
|
||||
|
||||
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)]
|
||||
|
@ -59,14 +59,14 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var scalingResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.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()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
|
@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var resourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.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));
|
||||
|
||||
|
@ -81,20 +81,20 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var edgeResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.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()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
var neighbourResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
|
@ -10,12 +10,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Device _device;
|
||||
private Fence _fence;
|
||||
private int _referenceCount;
|
||||
private int _lock;
|
||||
private readonly bool _concurrentWaitUnsupported;
|
||||
private bool _disposed;
|
||||
|
||||
public unsafe FenceHolder(Vk api, Device device)
|
||||
public unsafe FenceHolder(Vk api, Device device, bool concurrentWaitUnsupported)
|
||||
{
|
||||
_api = api;
|
||||
_device = device;
|
||||
_concurrentWaitUnsupported = concurrentWaitUnsupported;
|
||||
|
||||
var fenceCreateInfo = new FenceCreateInfo
|
||||
{
|
||||
@ -47,6 +50,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||
|
||||
if (_concurrentWaitUnsupported)
|
||||
{
|
||||
AcquireLock();
|
||||
}
|
||||
|
||||
fence = _fence;
|
||||
return true;
|
||||
}
|
||||
@ -57,6 +65,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _fence;
|
||||
}
|
||||
|
||||
public void PutLock()
|
||||
{
|
||||
Put();
|
||||
|
||||
if (_concurrentWaitUnsupported)
|
||||
{
|
||||
ReleaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void Put()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
|
@ -286,10 +286,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_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,
|
||||
Device device,
|
||||
CommandBufferScoped cbs)
|
||||
|
@ -115,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var strideChangeResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.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[]
|
||||
{
|
||||
@ -125,7 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var colorCopyResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 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[]
|
||||
{
|
||||
@ -155,7 +155,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var convertD32S8ToD24S8ResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.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[]
|
||||
{
|
||||
@ -165,7 +165,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var convertIndexBufferResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.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[]
|
||||
{
|
||||
@ -175,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
var convertIndirectDataResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 0)
|
||||
.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();
|
||||
|
||||
_programConvertIndirectData = gd.CreateProgramWithMinimalLayout(new[]
|
||||
|
@ -2,11 +2,10 @@ using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class ImageArray : IImageArray
|
||||
class ImageArray : ResourceArray, IImageArray
|
||||
{
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
@ -25,19 +24,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private HashSet<TextureStorage> _storages;
|
||||
|
||||
private DescriptorSet[] _cachedDescriptorSets;
|
||||
|
||||
private int _cachedCommandBufferIndex;
|
||||
private int _cachedSubmissionCount;
|
||||
|
||||
private ShaderCollection _cachedDscProgram;
|
||||
private int _cachedDscSetIndex;
|
||||
private int _cachedDscIndex;
|
||||
|
||||
private readonly bool _isBuffer;
|
||||
|
||||
private int _bindCount;
|
||||
|
||||
public ImageArray(VulkanRenderer gd, int size, bool isBuffer)
|
||||
{
|
||||
_gd = gd;
|
||||
@ -104,12 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_cachedCommandBufferIndex = -1;
|
||||
_storages = null;
|
||||
_cachedDescriptorSets = null;
|
||||
|
||||
if (_bindCount != 0)
|
||||
{
|
||||
_gd.PipelineInternal.ForceImageDirty();
|
||||
}
|
||||
SetDirty(_gd);
|
||||
}
|
||||
|
||||
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
||||
@ -195,7 +181,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
int setIndex,
|
||||
TextureView dummyTexture)
|
||||
{
|
||||
if (_cachedDescriptorSets != null)
|
||||
if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets))
|
||||
{
|
||||
// We still need to ensure the current command buffer holds a reference to all used textures.
|
||||
|
||||
@ -208,12 +194,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GetBufferViews(cbs);
|
||||
}
|
||||
|
||||
return _cachedDescriptorSets;
|
||||
return sets;
|
||||
}
|
||||
|
||||
_cachedDscProgram?.ReleaseManualDescriptorSetCollection(_cachedDscSetIndex, _cachedDscIndex);
|
||||
var dsc = program.GetNewManualDescriptorSetCollection(cbs.CommandBufferIndex, setIndex, out _cachedDscIndex).Get(cbs);
|
||||
|
||||
DescriptorSetTemplate template = program.Templates[setIndex];
|
||||
|
||||
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
|
||||
@ -227,24 +210,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
tu.Push(GetBufferViews(cbs));
|
||||
}
|
||||
|
||||
var sets = dsc.GetSets();
|
||||
templateUpdater.Commit(_gd, device, sets[0]);
|
||||
_cachedDescriptorSets = sets;
|
||||
_cachedDscProgram = program;
|
||||
_cachedDscSetIndex = setIndex;
|
||||
|
||||
return sets;
|
||||
}
|
||||
|
||||
public void IncrementBindCount()
|
||||
{
|
||||
_bindCount++;
|
||||
}
|
||||
|
||||
public void DecrementBindCount()
|
||||
{
|
||||
int newBindCount = --_bindCount;
|
||||
Debug.Assert(newBindCount >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
@ -165,14 +166,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// <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)
|
||||
{
|
||||
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);
|
||||
Span<Fence> fences = stackalloc Fence[count];
|
||||
|
||||
int fenceCount = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
for (int i = 0; i < fences.Length; i++)
|
||||
{
|
||||
if (fenceHolders[i].TryGet(out Fence fence))
|
||||
{
|
||||
@ -194,18 +196,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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].Put();
|
||||
for (int i = 0; i < fenceCount; i++)
|
||||
{
|
||||
fenceHolders[i].PutLock();
|
||||
}
|
||||
}
|
||||
|
||||
return signaled;
|
||||
|
@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
protected FramebufferParams FramebufferParams;
|
||||
private Auto<DisposableFramebuffer> _framebuffer;
|
||||
private RenderPassHolder _rpHolder;
|
||||
private Auto<DisposableRenderPass> _renderPass;
|
||||
private RenderPassHolder _nullRenderPass;
|
||||
private int _writtenAttachmentCount;
|
||||
@ -85,8 +86,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private bool _tfActive;
|
||||
|
||||
private readonly PipelineColorBlendAttachmentState[] _storedBlend;
|
||||
|
||||
private ulong _drawCountSinceBarrier;
|
||||
public ulong DrawCount { 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();
|
||||
|
||||
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device, this);
|
||||
_descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
|
||||
_vertexBufferUpdater = new VertexBufferUpdater(gd);
|
||||
|
||||
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
|
||||
@ -135,48 +134,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public unsafe void Barrier()
|
||||
{
|
||||
if (_drawCountSinceBarrier != DrawCount)
|
||||
{
|
||||
_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);
|
||||
Gd.Barriers.QueueMemoryBarrier();
|
||||
}
|
||||
|
||||
public void ComputeBarrier()
|
||||
@ -203,6 +161,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void BeginTransformFeedback(PrimitiveTopology topology)
|
||||
{
|
||||
Gd.Barriers.EnableTfbBarriers(true);
|
||||
_tfEnabled = true;
|
||||
}
|
||||
|
||||
@ -249,7 +208,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CreateRenderPass();
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||
|
||||
BeginRenderPass();
|
||||
|
||||
@ -287,7 +246,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CreateRenderPass();
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
|
||||
Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
|
||||
|
||||
BeginRenderPass();
|
||||
|
||||
@ -299,24 +258,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public unsafe void CommandBufferBarrier()
|
||||
{
|
||||
MemoryBarrier memoryBarrier = new()
|
||||
{
|
||||
SType = StructureType.MemoryBarrier,
|
||||
SrcAccessMask = BufferHolder.DefaultAccessFlags,
|
||||
DstAccessMask = AccessFlags.IndirectCommandReadBit,
|
||||
};
|
||||
|
||||
Gd.Api.CmdPipelineBarrier(
|
||||
CommandBuffer,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.DrawIndirectBit,
|
||||
0,
|
||||
1,
|
||||
memoryBarrier,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null);
|
||||
Gd.Barriers.QueueCommandBufferBarrier();
|
||||
}
|
||||
|
||||
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
||||
@ -722,6 +664,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void EndTransformFeedback()
|
||||
{
|
||||
Gd.Barriers.EnableTfbBarriers(false);
|
||||
PauseTransformFeedbackInternal();
|
||||
_tfEnabled = false;
|
||||
}
|
||||
@ -1020,6 +963,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_newState.RasterizerDiscardEnable = discard;
|
||||
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)
|
||||
@ -1401,24 +1351,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public unsafe void TextureBarrier()
|
||||
{
|
||||
MemoryBarrier memoryBarrier = new()
|
||||
{
|
||||
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);
|
||||
Gd.Barriers.QueueTextureBarrier();
|
||||
}
|
||||
|
||||
public void TextureBarrierTiled()
|
||||
@ -1525,12 +1458,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
// Use the null framebuffer.
|
||||
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
|
||||
|
||||
_rpHolder = _nullRenderPass;
|
||||
_renderPass = _nullRenderPass.GetRenderPass();
|
||||
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -1701,6 +1637,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (RenderPassActive)
|
||||
{
|
||||
FramebufferParams.AddStoreOpUsage();
|
||||
|
||||
PauseTransformFeedbackInternal();
|
||||
Gd.Api.CmdEndRenderPass(CommandBuffer);
|
||||
SignalRenderPassEnd();
|
||||
|
@ -9,13 +9,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -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(
|
||||
0,
|
||||
0,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
SubpassAccessMask,
|
||||
SubpassAccessMask,
|
||||
stages,
|
||||
stages,
|
||||
access,
|
||||
access,
|
||||
0);
|
||||
}
|
||||
|
||||
public unsafe static SubpassDependency2 CreateSubpassDependency2()
|
||||
public unsafe static SubpassDependency2 CreateSubpassDependency2(VulkanRenderer gd)
|
||||
{
|
||||
var (access, stages) = BarrierBatch.GetSubpassAccessSuperset(gd);
|
||||
|
||||
return new SubpassDependency2(
|
||||
StructureType.SubpassDependency2,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
PipelineStageFlags.AllGraphicsBit,
|
||||
SubpassAccessMask,
|
||||
SubpassAccessMask,
|
||||
stages,
|
||||
stages,
|
||||
access,
|
||||
access,
|
||||
0);
|
||||
}
|
||||
|
||||
@ -180,9 +177,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
pipeline.LogicOpEnable = state.LogicOpEnable;
|
||||
pipeline.LogicOp = state.LogicOp.Convert();
|
||||
|
||||
pipeline.MinDepthBounds = 0f; // Not implemented.
|
||||
pipeline.MaxDepthBounds = 0f; // Not implemented.
|
||||
|
||||
pipeline.PatchControlPoints = state.PatchControlPoints;
|
||||
pipeline.PolygonMode = PolygonMode.Fill; // Not implemented.
|
||||
pipeline.PrimitiveRestartEnable = state.PrimitiveRestartEnable;
|
||||
@ -208,17 +202,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
pipeline.StencilFrontPassOp = state.StencilTest.FrontDpPass.Convert();
|
||||
pipeline.StencilFrontDepthFailOp = state.StencilTest.FrontDpFail.Convert();
|
||||
pipeline.StencilFrontCompareOp = state.StencilTest.FrontFunc.Convert();
|
||||
pipeline.StencilFrontCompareMask = 0;
|
||||
pipeline.StencilFrontWriteMask = 0;
|
||||
pipeline.StencilFrontReference = 0;
|
||||
|
||||
pipeline.StencilBackFailOp = state.StencilTest.BackSFail.Convert();
|
||||
pipeline.StencilBackPassOp = state.StencilTest.BackDpPass.Convert();
|
||||
pipeline.StencilBackDepthFailOp = state.StencilTest.BackDpFail.Convert();
|
||||
pipeline.StencilBackCompareOp = state.StencilTest.BackFunc.Convert();
|
||||
pipeline.StencilBackCompareMask = 0;
|
||||
pipeline.StencilBackWriteMask = 0;
|
||||
pipeline.StencilBackReference = 0;
|
||||
|
||||
pipeline.StencilTestEnable = state.StencilTest.TestEnable;
|
||||
|
||||
|
@ -47,10 +47,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return;
|
||||
}
|
||||
|
||||
if (componentMask != 0xf)
|
||||
if (componentMask != 0xf || Gd.IsQualcommProprietary)
|
||||
{
|
||||
// We can't use CmdClearAttachments if not writing all components,
|
||||
// because on Vulkan, the pipeline state does not affect clears.
|
||||
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||
var dstTexture = FramebufferParams.GetColorView(index);
|
||||
if (dstTexture == null)
|
||||
{
|
||||
@ -87,10 +88,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
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,
|
||||
// because on Vulkan, the pipeline state does not affect clears.
|
||||
// On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all.
|
||||
var dstTexture = FramebufferParams.GetDepthStencilView();
|
||||
if (dstTexture == null)
|
||||
{
|
||||
@ -255,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PreloadCbs = null;
|
||||
}
|
||||
|
||||
Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
|
||||
Gd.Barriers.Flush(Cbs, false, null, null);
|
||||
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
|
||||
Gd.RegisterFlush();
|
||||
|
||||
|
@ -3,6 +3,7 @@ using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
@ -15,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private readonly Device _device;
|
||||
|
||||
public DescriptorSetLayout[] DescriptorSetLayouts { get; }
|
||||
public bool[] DescriptorSetLayoutsUpdateAfterBind { get; }
|
||||
public PipelineLayout PipelineLayout { get; }
|
||||
|
||||
private readonly int[] _consumedDescriptorsPerSet;
|
||||
@ -31,20 +33,37 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private struct ManualDescriptorSetEntry
|
||||
{
|
||||
public Auto<DescriptorSetCollection> DescriptorSet;
|
||||
public int CbIndex;
|
||||
public int CbSubmissionCount;
|
||||
public uint CbRefMask;
|
||||
public bool InUse;
|
||||
|
||||
public ManualDescriptorSetEntry(Auto<DescriptorSetCollection> descriptorSet, int cbIndex, int cbSubmissionCount, bool inUse)
|
||||
public ManualDescriptorSetEntry(Auto<DescriptorSetCollection> descriptorSet, int cbIndex)
|
||||
{
|
||||
DescriptorSet = descriptorSet;
|
||||
CbIndex = cbIndex;
|
||||
CbSubmissionCount = cbSubmissionCount;
|
||||
InUse = inUse;
|
||||
CbRefMask = 1u << cbIndex;
|
||||
InUse = true;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct PendingManualDsConsumption
|
||||
{
|
||||
public FenceHolder Fence { get; }
|
||||
public int CommandBufferIndex { get; }
|
||||
public int SetIndex { get; }
|
||||
public int CacheIndex { get; }
|
||||
|
||||
public PendingManualDsConsumption(FenceHolder fence, int commandBufferIndex, int setIndex, int cacheIndex)
|
||||
{
|
||||
Fence = fence;
|
||||
CommandBufferIndex = commandBufferIndex;
|
||||
SetIndex = setIndex;
|
||||
CacheIndex = cacheIndex;
|
||||
fence.Get();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<ManualDescriptorSetEntry>[] _manualDsCache;
|
||||
private readonly Queue<PendingManualDsConsumption> _pendingManualDsConsumptions;
|
||||
private readonly Queue<int>[] _freeManualDsCacheEntries;
|
||||
|
||||
private readonly Dictionary<long, DescriptorSetTemplate> _pdTemplates;
|
||||
private readonly ResourceDescriptorCollection _pdDescriptors;
|
||||
@ -70,6 +89,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_dsCacheCursor = new int[setsCount];
|
||||
_manualDsCache = new List<ManualDescriptorSetEntry>[setsCount];
|
||||
_pendingManualDsConsumptions = new Queue<PendingManualDsConsumption>();
|
||||
_freeManualDsCacheEntries = new Queue<int>[setsCount];
|
||||
}
|
||||
|
||||
public PipelineLayoutCacheEntry(
|
||||
@ -78,7 +99,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
|
||||
bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
|
||||
{
|
||||
(DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
|
||||
ResourceLayouts layouts = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
|
||||
|
||||
DescriptorSetLayouts = layouts.DescriptorSetLayouts;
|
||||
DescriptorSetLayoutsUpdateAfterBind = layouts.DescriptorSetLayoutsUpdateAfterBind;
|
||||
PipelineLayout = layouts.PipelineLayout;
|
||||
|
||||
_consumedDescriptorsPerSet = new int[setDescriptors.Count];
|
||||
_poolSizes = new DescriptorPoolSize[setDescriptors.Count][];
|
||||
@ -133,7 +158,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_poolSizes[setIndex],
|
||||
setIndex,
|
||||
_consumedDescriptorsPerSet[setIndex],
|
||||
false);
|
||||
DescriptorSetLayoutsUpdateAfterBind[setIndex]);
|
||||
|
||||
list.Add(dsc);
|
||||
isNew = true;
|
||||
@ -144,49 +169,99 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return list[index];
|
||||
}
|
||||
|
||||
public Auto<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 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))
|
||||
{
|
||||
entry.InUse = true;
|
||||
entry.CbIndex = commandBufferIndex;
|
||||
entry.CbSubmissionCount = submissionCount;
|
||||
Debug.Assert(!entry.InUse && entry.CbRefMask == 0);
|
||||
|
||||
cacheIndex = index;
|
||||
entry.InUse = true;
|
||||
entry.CbRefMask = 1u << cbs.CommandBufferIndex;
|
||||
cacheIndex = freeIndex;
|
||||
|
||||
return entry.DescriptorSet;
|
||||
}
|
||||
_pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, freeIndex));
|
||||
|
||||
return entry.DescriptorSet;
|
||||
}
|
||||
|
||||
// Otherwise create a new descriptor set, and add to our pending queue for command buffer consumption tracking.
|
||||
var dsc = _descriptorSetManager.AllocateDescriptorSet(
|
||||
_gd.Api,
|
||||
DescriptorSetLayouts[setIndex],
|
||||
_poolSizes[setIndex],
|
||||
setIndex,
|
||||
_consumedDescriptorsPerSet[setIndex],
|
||||
false);
|
||||
DescriptorSetLayoutsUpdateAfterBind[setIndex]);
|
||||
|
||||
cacheIndex = list.Count;
|
||||
list.Add(new ManualDescriptorSetEntry(dsc, commandBufferIndex, submissionCount, inUse: true));
|
||||
list.Add(new ManualDescriptorSetEntry(dsc, cbs.CommandBufferIndex));
|
||||
_pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex));
|
||||
|
||||
return dsc;
|
||||
}
|
||||
|
||||
public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex)
|
||||
{
|
||||
FreeCompletedManualDescriptorSets();
|
||||
|
||||
var list = _manualDsCache[setIndex];
|
||||
var span = CollectionsMarshal.AsSpan(list);
|
||||
ref var entry = ref span[cacheIndex];
|
||||
|
||||
uint cbMask = 1u << cbs.CommandBufferIndex;
|
||||
|
||||
if ((entry.CbRefMask & cbMask) == 0)
|
||||
{
|
||||
entry.CbRefMask |= cbMask;
|
||||
|
||||
_pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex));
|
||||
}
|
||||
}
|
||||
|
||||
private void FreeCompletedManualDescriptorSets()
|
||||
{
|
||||
FenceHolder signalledFence = null;
|
||||
while (_pendingManualDsConsumptions.TryPeek(out var pds) && (pds.Fence == signalledFence || pds.Fence.IsSignaled()))
|
||||
{
|
||||
signalledFence = pds.Fence; // Already checked - don't need to do it again.
|
||||
var dequeued = _pendingManualDsConsumptions.Dequeue();
|
||||
Debug.Assert(dequeued.Fence == pds.Fence);
|
||||
pds.Fence.Put();
|
||||
|
||||
var span = CollectionsMarshal.AsSpan(_manualDsCache[dequeued.SetIndex]);
|
||||
ref var entry = ref span[dequeued.CacheIndex];
|
||||
entry.CbRefMask &= ~(1u << dequeued.CommandBufferIndex);
|
||||
|
||||
if (!entry.InUse && entry.CbRefMask == 0)
|
||||
{
|
||||
// If not in use by any array, and not bound to any command buffer, the descriptor set can be re-used immediately.
|
||||
(_freeManualDsCacheEntries[dequeued.SetIndex] ??= new()).Enqueue(dequeued.CacheIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex)
|
||||
{
|
||||
var list = _manualDsCache[setIndex];
|
||||
var span = CollectionsMarshal.AsSpan(list);
|
||||
|
||||
span[cacheIndex].InUse = false;
|
||||
|
||||
if (span[cacheIndex].CbRefMask == 0)
|
||||
{
|
||||
// This is no longer in use by any array, so if not bound to any command buffer, the descriptor set can be re-used immediately.
|
||||
(_freeManualDsCacheEntries[setIndex] ??= new()).Enqueue(cacheIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, ResourceDescriptorCollection setDescriptor, uint multiplier)
|
||||
@ -291,6 +366,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null);
|
||||
}
|
||||
|
||||
while (_pendingManualDsConsumptions.TryDequeue(out var pds))
|
||||
{
|
||||
pds.Fence.Put();
|
||||
}
|
||||
|
||||
_descriptorSetManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,23 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
record struct ResourceLayouts(DescriptorSetLayout[] DescriptorSetLayouts, bool[] DescriptorSetLayoutsUpdateAfterBind, PipelineLayout PipelineLayout);
|
||||
|
||||
static class PipelineLayoutFactory
|
||||
{
|
||||
public static unsafe (DescriptorSetLayout[], PipelineLayout) Create(
|
||||
public static unsafe ResourceLayouts Create(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
|
||||
bool usePushDescriptors)
|
||||
{
|
||||
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
|
||||
bool[] updateAfterBindFlags = new bool[setDescriptors.Count];
|
||||
|
||||
bool isMoltenVk = gd.IsMoltenVk;
|
||||
|
||||
@ -32,10 +37,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count];
|
||||
|
||||
bool hasArray = false;
|
||||
|
||||
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
|
||||
{
|
||||
ResourceDescriptor descriptor = rdc.Descriptors[descIndex];
|
||||
|
||||
ResourceStages stages = descriptor.Stages;
|
||||
|
||||
if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk)
|
||||
@ -52,16 +58,37 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DescriptorCount = (uint)descriptor.Count,
|
||||
StageFlags = stages.Convert(),
|
||||
};
|
||||
|
||||
if (descriptor.Count > 1)
|
||||
{
|
||||
hasArray = true;
|
||||
}
|
||||
}
|
||||
|
||||
fixed (DescriptorSetLayoutBinding* pLayoutBindings = layoutBindings)
|
||||
{
|
||||
DescriptorSetLayoutCreateFlags flags = DescriptorSetLayoutCreateFlags.None;
|
||||
|
||||
if (usePushDescriptors && setIndex == 0)
|
||||
{
|
||||
flags = DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr;
|
||||
}
|
||||
|
||||
if (gd.Vendor == Vendor.Intel && hasArray)
|
||||
{
|
||||
// Some vendors (like Intel) have low per-stage limits.
|
||||
// We must set the flag if we exceed those limits.
|
||||
flags |= DescriptorSetLayoutCreateFlags.UpdateAfterBindPoolBit;
|
||||
|
||||
updateAfterBindFlags[setIndex] = true;
|
||||
}
|
||||
|
||||
var descriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo
|
||||
{
|
||||
SType = StructureType.DescriptorSetLayoutCreateInfo,
|
||||
PBindings = pLayoutBindings,
|
||||
BindingCount = (uint)layoutBindings.Length,
|
||||
Flags = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None,
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
|
||||
@ -82,7 +109,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
|
||||
}
|
||||
|
||||
return (layouts, layout);
|
||||
return new ResourceLayouts(layouts, updateAfterBindFlags, layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,244 +71,232 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
set => Internal.Id4 = (Internal.Id4 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
}
|
||||
|
||||
public float MinDepthBounds
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id5 >> 0) & 0xFFFFFFFF));
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFF00000000) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 0);
|
||||
}
|
||||
|
||||
public float MaxDepthBounds
|
||||
{
|
||||
readonly get => BitConverter.Int32BitsToSingle((int)((Internal.Id5 >> 32) & 0xFFFFFFFF));
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFF) | ((ulong)(uint)BitConverter.SingleToInt32Bits(value) << 32);
|
||||
}
|
||||
|
||||
public PolygonMode PolygonMode
|
||||
{
|
||||
readonly get => (PolygonMode)((Internal.Id6 >> 0) & 0x3FFFFFFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
|
||||
readonly get => (PolygonMode)((Internal.Id5 >> 0) & 0x3FFFFFFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFFFC0000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint StagesCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id6 >> 30) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
|
||||
readonly get => (byte)((Internal.Id5 >> 30) & 0xFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFFFFFC03FFFFFFF) | ((ulong)value << 30);
|
||||
}
|
||||
|
||||
public uint VertexAttributeDescriptionsCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id6 >> 38) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
|
||||
readonly get => (byte)((Internal.Id5 >> 38) & 0xFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFFFC03FFFFFFFFF) | ((ulong)value << 38);
|
||||
}
|
||||
|
||||
public uint VertexBindingDescriptionsCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id6 >> 46) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
|
||||
readonly get => (byte)((Internal.Id5 >> 46) & 0xFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xFFC03FFFFFFFFFFF) | ((ulong)value << 46);
|
||||
}
|
||||
|
||||
public uint ViewportsCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id6 >> 54) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
|
||||
readonly get => (byte)((Internal.Id5 >> 54) & 0xFF);
|
||||
set => Internal.Id5 = (Internal.Id5 & 0xC03FFFFFFFFFFFFF) | ((ulong)value << 54);
|
||||
}
|
||||
|
||||
public uint ScissorsCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id7 >> 0) & 0xFF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
|
||||
readonly get => (byte)((Internal.Id6 >> 0) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFFFF00) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint ColorBlendAttachmentStateCount
|
||||
{
|
||||
readonly get => (byte)((Internal.Id7 >> 8) & 0xFF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
|
||||
readonly get => (byte)((Internal.Id6 >> 8) & 0xFF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFFF00FF) | ((ulong)value << 8);
|
||||
}
|
||||
|
||||
public PrimitiveTopology Topology
|
||||
{
|
||||
readonly get => (PrimitiveTopology)((Internal.Id7 >> 16) & 0xF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
|
||||
readonly get => (PrimitiveTopology)((Internal.Id6 >> 16) & 0xF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFFF0FFFF) | ((ulong)value << 16);
|
||||
}
|
||||
|
||||
public LogicOp LogicOp
|
||||
{
|
||||
readonly get => (LogicOp)((Internal.Id7 >> 20) & 0xF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
|
||||
readonly get => (LogicOp)((Internal.Id6 >> 20) & 0xF);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFFF0FFFFF) | ((ulong)value << 20);
|
||||
}
|
||||
|
||||
public CompareOp DepthCompareOp
|
||||
{
|
||||
readonly get => (CompareOp)((Internal.Id7 >> 24) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
|
||||
readonly get => (CompareOp)((Internal.Id6 >> 24) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFF8FFFFFF) | ((ulong)value << 24);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontFailOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id7 >> 27) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 27) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFFC7FFFFFF) | ((ulong)value << 27);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontPassOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id7 >> 30) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 30) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFFE3FFFFFFF) | ((ulong)value << 30);
|
||||
}
|
||||
|
||||
public StencilOp StencilFrontDepthFailOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id7 >> 33) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 33) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFFF1FFFFFFFF) | ((ulong)value << 33);
|
||||
}
|
||||
|
||||
public CompareOp StencilFrontCompareOp
|
||||
{
|
||||
readonly get => (CompareOp)((Internal.Id7 >> 36) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
|
||||
readonly get => (CompareOp)((Internal.Id6 >> 36) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFF8FFFFFFFFF) | ((ulong)value << 36);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackFailOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id7 >> 39) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 39) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFFC7FFFFFFFFF) | ((ulong)value << 39);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackPassOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id7 >> 42) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 42) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFFE3FFFFFFFFFF) | ((ulong)value << 42);
|
||||
}
|
||||
|
||||
public StencilOp StencilBackDepthFailOp
|
||||
{
|
||||
readonly get => (StencilOp)((Internal.Id7 >> 45) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
|
||||
readonly get => (StencilOp)((Internal.Id6 >> 45) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFFF1FFFFFFFFFFF) | ((ulong)value << 45);
|
||||
}
|
||||
|
||||
public CompareOp StencilBackCompareOp
|
||||
{
|
||||
readonly get => (CompareOp)((Internal.Id7 >> 48) & 0x7);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
|
||||
readonly get => (CompareOp)((Internal.Id6 >> 48) & 0x7);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFF8FFFFFFFFFFFF) | ((ulong)value << 48);
|
||||
}
|
||||
|
||||
public CullModeFlags CullMode
|
||||
{
|
||||
readonly get => (CullModeFlags)((Internal.Id7 >> 51) & 0x3);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
|
||||
readonly get => (CullModeFlags)((Internal.Id6 >> 51) & 0x3);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFE7FFFFFFFFFFFF) | ((ulong)value << 51);
|
||||
}
|
||||
|
||||
public bool PrimitiveRestartEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 53) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
|
||||
readonly get => ((Internal.Id6 >> 53) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFDFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 53);
|
||||
}
|
||||
|
||||
public bool DepthClampEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 54) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
|
||||
readonly get => ((Internal.Id6 >> 54) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFFBFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 54);
|
||||
}
|
||||
|
||||
public bool RasterizerDiscardEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 55) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
|
||||
readonly get => ((Internal.Id6 >> 55) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFF7FFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 55);
|
||||
}
|
||||
|
||||
public FrontFace FrontFace
|
||||
{
|
||||
readonly get => (FrontFace)((Internal.Id7 >> 56) & 0x1);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
|
||||
readonly get => (FrontFace)((Internal.Id6 >> 56) & 0x1);
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFEFFFFFFFFFFFFFF) | ((ulong)value << 56);
|
||||
}
|
||||
|
||||
public bool DepthBiasEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 57) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
|
||||
readonly get => ((Internal.Id6 >> 57) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFDFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 57);
|
||||
}
|
||||
|
||||
public bool DepthTestEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 58) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
|
||||
readonly get => ((Internal.Id6 >> 58) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xFBFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 58);
|
||||
}
|
||||
|
||||
public bool DepthWriteEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 59) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
|
||||
readonly get => ((Internal.Id6 >> 59) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xF7FFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 59);
|
||||
}
|
||||
|
||||
public bool DepthBoundsTestEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 60) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
|
||||
readonly get => ((Internal.Id6 >> 60) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xEFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 60);
|
||||
}
|
||||
|
||||
public bool StencilTestEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 61) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
|
||||
readonly get => ((Internal.Id6 >> 61) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xDFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 61);
|
||||
}
|
||||
|
||||
public bool LogicOpEnable
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 62) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
|
||||
readonly get => ((Internal.Id6 >> 62) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0xBFFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 62);
|
||||
}
|
||||
|
||||
public bool HasDepthStencil
|
||||
{
|
||||
readonly get => ((Internal.Id7 >> 63) & 0x1) != 0UL;
|
||||
set => Internal.Id7 = (Internal.Id7 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
|
||||
readonly get => ((Internal.Id6 >> 63) & 0x1) != 0UL;
|
||||
set => Internal.Id6 = (Internal.Id6 & 0x7FFFFFFFFFFFFFFF) | ((value ? 1UL : 0UL) << 63);
|
||||
}
|
||||
|
||||
public uint PatchControlPoints
|
||||
{
|
||||
readonly get => (uint)((Internal.Id8 >> 0) & 0xFFFFFFFF);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
readonly get => (uint)((Internal.Id7 >> 0) & 0xFFFFFFFF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF00000000) | ((ulong)value << 0);
|
||||
}
|
||||
|
||||
public uint SamplesCount
|
||||
{
|
||||
readonly get => (uint)((Internal.Id8 >> 32) & 0xFFFFFFFF);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
readonly get => (uint)((Internal.Id7 >> 32) & 0xFFFFFFFF);
|
||||
set => Internal.Id7 = (Internal.Id7 & 0xFFFFFFFF) | ((ulong)value << 32);
|
||||
}
|
||||
|
||||
public bool AlphaToCoverageEnable
|
||||
{
|
||||
readonly get => ((Internal.Id9 >> 0) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
|
||||
readonly get => ((Internal.Id8 >> 0) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFE) | ((value ? 1UL : 0UL) << 0);
|
||||
}
|
||||
|
||||
public bool AlphaToOneEnable
|
||||
{
|
||||
readonly get => ((Internal.Id9 >> 1) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
|
||||
readonly get => ((Internal.Id8 >> 1) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFD) | ((value ? 1UL : 0UL) << 1);
|
||||
}
|
||||
|
||||
public bool AdvancedBlendSrcPreMultiplied
|
||||
{
|
||||
readonly get => ((Internal.Id9 >> 2) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
|
||||
readonly get => ((Internal.Id8 >> 2) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFFB) | ((value ? 1UL : 0UL) << 2);
|
||||
}
|
||||
|
||||
public bool AdvancedBlendDstPreMultiplied
|
||||
{
|
||||
readonly get => ((Internal.Id9 >> 3) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
|
||||
readonly get => ((Internal.Id8 >> 3) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFF7) | ((value ? 1UL : 0UL) << 3);
|
||||
}
|
||||
|
||||
public BlendOverlapEXT AdvancedBlendOverlap
|
||||
{
|
||||
readonly get => (BlendOverlapEXT)((Internal.Id9 >> 4) & 0x3);
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
|
||||
readonly get => (BlendOverlapEXT)((Internal.Id8 >> 4) & 0x3);
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFCF) | ((ulong)value << 4);
|
||||
}
|
||||
|
||||
public bool DepthMode
|
||||
{
|
||||
readonly get => ((Internal.Id9 >> 6) & 0x1) != 0UL;
|
||||
set => Internal.Id9 = (Internal.Id9 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
||||
readonly get => ((Internal.Id8 >> 6) & 0x1) != 0UL;
|
||||
set => Internal.Id8 = (Internal.Id8 & 0xFFFFFFFFFFFFFFBF) | ((value ? 1UL : 0UL) << 6);
|
||||
}
|
||||
|
||||
public bool HasTessellationControlShader;
|
||||
@ -408,8 +396,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions = &Internal.VertexAttributeDescriptions[0])
|
||||
fixed (VertexInputAttributeDescription* pVertexAttributeDescriptions2 = &_vertexAttributeDescriptions2[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])
|
||||
{
|
||||
var vertexInputState = new PipelineVertexInputStateCreateInfo
|
||||
@ -472,18 +458,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
CullMode = CullMode,
|
||||
FrontFace = FrontFace,
|
||||
DepthBiasEnable = DepthBiasEnable,
|
||||
DepthBiasClamp = DepthBiasClamp,
|
||||
DepthBiasConstantFactor = DepthBiasConstantFactor,
|
||||
DepthBiasSlopeFactor = DepthBiasSlopeFactor,
|
||||
};
|
||||
|
||||
var viewportState = new PipelineViewportStateCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineViewportStateCreateInfo,
|
||||
ViewportCount = ViewportsCount,
|
||||
PViewports = pViewports,
|
||||
ScissorCount = ScissorsCount,
|
||||
PScissors = pScissors,
|
||||
};
|
||||
|
||||
if (gd.Capabilities.SupportsDepthClipControl)
|
||||
@ -511,19 +492,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
StencilFrontFailOp,
|
||||
StencilFrontPassOp,
|
||||
StencilFrontDepthFailOp,
|
||||
StencilFrontCompareOp,
|
||||
StencilFrontCompareMask,
|
||||
StencilFrontWriteMask,
|
||||
StencilFrontReference);
|
||||
StencilFrontCompareOp);
|
||||
|
||||
var stencilBack = new StencilOpState(
|
||||
StencilBackFailOp,
|
||||
StencilBackPassOp,
|
||||
StencilBackDepthFailOp,
|
||||
StencilBackCompareOp,
|
||||
StencilBackCompareMask,
|
||||
StencilBackWriteMask,
|
||||
StencilBackReference);
|
||||
StencilBackCompareOp);
|
||||
|
||||
var depthStencilState = new PipelineDepthStencilStateCreateInfo
|
||||
{
|
||||
@ -531,12 +506,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
DepthTestEnable = DepthTestEnable,
|
||||
DepthWriteEnable = DepthWriteEnable,
|
||||
DepthCompareOp = DepthCompareOp,
|
||||
DepthBoundsTestEnable = DepthBoundsTestEnable,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = StencilTestEnable,
|
||||
Front = stencilFront,
|
||||
Back = stencilBack,
|
||||
MinDepthBounds = MinDepthBounds,
|
||||
MaxDepthBounds = MaxDepthBounds,
|
||||
};
|
||||
|
||||
uint blendEnables = 0;
|
||||
@ -591,22 +564,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
bool supportsExtDynamicState = gd.Capabilities.SupportsExtendedDynamicState;
|
||||
int dynamicStatesCount = supportsExtDynamicState ? 9 : 8;
|
||||
int dynamicStatesCount = supportsExtDynamicState ? 8 : 7;
|
||||
|
||||
DynamicState* dynamicStates = stackalloc DynamicState[dynamicStatesCount];
|
||||
|
||||
dynamicStates[0] = DynamicState.Viewport;
|
||||
dynamicStates[1] = DynamicState.Scissor;
|
||||
dynamicStates[2] = DynamicState.DepthBias;
|
||||
dynamicStates[3] = DynamicState.DepthBounds;
|
||||
dynamicStates[4] = DynamicState.StencilCompareMask;
|
||||
dynamicStates[5] = DynamicState.StencilWriteMask;
|
||||
dynamicStates[6] = DynamicState.StencilReference;
|
||||
dynamicStates[7] = DynamicState.BlendConstants;
|
||||
dynamicStates[3] = DynamicState.StencilCompareMask;
|
||||
dynamicStates[4] = DynamicState.StencilWriteMask;
|
||||
dynamicStates[5] = DynamicState.StencilReference;
|
||||
dynamicStates[6] = DynamicState.BlendConstants;
|
||||
|
||||
if (supportsExtDynamicState)
|
||||
{
|
||||
dynamicStates[8] = DynamicState.VertexInputBindingStrideExt;
|
||||
dynamicStates[7] = DynamicState.VertexInputBindingStrideExt;
|
||||
}
|
||||
|
||||
var pipelineDynamicStateCreateInfo = new PipelineDynamicStateCreateInfo
|
||||
@ -632,7 +604,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PDynamicState = &pipelineDynamicStateCreateInfo,
|
||||
Layout = PipelineLayout,
|
||||
RenderPass = renderPass,
|
||||
BasePipelineIndex = -1,
|
||||
};
|
||||
|
||||
Result result = gd.Api.CreateGraphicsPipelines(device, cache, 1, &pipelineCreateInfo, null, &pipelineHandle);
|
||||
|
@ -17,20 +17,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public ulong Id4;
|
||||
public ulong Id5;
|
||||
public ulong Id6;
|
||||
|
||||
public ulong Id7;
|
||||
|
||||
public ulong Id8;
|
||||
public ulong Id9;
|
||||
|
||||
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF);
|
||||
private readonly uint VertexBindingDescriptionsCount => (byte)((Id6 >> 46) & 0xFF);
|
||||
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id7 >> 8) & 0xFF);
|
||||
private readonly bool HasDepthStencil => ((Id7 >> 63) & 0x1) != 0UL;
|
||||
private readonly uint VertexAttributeDescriptionsCount => (byte)((Id5 >> 38) & 0xFF);
|
||||
private readonly uint VertexBindingDescriptionsCount => (byte)((Id5 >> 46) & 0xFF);
|
||||
private readonly uint ColorBlendAttachmentStateCount => (byte)((Id6 >> 8) & 0xFF);
|
||||
private readonly bool HasDepthStencil => ((Id6 >> 63) & 0x1) != 0UL;
|
||||
|
||||
public Array32<VertexInputAttributeDescription> VertexAttributeDescriptions;
|
||||
public Array33<VertexInputBindingDescription> VertexBindingDescriptions;
|
||||
public Array16<Viewport> Viewports;
|
||||
public Array16<Rect2D> Scissors;
|
||||
public Array8<PipelineColorBlendAttachmentState> ColorBlendAttachmentState;
|
||||
public Array9<Format> AttachmentFormats;
|
||||
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)) ||
|
||||
!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;
|
||||
}
|
||||
@ -88,8 +85,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Id5 * 23 ^
|
||||
Id6 * 23 ^
|
||||
Id7 * 23 ^
|
||||
Id8 * 23 ^
|
||||
Id9 * 23;
|
||||
Id8 * 23;
|
||||
|
||||
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
|
||||
{
|
||||
|
@ -1,5 +1,7 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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 Auto<DisposableRenderPass> _renderPass;
|
||||
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
|
||||
private readonly RenderPassCacheKey _key;
|
||||
private readonly List<ForcedFence> _forcedFences;
|
||||
|
||||
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
|
||||
{
|
||||
@ -105,7 +110,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency();
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency(gd);
|
||||
|
||||
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
||||
{
|
||||
@ -138,6 +143,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_textures = textures;
|
||||
_key = key;
|
||||
|
||||
_forcedFences = new List<ForcedFence>();
|
||||
}
|
||||
|
||||
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
|
||||
@ -159,6 +166,37 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _renderPass;
|
||||
}
|
||||
|
||||
public void AddForcedFence(TextureStorage storage, PipelineStageFlags stageFlags)
|
||||
{
|
||||
if (!_forcedFences.Any(fence => fence.Texture == storage))
|
||||
{
|
||||
_forcedFences.Add(new ForcedFence(storage, stageFlags));
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertForcedFences(CommandBufferScoped cbs)
|
||||
{
|
||||
if (_forcedFences.Count > 0)
|
||||
{
|
||||
_forcedFences.RemoveAll((entry) =>
|
||||
{
|
||||
if (entry.Texture.Disposed)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
entry.Texture.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, entry.StageFlags);
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsAttachment(TextureStorage storage)
|
||||
{
|
||||
return _textures.Any(view => view.Storage == storage);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Dispose all framebuffers.
|
||||
|
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
|
||||
{
|
||||
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
};
|
||||
|
||||
_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;
|
||||
}
|
||||
|
@ -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 HasTessellationControlShader => (Stages & (1u << 3)) != 0;
|
||||
|
||||
public bool UpdateTexturesWithoutTemplate { get; }
|
||||
|
||||
public uint Stages { get; }
|
||||
|
||||
public PipelineStageFlags IncoherentBufferWriteStages { get; }
|
||||
public PipelineStageFlags IncoherentTextureWriteStages { get; }
|
||||
|
||||
public ResourceBindingSegment[][] ClearSegments { get; }
|
||||
public ResourceBindingSegment[][] BindingSegments { get; }
|
||||
public DescriptorSetTemplate[] Templates { get; }
|
||||
@ -127,8 +132,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Stages = stages;
|
||||
|
||||
ClearSegments = BuildClearSegments(sets);
|
||||
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
|
||||
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages, out bool usesBufferTextures);
|
||||
Templates = BuildTemplates(usePushDescriptors);
|
||||
(IncoherentBufferWriteStages, IncoherentTextureWriteStages) = BuildIncoherentStages(resourceLayout.SetUsages);
|
||||
|
||||
// Updating buffer texture bindings using template updates crashes the Adreno driver on Windows.
|
||||
UpdateTexturesWithoutTemplate = gd.IsQualcommProprietary && usesBufferTextures;
|
||||
|
||||
_compileTask = Task.CompletedTask;
|
||||
_firstBackgroundUse = false;
|
||||
@ -280,8 +289,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
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][];
|
||||
|
||||
for (int setIndex = 0; setIndex < setUsages.Count; setIndex++)
|
||||
@ -295,6 +306,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
ResourceUsage usage = setUsages[setIndex].Usages[index];
|
||||
|
||||
if (usage.Type == ResourceType.BufferTexture)
|
||||
{
|
||||
usesBufferTextures = true;
|
||||
}
|
||||
|
||||
if (currentUsage.Binding + currentCount != usage.Binding ||
|
||||
currentUsage.Type != usage.Type ||
|
||||
currentUsage.Stages != usage.Stages ||
|
||||
@ -365,6 +381,73 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return templates;
|
||||
}
|
||||
|
||||
private PipelineStageFlags GetPipelineStages(ResourceStages stages)
|
||||
{
|
||||
PipelineStageFlags result = 0;
|
||||
|
||||
if ((stages & ResourceStages.Compute) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.ComputeShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Vertex) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.VertexShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Fragment) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.FragmentShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.Geometry) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.GeometryShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.TessellationControl) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.TessellationControlShaderBit;
|
||||
}
|
||||
|
||||
if ((stages & ResourceStages.TessellationEvaluation) != 0)
|
||||
{
|
||||
result |= PipelineStageFlags.TessellationEvaluationShaderBit;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private (PipelineStageFlags Buffer, PipelineStageFlags Texture) BuildIncoherentStages(ReadOnlyCollection<ResourceUsageCollection> setUsages)
|
||||
{
|
||||
PipelineStageFlags buffer = PipelineStageFlags.None;
|
||||
PipelineStageFlags texture = PipelineStageFlags.None;
|
||||
|
||||
foreach (var set in setUsages)
|
||||
{
|
||||
foreach (var range in set.Usages)
|
||||
{
|
||||
if (range.Write)
|
||||
{
|
||||
PipelineStageFlags stages = GetPipelineStages(range.Stages);
|
||||
|
||||
switch (range.Type)
|
||||
{
|
||||
case ResourceType.Image:
|
||||
texture |= stages;
|
||||
break;
|
||||
case ResourceType.StorageBuffer:
|
||||
case ResourceType.BufferImage:
|
||||
buffer |= stages;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (buffer, texture);
|
||||
}
|
||||
|
||||
private async Task BackgroundCompilation()
|
||||
{
|
||||
await Task.WhenAll(_shaders.Select(shader => shader.CompileTask));
|
||||
@ -604,9 +687,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
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)
|
||||
|
@ -2,11 +2,10 @@ using Ryujinx.Graphics.GAL;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
class TextureArray : ITextureArray
|
||||
class TextureArray : ResourceArray, ITextureArray
|
||||
{
|
||||
private readonly VulkanRenderer _gd;
|
||||
|
||||
@ -25,19 +24,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private HashSet<TextureStorage> _storages;
|
||||
|
||||
private DescriptorSet[] _cachedDescriptorSets;
|
||||
|
||||
private int _cachedCommandBufferIndex;
|
||||
private int _cachedSubmissionCount;
|
||||
|
||||
private ShaderCollection _cachedDscProgram;
|
||||
private int _cachedDscSetIndex;
|
||||
private int _cachedDscIndex;
|
||||
|
||||
private readonly bool _isBuffer;
|
||||
|
||||
private int _bindCount;
|
||||
|
||||
public TextureArray(VulkanRenderer gd, int size, bool isBuffer)
|
||||
{
|
||||
_gd = gd;
|
||||
@ -113,12 +104,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_cachedCommandBufferIndex = -1;
|
||||
_storages = null;
|
||||
_cachedDescriptorSets = null;
|
||||
|
||||
if (_bindCount != 0)
|
||||
{
|
||||
_gd.PipelineInternal.ForceTextureDirty();
|
||||
}
|
||||
SetDirty(_gd);
|
||||
}
|
||||
|
||||
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
|
||||
@ -211,7 +197,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
TextureView dummyTexture,
|
||||
SamplerHolder dummySampler)
|
||||
{
|
||||
if (_cachedDescriptorSets != null)
|
||||
if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets))
|
||||
{
|
||||
// We still need to ensure the current command buffer holds a reference to all used textures.
|
||||
|
||||
@ -224,12 +210,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GetBufferViews(cbs);
|
||||
}
|
||||
|
||||
return _cachedDescriptorSets;
|
||||
return sets;
|
||||
}
|
||||
|
||||
_cachedDscProgram?.ReleaseManualDescriptorSetCollection(_cachedDscSetIndex, _cachedDscIndex);
|
||||
var dsc = program.GetNewManualDescriptorSetCollection(cbs.CommandBufferIndex, setIndex, out _cachedDscIndex).Get(cbs);
|
||||
|
||||
DescriptorSetTemplate template = program.Templates[setIndex];
|
||||
|
||||
DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
|
||||
@ -243,24 +226,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
tu.Push(GetBufferViews(cbs));
|
||||
}
|
||||
|
||||
var sets = dsc.GetSets();
|
||||
templateUpdater.Commit(_gd, device, sets[0]);
|
||||
_cachedDescriptorSets = sets;
|
||||
_cachedDscProgram = program;
|
||||
_cachedDscSetIndex = setIndex;
|
||||
|
||||
return sets;
|
||||
}
|
||||
|
||||
public void IncrementBindCount()
|
||||
{
|
||||
_bindCount++;
|
||||
}
|
||||
|
||||
public void DecrementBindCount()
|
||||
{
|
||||
int newBindCount = --_bindCount;
|
||||
Debug.Assert(newBindCount >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ImageLayout.General,
|
||||
ImageLayout.General);
|
||||
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency2();
|
||||
var subpassDependency = PipelineConverter.CreateSubpassDependency2(gd);
|
||||
|
||||
fixed (AttachmentDescription2* pAttachmentDescs = attachmentDescs)
|
||||
{
|
||||
|
@ -38,6 +38,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public TextureCreateInfo Info => _info;
|
||||
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
private readonly Image _image;
|
||||
private readonly Auto<DisposableImage> _imageAuto;
|
||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
||||
@ -80,7 +82,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
|
||||
|
||||
var flags = ImageCreateFlags.CreateMutableFormatBit;
|
||||
var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
|
||||
|
||||
// This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
|
||||
bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
|
||||
@ -433,6 +435,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
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)
|
||||
{
|
||||
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
|
||||
@ -458,7 +471,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
_gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
|
||||
_gd.Barriers.QueueBarrier(barrier, this, srcStageFlags, dstStageFlags);
|
||||
|
||||
_lastReadStage = PipelineStageFlags.None;
|
||||
_lastReadAccess = AccessFlags.None;
|
||||
@ -491,7 +504,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_info.GetLayers(),
|
||||
_info.Levels);
|
||||
|
||||
_gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
|
||||
_gd.Barriers.QueueBarrier(barrier, this, _lastModificationStage, dstStageFlags);
|
||||
|
||||
_lastModificationAccess = AccessFlags.None;
|
||||
}
|
||||
@ -514,6 +527,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
|
||||
if (_aliasedStorages != null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var usage = new ImageViewUsageCreateInfo
|
||||
var imageViewUsage = new ImageViewUsageCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageViewUsageCreateInfo,
|
||||
Usage = usageFlags,
|
||||
@ -114,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Format = format,
|
||||
Components = cm,
|
||||
SubresourceRange = sr,
|
||||
PNext = &usage,
|
||||
PNext = &imageViewUsage,
|
||||
};
|
||||
|
||||
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||
@ -123,7 +123,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit;
|
||||
|
||||
if (info.Format.IsImageCompatible())
|
||||
if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample()))
|
||||
{
|
||||
shaderUsage |= ImageUsageFlags.StorageBit;
|
||||
}
|
||||
@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
else
|
||||
{
|
||||
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
|
||||
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, 1, (uint)firstLayer, (uint)info.Depth);
|
||||
|
||||
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage);
|
||||
}
|
||||
@ -993,7 +993,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||
public (RenderPassHolder rpHolder, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||
VulkanRenderer gd,
|
||||
Device device,
|
||||
CommandBufferScoped cbs,
|
||||
@ -1006,7 +1006,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
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)
|
||||
|
@ -69,27 +69,32 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
DriverId.AmdProprietary => "AMD",
|
||||
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.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.MesaV3DV => "V3DV",
|
||||
DriverId.MesaVenus => "Venus",
|
||||
DriverId.Moltenvk => "MoltenVK",
|
||||
DriverId.NvidiaProprietary => "NVIDIA",
|
||||
DriverId.QualcommProprietary => "Qualcomm",
|
||||
DriverId.MesaPanvk => "PanVK",
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
"VK_EXT_depth_clip_control",
|
||||
"VK_KHR_portability_subset", // As per spec, we should enable this if present.
|
||||
"VK_EXT_4444_formats",
|
||||
"VK_KHR_8bit_storage",
|
||||
"VK_KHR_maintenance2",
|
||||
};
|
||||
|
||||
private static readonly string[] _requiredExtensions = {
|
||||
@ -355,6 +357,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
features2.PNext = &supportedFeaturesDepthClipControl;
|
||||
}
|
||||
|
||||
PhysicalDeviceVulkan12Features supportedPhysicalDeviceVulkan12Features = new()
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||
PNext = features2.PNext,
|
||||
};
|
||||
|
||||
features2.PNext = &supportedPhysicalDeviceVulkan12Features;
|
||||
|
||||
api.GetPhysicalDeviceFeatures2(physicalDevice.PhysicalDevice, &features2);
|
||||
|
||||
var supportedFeatures = features2.Features;
|
||||
@ -382,6 +392,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
TessellationShader = supportedFeatures.TessellationShader,
|
||||
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
|
||||
RobustBufferAccess = useRobustBufferAccess,
|
||||
SampleRateShading = supportedFeatures.SampleRateShading,
|
||||
};
|
||||
|
||||
void* pExtendedFeatures = null;
|
||||
@ -451,9 +462,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
SType = StructureType.PhysicalDeviceVulkan12Features,
|
||||
PNext = pExtendedFeatures,
|
||||
DescriptorIndexing = physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"),
|
||||
DrawIndirectCount = physicalDevice.IsDeviceExtensionPresent(KhrDrawIndirectCount.ExtensionName),
|
||||
UniformBufferStandardLayout = physicalDevice.IsDeviceExtensionPresent("VK_KHR_uniform_buffer_standard_layout"),
|
||||
DescriptorIndexing = supportedPhysicalDeviceVulkan12Features.DescriptorIndexing,
|
||||
DrawIndirectCount = supportedPhysicalDeviceVulkan12Features.DrawIndirectCount,
|
||||
UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout,
|
||||
UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess,
|
||||
StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess,
|
||||
};
|
||||
|
||||
pExtendedFeatures = &featuresVk12;
|
||||
|
@ -87,9 +87,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal bool IsAmdGcn { get; private set; }
|
||||
internal bool IsNvidiaPreTuring { get; private set; }
|
||||
internal bool IsIntelArc { get; private set; }
|
||||
internal bool IsQualcommProprietary { get; private set; }
|
||||
internal bool IsMoltenVk { get; private set; }
|
||||
internal bool IsTBDR { get; private set; }
|
||||
internal bool IsSharedMemory { get; private set; }
|
||||
|
||||
public string GpuVendor { get; private set; }
|
||||
public string GpuDriver { get; private set; }
|
||||
public string GpuRenderer { get; private set; }
|
||||
@ -344,7 +346,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
IsNvidiaPreTuring = gpuNumber < 2000;
|
||||
}
|
||||
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX"))
|
||||
else if (GpuRenderer.Contains("TITAN") && !GpuRenderer.Contains("RTX"))
|
||||
{
|
||||
IsNvidiaPreTuring = true;
|
||||
}
|
||||
@ -354,6 +356,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
|
||||
}
|
||||
|
||||
IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary;
|
||||
|
||||
ulong minResourceAlignment = Math.Max(
|
||||
Math.Max(
|
||||
properties.Limits.MinStorageBufferOffsetAlignment,
|
||||
@ -411,7 +415,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Api.TryGetDeviceExtension(_instance.Instance, _device, out ExtExternalMemoryHost hostMemoryApi);
|
||||
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();
|
||||
|
||||
@ -688,7 +692,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
GpuVendor,
|
||||
memoryType: memoryType,
|
||||
hasFrontFacingBug: IsIntelWindows,
|
||||
hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
|
||||
hasVectorIndexingBug: IsQualcommProprietary,
|
||||
needsFragmentOutputSpecialization: IsMoltenVk,
|
||||
reduceShaderPrecision: IsMoltenVk,
|
||||
supportsAstcCompression: features2.Features.TextureCompressionAstcLdr && supportsAstcFormats,
|
||||
@ -935,6 +939,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ScreenCaptured?.Invoke(this, bitmap);
|
||||
}
|
||||
|
||||
public bool SupportsRenderPassBarrier(PipelineStageFlags flags)
|
||||
{
|
||||
return !(IsMoltenVk || IsQualcommProprietary);
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
if (!_initialized)
|
||||
|
@ -623,7 +623,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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)
|
||||
|
@ -7,6 +7,7 @@ using Ryujinx.Common.SystemInterop;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.UI;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
@ -322,7 +323,35 @@ namespace Ryujinx
|
||||
|
||||
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))
|
||||
|
@ -37,7 +37,9 @@ using Ryujinx.UI.Windows;
|
||||
using Silk.NET.Vulkan;
|
||||
using SPB.Graphics.Vulkan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
@ -60,7 +62,6 @@ namespace Ryujinx.UI
|
||||
|
||||
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
||||
|
||||
private readonly ApplicationLibrary _applicationLibrary;
|
||||
private readonly GtkHostUIHandler _uiHandler;
|
||||
private readonly AutoResetEvent _deviceExitStatus;
|
||||
private readonly ListStore _tableStore;
|
||||
@ -69,11 +70,12 @@ namespace Ryujinx.UI
|
||||
private bool _gameLoaded;
|
||||
private bool _ending;
|
||||
|
||||
private string _currentEmulatedGamePath = null;
|
||||
private ApplicationData _currentApplicationData = null;
|
||||
|
||||
private string _lastScannedAmiiboId = "";
|
||||
private bool _lastScannedAmiiboShowAll = false;
|
||||
|
||||
public readonly ApplicationLibrary ApplicationLibrary;
|
||||
public RendererWidgetBase RendererWidget;
|
||||
public InputManager InputManager;
|
||||
|
||||
@ -180,8 +182,12 @@ namespace Ryujinx.UI
|
||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, CommandLineState.Profile);
|
||||
_userChannelPersistence = new UserChannelPersistence();
|
||||
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
// Instantiate GUI objects.
|
||||
_applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
|
||||
ApplicationLibrary = new ApplicationLibrary(_virtualFileSystem, checkLevel);
|
||||
_uiHandler = new GtkHostUIHandler(this);
|
||||
_deviceExitStatus = new AutoResetEvent(false);
|
||||
|
||||
@ -190,8 +196,8 @@ namespace Ryujinx.UI
|
||||
FocusInEvent += MainWindow_FocusInEvent;
|
||||
FocusOutEvent += MainWindow_FocusOutEvent;
|
||||
|
||||
_applicationLibrary.ApplicationAdded += Application_Added;
|
||||
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||
ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||
|
||||
_fileMenu.StateChanged += FileMenu_StateChanged;
|
||||
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
||||
@ -732,7 +738,7 @@ namespace Ryujinx.UI
|
||||
|
||||
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;
|
||||
})
|
||||
@ -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();
|
||||
|
||||
@ -857,7 +863,7 @@ namespace Ryujinx.UI
|
||||
case ".xci":
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
||||
|
||||
return _emulationContext.LoadXci(path);
|
||||
return _emulationContext.LoadXci(path, applicationId);
|
||||
case ".nca":
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
||||
|
||||
@ -866,7 +872,7 @@ namespace Ryujinx.UI
|
||||
case ".pfs0":
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
||||
|
||||
return _emulationContext.LoadNsp(path);
|
||||
return _emulationContext.LoadNsp(path, applicationId);
|
||||
default:
|
||||
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
||||
try
|
||||
@ -887,7 +893,7 @@ namespace Ryujinx.UI
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RunApplication(string path, bool startFullscreen = false)
|
||||
public void RunApplication(ApplicationData application, bool startFullscreen = false)
|
||||
{
|
||||
if (_gameLoaded)
|
||||
{
|
||||
@ -909,14 +915,14 @@ namespace Ryujinx.UI
|
||||
|
||||
bool isFirmwareTitle = false;
|
||||
|
||||
if (path.StartsWith("@SystemContent"))
|
||||
if (application.Path.StartsWith("@SystemContent"))
|
||||
{
|
||||
path = VirtualFileSystem.SwitchPathToSystemPath(path);
|
||||
application.Path = VirtualFileSystem.SwitchPathToSystemPath(application.Path);
|
||||
|
||||
isFirmwareTitle = true;
|
||||
}
|
||||
|
||||
if (!LoadApplication(path, isFirmwareTitle))
|
||||
if (!LoadApplication(application.Path, application.Id, isFirmwareTitle))
|
||||
{
|
||||
_emulationContext.Dispose();
|
||||
SwitchToGameTable();
|
||||
@ -926,7 +932,7 @@ namespace Ryujinx.UI
|
||||
|
||||
SetupProgressUIHandlers();
|
||||
|
||||
_currentEmulatedGamePath = path;
|
||||
_currentApplicationData = application;
|
||||
|
||||
_deviceExitStatus.Reset();
|
||||
|
||||
@ -1165,7 +1171,7 @@ namespace Ryujinx.UI
|
||||
_tableStore.AppendValues(
|
||||
args.AppData.Favorite,
|
||||
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.Version,
|
||||
args.AppData.TimePlayedString,
|
||||
@ -1253,9 +1259,22 @@ namespace Ryujinx.UI
|
||||
{
|
||||
_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)
|
||||
@ -1313,13 +1332,22 @@ namespace Ryujinx.UI
|
||||
return;
|
||||
}
|
||||
|
||||
string titleFilePath = _tableStore.GetValue(treeIter, 9).ToString();
|
||||
string titleName = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[0];
|
||||
string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
|
||||
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),
|
||||
};
|
||||
|
||||
BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
|
||||
|
||||
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData);
|
||||
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, application);
|
||||
}
|
||||
|
||||
private void Load_Application_File(object sender, EventArgs args)
|
||||
@ -1341,7 +1369,15 @@ namespace Ryujinx.UI
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
RunApplication(contentPath);
|
||||
ApplicationData applicationData = new()
|
||||
{
|
||||
Name = "miiEdit",
|
||||
Id = 0x0100000000001009ul,
|
||||
Path = contentPath,
|
||||
};
|
||||
|
||||
RunApplication(applicationData);
|
||||
}
|
||||
|
||||
private void Open_Ryu_Folder(object sender, EventArgs args)
|
||||
@ -1646,13 +1695,13 @@ namespace Ryujinx.UI
|
||||
{
|
||||
_userChannelPersistence.ShouldRestart = false;
|
||||
|
||||
RunApplication(_currentEmulatedGamePath);
|
||||
RunApplication(_currentApplicationData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, clear state.
|
||||
_userChannelPersistence = new UserChannelPersistence();
|
||||
_currentEmulatedGamePath = null;
|
||||
_currentApplicationData = null;
|
||||
_actionMenu.Sensitive = false;
|
||||
_firmwareInstallFile.Sensitive = true;
|
||||
_firmwareInstallDirectory.Sensitive = true;
|
||||
@ -1714,7 +1763,7 @@ namespace Ryujinx.UI
|
||||
_emulationContext.Processes.ActiveApplication.ProgramId,
|
||||
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
|
||||
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
|
||||
_currentEmulatedGamePath);
|
||||
_currentApplicationData.Path);
|
||||
|
||||
window.Destroyed += CheatWindow_Destroyed;
|
||||
window.Show();
|
||||
|
@ -16,6 +16,8 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
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.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
@ -23,7 +25,6 @@ using Ryujinx.UI.Windows;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
@ -36,17 +37,13 @@ namespace Ryujinx.UI.Widgets
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly AccountManager _accountManager;
|
||||
private readonly HorizonClient _horizonClient;
|
||||
private readonly BlitStruct<ApplicationControlProperty> _controlData;
|
||||
|
||||
private readonly string _titleFilePath;
|
||||
private readonly string _titleName;
|
||||
private readonly string _titleIdText;
|
||||
private readonly ulong _titleId;
|
||||
private readonly ApplicationData _applicationData;
|
||||
|
||||
private MessageDialog _dialog;
|
||||
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;
|
||||
|
||||
@ -55,23 +52,22 @@ namespace Ryujinx.UI.Widgets
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_accountManager = accountManager;
|
||||
_horizonClient = horizonClient;
|
||||
_titleFilePath = titleFilePath;
|
||||
_titleName = titleName;
|
||||
_titleIdText = titleId;
|
||||
_controlData = controlData;
|
||||
_applicationData = applicationData;
|
||||
|
||||
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");
|
||||
|
||||
return;
|
||||
_openSaveUserDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.UserAccountSaveDataSize > 0;
|
||||
_openSaveDeviceDirMenuItem.Sensitive = _applicationData.ControlHolder.Value.DeviceSaveDataSize > 0;
|
||||
_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;
|
||||
_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();
|
||||
string fileExt = System.IO.Path.GetExtension(_applicationData.Path).ToLower();
|
||||
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
|
||||
|
||||
_extractRomFsMenuItem.Sensitive = hasNca;
|
||||
@ -137,7 +133,7 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
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;
|
||||
}
|
||||
@ -190,7 +186,7 @@ namespace Ryujinx.UI.Widgets
|
||||
{
|
||||
Title = "Ryujinx - NCA Section Extractor",
|
||||
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,
|
||||
};
|
||||
|
||||
@ -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 patchNca = null;
|
||||
|
||||
if ((System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".nsp") ||
|
||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".pfs0") ||
|
||||
(System.IO.Path.GetExtension(_titleFilePath).ToLower() == ".xci"))
|
||||
if ((System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".nsp") ||
|
||||
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".pfs0") ||
|
||||
(System.IO.Path.GetExtension(_applicationData.Path).ToLower() == ".xci"))
|
||||
{
|
||||
IFileSystem pfs;
|
||||
|
||||
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;
|
||||
}
|
||||
IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(_applicationData.Path, _virtualFileSystem);
|
||||
|
||||
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());
|
||||
}
|
||||
@ -266,7 +249,11 @@ namespace Ryujinx.UI.Widgets
|
||||
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)
|
||||
{
|
||||
@ -460,44 +447,44 @@ namespace Ryujinx.UI.Widgets
|
||||
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 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
|
||||
new DlcWindow(_virtualFileSystem, _applicationData.IdString, _applicationData).Show();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
string modsBasePath = ModLoader.GetModsBasePath();
|
||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _titleIdText);
|
||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, _applicationData.IdString);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
@ -505,7 +492,7 @@ namespace Ryujinx.UI.Widgets
|
||||
private void OpenTitleSdModDir_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _titleIdText);
|
||||
string titleModsPath = ModLoader.GetApplicationDir(sdModsBasePath, _applicationData.IdString);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
@ -527,7 +514,7 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
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 backupPath = System.IO.Path.Combine(ptcDir, "1");
|
||||
@ -544,7 +531,7 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
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))
|
||||
{
|
||||
@ -556,10 +543,10 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
private void PurgePtcCache_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "0"));
|
||||
DirectoryInfo backupDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleIdText, "cache", "cpu", "1"));
|
||||
DirectoryInfo mainDir = new(System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationData.IdString, "cache", "cpu", "0"));
|
||||
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();
|
||||
|
||||
@ -593,9 +580,9 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
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<FileInfo> newCacheFiles = new();
|
||||
@ -637,8 +624,11 @@ namespace Ryujinx.UI.Widgets
|
||||
|
||||
private void CreateShortcut_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
|
||||
ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? 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
|
||||
|
||||
byte[] input = new byte[stream.Length - stream.Position];
|
||||
stream.Read(input, 0, input.Length);
|
||||
stream.ReadExactly(input, 0, input.Length);
|
||||
|
||||
long inputOffset = 0;
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
using Gtk;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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"))
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
: IntegrityCheckLevel.None;
|
||||
|
||||
_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 titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId.ToString("X16"));
|
||||
|
@ -2,17 +2,21 @@ using Gtk;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Widgets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.UI.Windows
|
||||
@ -20,7 +24,7 @@ namespace Ryujinx.UI.Windows
|
||||
public class DlcWindow : Window
|
||||
{
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly string _titleId;
|
||||
private readonly string _applicationId;
|
||||
private readonly string _dlcJsonPath;
|
||||
private readonly List<DownloadableContentContainer> _dlcContainerList;
|
||||
|
||||
@ -32,16 +36,16 @@ namespace Ryujinx.UI.Windows
|
||||
[GUI] TreeSelection _dlcTreeSelection;
|
||||
#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);
|
||||
|
||||
_titleId = titleId;
|
||||
_applicationId = applicationId;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _titleId, "dlc.json");
|
||||
_baseTitleInfoLabel.Text = $"DLC Available for {titleName} [{titleId.ToUpper()}]";
|
||||
_dlcJsonPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, _applicationId, "dlc.json");
|
||||
_baseTitleInfoLabel.Text = $"DLC Available for {applicationData.Name} [{applicationId.ToUpper()}]";
|
||||
|
||||
try
|
||||
{
|
||||
@ -72,7 +76,7 @@ namespace Ryujinx.UI.Windows
|
||||
};
|
||||
|
||||
_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);
|
||||
|
||||
foreach (DownloadableContentContainer dlcContainer in _dlcContainerList)
|
||||
@ -86,18 +90,18 @@ namespace Ryujinx.UI.Windows
|
||||
bool areAllContentPacksEnabled = dlcContainer.DownloadableContentNcaList.TrueForAll((nca) => nca.Enabled);
|
||||
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();
|
||||
pfs.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(pfs);
|
||||
if (partitionFileSystem == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (DownloadableContentNca dlcNca in dlcContainer.DownloadableContentNcaList)
|
||||
{
|
||||
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);
|
||||
|
||||
if (nca != null)
|
||||
@ -112,6 +116,9 @@ namespace Ryujinx.UI.Windows
|
||||
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)
|
||||
@ -128,6 +135,52 @@ namespace Ryujinx.UI.Windows
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!File.Exists(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!");
|
||||
}
|
||||
AddDlc(containerPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,17 @@ using Gtk;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.UI.App.Common;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Widgets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -24,7 +27,7 @@ namespace Ryujinx.UI.Windows
|
||||
{
|
||||
private readonly MainWindow _parent;
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly string _titleId;
|
||||
private readonly ApplicationData _applicationData;
|
||||
private readonly string _updateJsonPath;
|
||||
|
||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
||||
@ -38,17 +41,17 @@ namespace Ryujinx.UI.Windows
|
||||
[GUI] RadioButton _noUpdateRadioButton;
|
||||
#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;
|
||||
|
||||
builder.Autoconnect(this);
|
||||
|
||||
_titleId = titleId;
|
||||
_applicationData = applicationData;
|
||||
_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>();
|
||||
|
||||
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)
|
||||
{
|
||||
@ -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();
|
||||
nsp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? 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();
|
||||
|
||||
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;
|
||||
radioLabel = "Bundled: " + radioLabel;
|
||||
}
|
||||
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!");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog($"{exception.Message}. Errored File: {path}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.HOS.Services.Ssl;
|
||||
using Ryujinx.HLE.HOS.Services.Time;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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)
|
||||
{
|
||||
// TODO: Check Aoc version.
|
||||
@ -232,11 +198,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
if (!mergedToContainer)
|
||||
{
|
||||
using FileStream fileStream = File.OpenRead(containerPath);
|
||||
using PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
using var pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(containerPath, _virtualFileSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
@ -1546,8 +1546,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
#pragma warning disable CA1822 // Mark member as static
|
||||
public Result SetProcessMemoryPermission(
|
||||
int handle,
|
||||
[PointerSized] ulong src,
|
||||
[PointerSized] ulong size,
|
||||
ulong src,
|
||||
ulong size,
|
||||
KMemoryPermission permission)
|
||||
{
|
||||
if (!PageAligned(src))
|
||||
|
@ -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();
|
||||
|
@ -474,9 +474,9 @@ namespace Ryujinx.HLE.HOS.Services
|
||||
{
|
||||
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);
|
||||
|
||||
|
@ -85,9 +85,9 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
|
||||
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);
|
||||
|
||||
|
@ -3,7 +3,6 @@ using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.SurfaceFlinger.Types;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@ -13,7 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
sealed class Parcel : IDisposable
|
||||
{
|
||||
private readonly IMemoryOwner<byte> _rawDataOwner;
|
||||
private readonly MemoryOwner<byte> _rawDataOwner;
|
||||
|
||||
private Span<byte> Raw => _rawDataOwner.Memory.Span;
|
||||
|
||||
@ -30,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
|
||||
public Parcel(ReadOnlySpan<byte> data)
|
||||
{
|
||||
_rawDataOwner = ByteMemoryPool.RentCopy(data);
|
||||
_rawDataOwner = MemoryOwner<byte>.RentCopy(data);
|
||||
|
||||
_payloadPosition = 0;
|
||||
_objectPosition = 0;
|
||||
@ -40,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||
{
|
||||
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.ObjectsSize = objectsSize;
|
||||
|
@ -3,7 +3,6 @@ using LibHac.FsSystem;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
@ -7,16 +7,25 @@ using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using LibHac.Tools.Ncm;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ApplicationId = LibHac.Ncm.ApplicationId;
|
||||
using ContentType = LibHac.Ncm.ContentType;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
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)
|
||||
{
|
||||
// Extract RomFs and ExeFs from NCA.
|
||||
@ -47,7 +56,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
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.
|
||||
(_, 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;
|
||||
}
|
||||
|
||||
public static ulong GetProgramIdBase(this Nca nca)
|
||||
{
|
||||
return nca.Header.TitleId & ~0x1FFFUL;
|
||||
}
|
||||
|
||||
public static int GetProgramIndex(this Nca nca)
|
||||
{
|
||||
return (int)(nca.Header.TitleId & 0xF);
|
||||
@ -96,6 +110,11 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
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)
|
||||
{
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
@ -108,6 +127,43 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
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)
|
||||
{
|
||||
IFileSystem exeFs = null;
|
||||
@ -172,5 +228,31 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
|
||||
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.Keys;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using LibHac.Tools.Ncm;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using ContentType = LibHac.Ncm.ContentType;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
{
|
||||
public static class PartitionFileSystemExtensions
|
||||
{
|
||||
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 TFormat : IPartitionFileSystemFormat
|
||||
where THeader : unmanaged, IPartitionFileSystemHeader
|
||||
@ -35,31 +67,22 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
|
||||
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.
|
||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||
if (applicationId == 0)
|
||||
{
|
||||
Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath);
|
||||
|
||||
if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index)
|
||||
foreach ((ulong _, ContentMetaData content) in applications)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nca.IsPatch())
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else if (nca.IsProgram())
|
||||
{
|
||||
mainNca = nca;
|
||||
}
|
||||
else if (nca.IsControl())
|
||||
{
|
||||
controlNca = nca;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
@ -79,54 +102,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
return (false, ProcessResult.Failed);
|
||||
}
|
||||
|
||||
// Load Update NCAs.
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(Nca updatePatchNca, Nca updateControlNca) = mainNca.GetUpdateData(device.FileSystem, device.System.FsIntegrityCheckLevel, device.Configuration.UserChannelPersistence.Index, out string _);
|
||||
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
@ -138,10 +114,8 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
controlNca = updateControlNca;
|
||||
}
|
||||
|
||||
// Load contained DownloadableContents.
|
||||
// TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here.
|
||||
device.Configuration.ContentManager.ClearAocData();
|
||||
device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel);
|
||||
|
||||
// Load DownloadableContents.
|
||||
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)
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -168,18 +145,18 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
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);
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
public bool LoadXci(string path)
|
||||
public bool LoadXci(string path, ulong applicationId)
|
||||
{
|
||||
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
|
||||
Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage());
|
||||
@ -44,7 +44,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
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)
|
||||
{
|
||||
@ -66,13 +66,13 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool LoadNsp(string path)
|
||||
public bool LoadNsp(string path, ulong applicationId)
|
||||
{
|
||||
FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
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)
|
||||
{
|
||||
|
@ -43,15 +43,14 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ulong currentProgramId = nca.Header.TitleId;
|
||||
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
|
||||
ulong currentMainProgramId = nca.GetProgramIdBase();
|
||||
|
||||
if (applicationId == 0 && currentMainProgramId != 0)
|
||||
{
|
||||
@ -68,7 +67,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
break;
|
||||
}
|
||||
|
||||
hasIndex[(int)(currentProgramId & 0xF)] = true;
|
||||
hasIndex[nca.GetProgramIndex()] = true;
|
||||
}
|
||||
|
||||
if (programCount == 0)
|
||||
|
@ -73,9 +73,9 @@ namespace Ryujinx.HLE
|
||||
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)
|
||||
@ -83,9 +83,9 @@ namespace Ryujinx.HLE
|
||||
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)
|
||||
|
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)]
|
||||
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)
|
||||
{
|
||||
|
@ -9,9 +9,11 @@ using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.UI.App.Common
|
||||
{
|
||||
@ -19,10 +21,10 @@ namespace Ryujinx.UI.App.Common
|
||||
{
|
||||
public bool Favorite { get; set; }
|
||||
public byte[] Icon { get; set; }
|
||||
public string TitleName { get; set; }
|
||||
public string TitleId { get; set; }
|
||||
public string Developer { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Name { get; set; } = "Unknown";
|
||||
public ulong Id { get; set; }
|
||||
public string Developer { get; set; } = "Unknown";
|
||||
public string Version { get; set; } = "0";
|
||||
public TimeSpan TimePlayed { get; set; }
|
||||
public DateTime? LastPlayed { get; set; }
|
||||
public string FileExtension { get; set; }
|
||||
@ -36,7 +38,11 @@ namespace Ryujinx.UI.App.Common
|
||||
|
||||
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);
|
||||
|
||||
@ -105,7 +111,7 @@ namespace Ryujinx.UI.App.Common
|
||||
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)
|
||||
{
|
||||
|
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