diff --git a/Directory.Packages.props b/Directory.Packages.props
index d04e237e0..6919a2485 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -11,7 +11,7 @@
-
+
@@ -20,9 +20,9 @@
-
+
-
+
@@ -49,4 +49,4 @@
-
+
\ No newline at end of file
diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
index 12ebabddd..89b1e9e6b 100644
--- a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
+++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
@@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
long originalPosition = _stream.Position;
_stream.Seek(0, SeekOrigin.Begin);
- _stream.Read(code, 0, code.Length);
+ _stream.ReadExactly(code, 0, code.Length);
_stream.Seek(originalPosition, SeekOrigin.Begin);
RelocInfo relocInfo;
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
index f156e0886..16feeb914 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
@@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
- int selectedReg = GetHighestValueIndex(freePositions);
+ // If this is a copy destination variable, we prefer the register used for the copy source.
+ // If the register is available, then the copy can be eliminated later as both source
+ // and destination will use the same register.
+ int selectedReg;
+
+ if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
+ {
+ selectedReg = preferredReg;
+ }
+ else
+ {
+ selectedReg = GetHighestValueIndex(freePositions);
+ }
+
int selectedNextUse = freePositions[selectedReg];
// Intervals starts and ends at odd positions, unless they span an entire
@@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
- private static int GetHighestValueIndex(Span span)
+ private static int GetHighestValueIndex(ReadOnlySpan span)
{
int highest = int.MinValue;
@@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
// The "visited" state is stored in the MSB of the local's value.
const ulong VisitedMask = 1ul << 63;
- bool IsVisited(Operand local)
+ static bool IsVisited(Operand local)
{
return (local.GetValueUnsafe() & VisitedMask) != 0;
}
- void SetVisited(Operand local)
+ static void SetVisited(Operand local)
{
local.GetValueUnsafe() |= VisitedMask;
}
@@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
dest.NumberLocal(_intervals.Count);
- _intervals.Add(new LiveInterval(dest));
+ LiveInterval interval = new LiveInterval(dest);
+ _intervals.Add(interval);
SetVisited(dest);
+
+ // If this is a copy (or copy-like operation), set the copy source interval as well.
+ // This is used for register preferencing later on, which allows the copy to be eliminated
+ // in some cases.
+ if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
+ {
+ Operand source = node.GetSource(0);
+
+ if (source.Kind == OperandKind.LocalVariable &&
+ source.GetLocalNumber() > 0 &&
+ (node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
+ {
+ interval.SetCopySource(_intervals[source.GetLocalNumber()]);
+ }
+ }
}
}
}
diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
index 333d3951b..cfe1bc7ca 100644
--- a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
+++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
@@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public LiveRange CurrRange;
public LiveInterval Parent;
+ public LiveInterval CopySource;
public UseList Uses;
public LiveIntervalList Children;
@@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private ref LiveRange CurrRange => ref _data->CurrRange;
private ref LiveRange PrevRange => ref _data->PrevRange;
private ref LiveInterval Parent => ref _data->Parent;
+ private ref LiveInterval CopySource => ref _data->CopySource;
private ref UseList Uses => ref _data->Uses;
private ref LiveIntervalList Children => ref _data->Children;
@@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
Register = register;
}
+ public void SetCopySource(LiveInterval copySource)
+ {
+ CopySource = copySource;
+ }
+
+ public bool TryGetCopySourceRegister(out int copySourceRegIndex)
+ {
+ if (CopySource._data != null)
+ {
+ copySourceRegIndex = CopySource.Register.Index;
+
+ return true;
+ }
+
+ copySourceRegIndex = 0;
+
+ return false;
+ }
+
public void Reset()
{
PrevRange = default;
diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs
index 55bf07248..96f4de049 100644
--- a/src/ARMeilleure/CodeGen/X86/Assembler.cs
+++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs
@@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
Span buffer = new byte[jump.JumpPosition - _stream.Position];
- _stream.Read(buffer);
+ _stream.ReadExactly(buffer);
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
codeStream.Write(buffer);
diff --git a/src/ARMeilleure/Translation/ControlFlowGraph.cs b/src/ARMeilleure/Translation/ControlFlowGraph.cs
index 3ead49c93..45b092ec5 100644
--- a/src/ARMeilleure/Translation/ControlFlowGraph.cs
+++ b/src/ARMeilleure/Translation/ControlFlowGraph.cs
@@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
private int[] _postOrderMap;
public int LocalsCount { get; private set; }
- public BasicBlock Entry { get; }
+ public BasicBlock Entry { get; private set; }
public IntrusiveList Blocks { get; }
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
public int[] PostOrderMap => _postOrderMap;
@@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
return result;
}
+ public void UpdateEntry(BasicBlock newEntry)
+ {
+ newEntry.AddSuccessor(Entry);
+
+ Entry = newEntry;
+ Blocks.AddFirst(newEntry);
+ Update();
+ }
+
public void Update()
{
RemoveUnreachableBlocks(Blocks);
diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs
index f56bdce1c..c2eed7a55 100644
--- a/src/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/src/ARMeilleure/Translation/PTC/Ptc.cs
@@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
- private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project.
+ private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
diff --git a/src/ARMeilleure/Translation/RegisterUsage.cs b/src/ARMeilleure/Translation/RegisterUsage.cs
index c8c250626..472b0f67b 100644
--- a/src/ARMeilleure/Translation/RegisterUsage.cs
+++ b/src/ARMeilleure/Translation/RegisterUsage.cs
@@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
{
+ if (cfg.Entry.Predecessors.Count != 0)
+ {
+ // We expect the entry block to have no predecessors.
+ // This is required because we have a implicit context load at the start of the function,
+ // but if there is a jump to the start of the function, the context load would trash the modified values.
+ // Here we insert a new entry block that will jump to the existing entry block.
+ BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
+
+ cfg.UpdateEntry(newEntry);
+ }
+
// Compute local register inputs and outputs used inside blocks.
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
@@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
// The only block without any predecessor should be the entry block.
// It always needs a context load as it is the first block to run.
- if (block.Predecessors.Count == 0 || hasContextLoad)
+ if (block == cfg.Entry || hasContextLoad)
{
long vecMask = globalInputs[block.Index].VecMask;
long intMask = globalInputs[block.Index].IntMask;
diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
index 62fe5025d..4eb75a578 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
@@ -89,9 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
return;
}
- using IMemoryOwner samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame);
+ using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * _bytesPerFrame);
- Span samples = samplesOwner.Memory.Span;
+ Span samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
index 4011a1214..e9cc6a8e1 100644
--- a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
+++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs
@@ -122,9 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
int channelCount = areas.Length;
- using IMemoryOwner samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame);
+ using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * bytesPerFrame);
- Span samples = samplesOwner.Memory.Span;
+ Span samples = samplesOwner.Span;
_ringBuffer.Read(samples, 0, samples.Length);
diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
index b95e5bed1..7aefe8865 100644
--- a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
+++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Backends.Common
private readonly object _lock = new();
- private IMemoryOwner _bufferOwner;
+ private MemoryOwner _bufferOwner;
private Memory _buffer;
private int _size;
private int _headOffset;
@@ -24,7 +24,7 @@ namespace Ryujinx.Audio.Backends.Common
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
{
- _bufferOwner = ByteMemoryPool.RentCleared(initialCapacity);
+ _bufferOwner = MemoryOwner.RentCleared(initialCapacity);
_buffer = _bufferOwner.Memory;
}
@@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Backends.Common
private void SetCapacityLocked(int capacity)
{
- IMemoryOwner newBufferOwner = ByteMemoryPool.RentCleared(capacity);
+ MemoryOwner newBufferOwner = MemoryOwner.RentCleared(capacity);
Memory newBuffer = newBufferOwner.Memory;
if (_size > 0)
diff --git a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
index 608381af1..7f881373f 100644
--- a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
+++ b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
@@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common
{
public const int Align = 0x10;
public const int BiquadStateOffset = 0x0;
- public const int BiquadStateSize = 0x10;
///
/// The state of the biquad filters of this voice.
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
index 1a51a1fbd..31f614d67 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
@@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// The biquad filter parameter
/// The biquad filter state
/// The output buffer to write the result
- /// The input buffer to write the result
+ /// The input buffer to read the samples from
/// The count of samples to process
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
+ public static void ProcessBiquadFilter(
+ ref BiquadFilterParameter parameter,
+ ref BiquadFilterState state,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount)
{
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
@@ -40,6 +45,96 @@ namespace Ryujinx.Audio.Renderer.Dsp
}
}
+ ///
+ /// Apply a single biquad filter and mix the result into the output buffer.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to read the samples from
+ /// The count of samples to process
+ /// Mix volume
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ProcessBiquadFilterAndMix(
+ ref BiquadFilterParameter parameter,
+ ref BiquadFilterState state,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount,
+ float volume)
+ {
+ float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
+ float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
+ float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
+ float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
+
+ state.State1 = state.State0;
+ state.State0 = input;
+ state.State3 = state.State2;
+ state.State2 = output;
+
+ outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
+ }
+ }
+
+ ///
+ /// Apply a single biquad filter and mix the result into the output buffer with volume ramp.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to read the samples from
+ /// The count of samples to process
+ /// Initial mix volume
+ /// Volume increment step
+ /// Last filtered sample value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ProcessBiquadFilterAndMixRamp(
+ ref BiquadFilterParameter parameter,
+ ref BiquadFilterState state,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount,
+ float volume,
+ float ramp)
+ {
+ float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
+ float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
+ float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
+ float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
+
+ float mixState = 0f;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
+
+ state.State1 = state.State0;
+ state.State0 = input;
+ state.State3 = state.State2;
+ state.State2 = output;
+
+ mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
+
+ outputBuffer[i] += mixState;
+ volume += ramp;
+ }
+
+ return mixState;
+ }
+
///
/// Apply multiple biquad filter.
///
@@ -47,10 +142,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
/// The biquad filter parameter
/// The biquad filter state
/// The output buffer to write the result
- /// The input buffer to write the result
+ /// The input buffer to read the samples from
/// The count of samples to process
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ProcessBiquadFilter(ReadOnlySpan parameters, Span states, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
+ public static void ProcessBiquadFilter(
+ ReadOnlySpan parameters,
+ Span states,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount)
{
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
{
@@ -67,7 +167,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
for (int i = 0; i < sampleCount; i++)
{
- float input = inputBuffer[i];
+ float input = stageIndex != 0 ? outputBuffer[i] : inputBuffer[i];
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
state.State1 = state.State0;
@@ -79,5 +179,129 @@ namespace Ryujinx.Audio.Renderer.Dsp
}
}
}
+
+ ///
+ /// Apply double biquad filter and mix the result into the output buffer.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to read the samples from
+ /// The count of samples to process
+ /// Mix volume
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ProcessDoubleBiquadFilterAndMix(
+ ref BiquadFilterParameter parameter0,
+ ref BiquadFilterParameter parameter1,
+ ref BiquadFilterState state0,
+ ref BiquadFilterState state1,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount,
+ float volume)
+ {
+ float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
+ float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
+ float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
+ float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
+
+ float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
+ float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
+ float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
+ float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
+
+ state0.State1 = state0.State0;
+ state0.State0 = input;
+ state0.State3 = state0.State2;
+ state0.State2 = output;
+
+ input = output;
+ output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
+
+ state1.State1 = state1.State0;
+ state1.State0 = input;
+ state1.State3 = state1.State2;
+ state1.State2 = output;
+
+ outputBuffer[i] += FloatingPointHelper.MultiplyRoundUp(output, volume);
+ }
+ }
+
+ ///
+ /// Apply double biquad filter and mix the result into the output buffer with volume ramp.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to read the samples from
+ /// The count of samples to process
+ /// Initial mix volume
+ /// Volume increment step
+ /// Last filtered sample value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ProcessDoubleBiquadFilterAndMixRamp(
+ ref BiquadFilterParameter parameter0,
+ ref BiquadFilterParameter parameter1,
+ ref BiquadFilterState state0,
+ ref BiquadFilterState state1,
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ uint sampleCount,
+ float volume,
+ float ramp)
+ {
+ float a00 = FixedPointHelper.ToFloat(parameter0.Numerator[0], FixedPointPrecisionForParameter);
+ float a10 = FixedPointHelper.ToFloat(parameter0.Numerator[1], FixedPointPrecisionForParameter);
+ float a20 = FixedPointHelper.ToFloat(parameter0.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b10 = FixedPointHelper.ToFloat(parameter0.Denominator[0], FixedPointPrecisionForParameter);
+ float b20 = FixedPointHelper.ToFloat(parameter0.Denominator[1], FixedPointPrecisionForParameter);
+
+ float a01 = FixedPointHelper.ToFloat(parameter1.Numerator[0], FixedPointPrecisionForParameter);
+ float a11 = FixedPointHelper.ToFloat(parameter1.Numerator[1], FixedPointPrecisionForParameter);
+ float a21 = FixedPointHelper.ToFloat(parameter1.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b11 = FixedPointHelper.ToFloat(parameter1.Denominator[0], FixedPointPrecisionForParameter);
+ float b21 = FixedPointHelper.ToFloat(parameter1.Denominator[1], FixedPointPrecisionForParameter);
+
+ float mixState = 0f;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a00 + state0.State0 * a10 + state0.State1 * a20 + state0.State2 * b10 + state0.State3 * b20;
+
+ state0.State1 = state0.State0;
+ state0.State0 = input;
+ state0.State3 = state0.State2;
+ state0.State2 = output;
+
+ input = output;
+ output = input * a01 + state1.State0 * a11 + state1.State1 * a21 + state1.State2 * b11 + state1.State3 * b21;
+
+ state1.State1 = state1.State0;
+ state1.State0 = input;
+ state1.State3 = state1.State2;
+ state1.State2 = output;
+
+ mixState = FloatingPointHelper.MultiplyRoundUp(output, volume);
+
+ outputBuffer[i] += mixState;
+ volume += ramp;
+ }
+
+ return mixState;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs
new file mode 100644
index 000000000..106fc0357
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterAndMixCommand.cs
@@ -0,0 +1,123 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class BiquadFilterAndMixCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.BiquadFilterAndMix;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ private BiquadFilterParameter _parameter;
+
+ public Memory BiquadFilterState { get; }
+ public Memory PreviousBiquadFilterState { get; }
+
+ public Memory State { get; }
+
+ public int LastSampleIndex { get; }
+
+ public float Volume0 { get; }
+ public float Volume1 { get; }
+
+ public bool NeedInitialization { get; }
+ public bool HasVolumeRamp { get; }
+ public bool IsFirstMixBuffer { get; }
+
+ public BiquadFilterAndMixCommand(
+ float volume0,
+ float volume1,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ int lastSampleIndex,
+ Memory state,
+ ref BiquadFilterParameter filter,
+ Memory biquadFilterState,
+ Memory previousBiquadFilterState,
+ bool needInitialization,
+ bool hasVolumeRamp,
+ bool isFirstMixBuffer,
+ int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+
+ _parameter = filter;
+ BiquadFilterState = biquadFilterState;
+ PreviousBiquadFilterState = previousBiquadFilterState;
+
+ State = state;
+ LastSampleIndex = lastSampleIndex;
+
+ Volume0 = volume0;
+ Volume1 = volume1;
+
+ NeedInitialization = needInitialization;
+ HasVolumeRamp = hasVolumeRamp;
+ IsFirstMixBuffer = isFirstMixBuffer;
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ if (NeedInitialization)
+ {
+ // If there is no previous state, initialize to zero.
+
+ BiquadFilterState.Span[0] = new BiquadFilterState();
+ }
+ else if (IsFirstMixBuffer)
+ {
+ // This is the first buffer, set previous state to current state.
+
+ PreviousBiquadFilterState.Span[0] = BiquadFilterState.Span[0];
+ }
+ else
+ {
+ // Rewind the current state by copying back the previous state.
+
+ BiquadFilterState.Span[0] = PreviousBiquadFilterState.Span[0];
+ }
+
+ if (HasVolumeRamp)
+ {
+ float volume = Volume0;
+ float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
+
+ State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessBiquadFilterAndMixRamp(
+ ref _parameter,
+ ref BiquadFilterState.Span[0],
+ outputBuffer,
+ inputBuffer,
+ context.SampleCount,
+ volume,
+ ramp);
+ }
+ else
+ {
+ BiquadFilterHelper.ProcessBiquadFilterAndMix(
+ ref _parameter,
+ ref BiquadFilterState.Span[0],
+ outputBuffer,
+ inputBuffer,
+ context.SampleCount,
+ Volume1);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
index 098a04a04..de5c0ea2c 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
CopyMixBuffer,
LimiterVersion1,
LimiterVersion2,
- GroupedBiquadFilter,
+ MultiTapBiquadFilter,
CaptureBuffer,
Compressor,
+ BiquadFilterAndMix,
+ MultiTapBiquadFilterAndMix,
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
index 3c7dd63b2..41ac84c1a 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
@@ -24,7 +24,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public Memory State { get; }
- public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span volume0, Span volume1, Memory state, int nodeId)
+ public MixRampGroupedCommand(
+ uint mixBufferCount,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ ReadOnlySpan volume0,
+ ReadOnlySpan volume1,
+ Memory state,
+ int nodeId)
{
Enabled = true;
MixBufferCount = mixBufferCount;
@@ -48,7 +55,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static float ProcessMixRampGrouped(Span outputBuffer, ReadOnlySpan inputBuffer, float volume0, float volume1, int sampleCount)
+ private static float ProcessMixRampGrouped(
+ Span outputBuffer,
+ ReadOnlySpan inputBuffer,
+ float volume0,
+ float volume1,
+ int sampleCount)
{
float ramp = (volume1 - volume0) / sampleCount;
float volume = volume0;
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs
new file mode 100644
index 000000000..e359371b4
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterAndMixCommand.cs
@@ -0,0 +1,145 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MultiTapBiquadFilterAndMixCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.MultiTapBiquadFilterAndMix;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ private BiquadFilterParameter _parameter0;
+ private BiquadFilterParameter _parameter1;
+
+ public Memory BiquadFilterState0 { get; }
+ public Memory BiquadFilterState1 { get; }
+ public Memory PreviousBiquadFilterState0 { get; }
+ public Memory PreviousBiquadFilterState1 { get; }
+
+ public Memory State { get; }
+
+ public int LastSampleIndex { get; }
+
+ public float Volume0 { get; }
+ public float Volume1 { get; }
+
+ public bool NeedInitialization0 { get; }
+ public bool NeedInitialization1 { get; }
+ public bool HasVolumeRamp { get; }
+ public bool IsFirstMixBuffer { get; }
+
+ public MultiTapBiquadFilterAndMixCommand(
+ float volume0,
+ float volume1,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ int lastSampleIndex,
+ Memory state,
+ ref BiquadFilterParameter filter0,
+ ref BiquadFilterParameter filter1,
+ Memory biquadFilterState0,
+ Memory biquadFilterState1,
+ Memory previousBiquadFilterState0,
+ Memory previousBiquadFilterState1,
+ bool needInitialization0,
+ bool needInitialization1,
+ bool hasVolumeRamp,
+ bool isFirstMixBuffer,
+ int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+
+ _parameter0 = filter0;
+ _parameter1 = filter1;
+ BiquadFilterState0 = biquadFilterState0;
+ BiquadFilterState1 = biquadFilterState1;
+ PreviousBiquadFilterState0 = previousBiquadFilterState0;
+ PreviousBiquadFilterState1 = previousBiquadFilterState1;
+
+ State = state;
+ LastSampleIndex = lastSampleIndex;
+
+ Volume0 = volume0;
+ Volume1 = volume1;
+
+ NeedInitialization0 = needInitialization0;
+ NeedInitialization1 = needInitialization1;
+ HasVolumeRamp = hasVolumeRamp;
+ IsFirstMixBuffer = isFirstMixBuffer;
+ }
+
+ private void UpdateState(Memory state, Memory previousState, bool needInitialization)
+ {
+ if (needInitialization)
+ {
+ // If there is no previous state, initialize to zero.
+
+ state.Span[0] = new BiquadFilterState();
+ }
+ else if (IsFirstMixBuffer)
+ {
+ // This is the first buffer, set previous state to current state.
+
+ previousState.Span[0] = state.Span[0];
+ }
+ else
+ {
+ // Rewind the current state by copying back the previous state.
+
+ state.Span[0] = previousState.Span[0];
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ UpdateState(BiquadFilterState0, PreviousBiquadFilterState0, NeedInitialization0);
+ UpdateState(BiquadFilterState1, PreviousBiquadFilterState1, NeedInitialization1);
+
+ if (HasVolumeRamp)
+ {
+ float volume = Volume0;
+ float ramp = (Volume1 - Volume0) / (int)context.SampleCount;
+
+ State.Span[0].LastSamples[LastSampleIndex] = BiquadFilterHelper.ProcessDoubleBiquadFilterAndMixRamp(
+ ref _parameter0,
+ ref _parameter1,
+ ref BiquadFilterState0.Span[0],
+ ref BiquadFilterState1.Span[0],
+ outputBuffer,
+ inputBuffer,
+ context.SampleCount,
+ volume,
+ ramp);
+ }
+ else
+ {
+ BiquadFilterHelper.ProcessDoubleBiquadFilterAndMix(
+ ref _parameter0,
+ ref _parameter1,
+ ref BiquadFilterState0.Span[0],
+ ref BiquadFilterState1.Span[0],
+ outputBuffer,
+ inputBuffer,
+ context.SampleCount,
+ Volume1);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs
similarity index 84%
rename from src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
rename to src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs
index 7af851bdc..e159f8ef7 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MultiTapBiquadFilterCommand.cs
@@ -4,13 +4,13 @@ using System;
namespace Ryujinx.Audio.Renderer.Dsp.Command
{
- public class GroupedBiquadFilterCommand : ICommand
+ public class MultiTapBiquadFilterCommand : ICommand
{
public bool Enabled { get; set; }
public int NodeId { get; }
- public CommandType CommandType => CommandType.GroupedBiquadFilter;
+ public CommandType CommandType => CommandType.MultiTapBiquadFilter;
public uint EstimatedProcessingTime { get; set; }
@@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
private readonly int _outputBufferIndex;
private readonly bool[] _isInitialized;
- public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
+ public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
{
_parameters = filters.ToArray();
_biquadFilterStates = biquadFilterStateMemory;
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
index f9a32b3f9..58a2d9cce 100644
--- a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
+++ b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
@@ -2,12 +2,16 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
- [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)]
public struct BiquadFilterState
{
public float State0;
public float State1;
public float State2;
public float State3;
+ public float State4;
+ public float State5;
+ public float State6;
+ public float State7;
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
new file mode 100644
index 000000000..807232f20
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs
@@ -0,0 +1,43 @@
+using Ryujinx.Common.Memory;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Generic interface for the splitter destination parameters.
+ ///
+ public interface ISplitterDestinationInParameter
+ {
+ ///
+ /// Target splitter destination data id.
+ ///
+ int Id { get; }
+
+ ///
+ /// The mix to output the result of the splitter.
+ ///
+ int DestinationId { get; }
+
+ ///
+ /// Biquad filter parameters.
+ ///
+ Array2 BiquadFilters { get; }
+
+ ///
+ /// Set to true if in use.
+ ///
+ bool IsUsed { get; }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ Span MixBufferVolume { get; }
+
+ ///
+ /// Check if the magic is valid.
+ ///
+ /// Returns true if the magic is valid.
+ bool IsMagicValid();
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
similarity index 73%
rename from src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs
rename to src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
index b74b67be0..029c001ea 100644
--- a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs
+++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities;
using System;
using System.Runtime.InteropServices;
@@ -5,10 +6,10 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter
{
///
- /// Input header for a splitter destination update.
+ /// Input header for a splitter destination version 1 update.
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
- public struct SplitterDestinationInParameter
+ public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter
{
///
/// Magic of the input header.
@@ -41,7 +42,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
///
private unsafe fixed byte _reserved[3];
- [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
private struct MixArray { }
///
@@ -50,6 +51,14 @@ namespace Ryujinx.Audio.Renderer.Parameter
/// Used when a splitter id is specified in the mix.
public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume);
+ readonly int ISplitterDestinationInParameter.Id => Id;
+
+ readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
+
+ readonly Array2 ISplitterDestinationInParameter.BiquadFilters => default;
+
+ readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
+
///
/// The expected constant of any input header.
///
diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
new file mode 100644
index 000000000..312be8b70
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs
@@ -0,0 +1,81 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Parameter
+{
+ ///
+ /// Input header for a splitter destination version 2 update.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter
+ {
+ ///
+ /// Magic of the input header.
+ ///
+ public uint Magic;
+
+ ///
+ /// Target splitter destination data id.
+ ///
+ public int Id;
+
+ ///
+ /// Mix buffer volumes storage.
+ ///
+ private MixArray _mixBufferVolume;
+
+ ///
+ /// The mix to output the result of the splitter.
+ ///
+ public int DestinationId;
+
+ ///
+ /// Biquad filter parameters.
+ ///
+ public Array2 BiquadFilters;
+
+ ///
+ /// Set to true if in use.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private unsafe fixed byte _reserved[11];
+
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixArray { }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume);
+
+ readonly int ISplitterDestinationInParameter.Id => Id;
+
+ readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
+
+ readonly Array2 ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
+
+ readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
+
+ ///
+ /// The expected constant of any input header.
+ ///
+ private const uint ValidMagic = 0x44444E53;
+
+ ///
+ /// Check if the magic is valid.
+ ///
+ /// Returns true if the magic is valid.
+ public readonly bool IsMagicValid()
+ {
+ return Magic == ValidMagic;
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index 9b56f5cbd..246889c48 100644
--- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -1,6 +1,7 @@
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server.Effect;
using Ryujinx.Audio.Renderer.Server.MemoryPool;
@@ -173,6 +174,22 @@ namespace Ryujinx.Audio.Renderer.Server
return ResultCode.WorkBufferTooSmall;
}
+ Memory splitterBqfStates = Memory.Empty;
+
+ if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
+ parameter.SplitterCount > 0 &&
+ parameter.SplitterDestinationCount > 0)
+ {
+ splitterBqfStates = workBufferAllocator.Allocate(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
+
+ if (splitterBqfStates.IsEmpty)
+ {
+ return ResultCode.WorkBufferTooSmall;
+ }
+
+ splitterBqfStates.Span.Clear();
+ }
+
// Invalidate DSP cache on what was currently allocated with workBuffer.
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
@@ -292,7 +309,7 @@ namespace Ryujinx.Audio.Renderer.Server
state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
}
- if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator))
+ if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator, splitterBqfStates))
{
return ResultCode.WorkBufferTooSmall;
}
@@ -775,6 +792,13 @@ namespace Ryujinx.Audio.Renderer.Server
// Splitter
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
+ if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
+ parameter.SplitterCount > 0 &&
+ parameter.SplitterDestinationCount > 0)
+ {
+ size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
+ }
+
// DSP Voice
size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align);
diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
index fe1dfc4be..32c7de6cf 100644
--- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Server
/// was added to supply the count of update done sent to the DSP.
/// A new version of the command estimator was added to address timing changes caused by the voice changes.
/// Additionally, the rendering limit percent was incremented to 80%.
- ///
///
/// This was added in system update 6.0.0
public const int Revision5 = 5 << 24;
@@ -101,10 +100,18 @@ namespace Ryujinx.Audio.Renderer.Server
/// This was added in system update 14.0.0 but some changes were made in 15.0.0
public const int Revision11 = 11 << 24;
+ ///
+ /// REV12:
+ /// Two new commands were added to for biquad filtering and mixing (with optinal volume ramp) on the same command.
+ /// Splitter destinations can now specify up to two biquad filtering parameters, used for filtering the buffer before mixing.
+ ///
+ /// This was added in system update 17.0.0
+ public const int Revision12 = 12 << 24;
+
///
/// Last revision supported by the implementation.
///
- public const int LastRevision = Revision11;
+ public const int LastRevision = Revision12;
///
/// Target revision magic supported by the implementation.
@@ -354,7 +361,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice.
///
/// True if the audio renderer should use the optimization.
- public bool IsBiquadFilterGroupedOptimizationSupported()
+ public bool UseMultiTapBiquadFilterProcessing()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
}
@@ -368,6 +375,15 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
}
+ ///
+ /// Check if the audio renderer should support biquad filter on splitter.
+ ///
+ /// True if the audio renderer support biquad filter on splitter
+ public bool IsBiquadFilterParameterForSplitterEnabled()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
+ }
+
///
/// Get the version of the .
///
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index f4174a913..702f05462 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server
}
///
- /// Create a new .
+ /// Create a new .
///
/// The base index of the input and output buffer.
/// The biquad filter parameters.
@@ -213,9 +213,9 @@ namespace Ryujinx.Audio.Renderer.Server
/// The output buffer offset.
/// Set to true if the biquad filter state is initialized.
/// The node id associated to this command.
- public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
+ public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
{
- GroupedBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
+ MultiTapBiquadFilterCommand command = new(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
@@ -232,7 +232,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// The new volume.
/// The to generate the command from.
/// The node id associated to this command.
- public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span previousVolume, Span volume, Memory state, int nodeId)
+ public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan previousVolume, ReadOnlySpan volume, Memory state, int nodeId)
{
MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
@@ -260,6 +260,120 @@ namespace Ryujinx.Audio.Renderer.Server
AddCommand(command);
}
+ ///
+ /// Generate a new .
+ ///
+ /// The previous volume.
+ /// The new volume.
+ /// The input buffer index.
+ /// The output buffer index.
+ /// The index in the array to store the ramped sample.
+ /// The to generate the command from.
+ /// The biquad filter parameter.
+ /// The biquad state.
+ /// The previous biquad state.
+ /// Set to true if the biquad filter state needs to be initialized.
+ /// Set to true if the mix has volume ramp, and should be taken into account.
+ /// Set to true if the buffer is the first mix buffer.
+ /// The node id associated to this command.
+ public void GenerateBiquadFilterAndMix(
+ float previousVolume,
+ float volume,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ int lastSampleIndex,
+ Memory state,
+ ref BiquadFilterParameter filter,
+ Memory biquadFilterState,
+ Memory previousBiquadFilterState,
+ bool needInitialization,
+ bool hasVolumeRamp,
+ bool isFirstMixBuffer,
+ int nodeId)
+ {
+ BiquadFilterAndMixCommand command = new(
+ previousVolume,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ lastSampleIndex,
+ state,
+ ref filter,
+ biquadFilterState,
+ previousBiquadFilterState,
+ needInitialization,
+ hasVolumeRamp,
+ isFirstMixBuffer,
+ nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
+ ///
+ /// Generate a new .
+ ///
+ /// The previous volume.
+ /// The new volume.
+ /// The input buffer index.
+ /// The output buffer index.
+ /// The index in the array to store the ramped sample.
+ /// The to generate the command from.
+ /// First biquad filter parameter.
+ /// Second biquad filter parameter.
+ /// First biquad state.
+ /// Second biquad state.
+ /// First previous biquad state.
+ /// Second previous biquad state.
+ /// Set to true if the first biquad filter state needs to be initialized.
+ /// Set to true if the second biquad filter state needs to be initialized.
+ /// Set to true if the mix has volume ramp, and should be taken into account.
+ /// Set to true if the buffer is the first mix buffer.
+ /// The node id associated to this command.
+ public void GenerateMultiTapBiquadFilterAndMix(
+ float previousVolume,
+ float volume,
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ int lastSampleIndex,
+ Memory state,
+ ref BiquadFilterParameter filter0,
+ ref BiquadFilterParameter filter1,
+ Memory biquadFilterState0,
+ Memory biquadFilterState1,
+ Memory previousBiquadFilterState0,
+ Memory previousBiquadFilterState1,
+ bool needInitialization0,
+ bool needInitialization1,
+ bool hasVolumeRamp,
+ bool isFirstMixBuffer,
+ int nodeId)
+ {
+ MultiTapBiquadFilterAndMixCommand command = new(
+ previousVolume,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ lastSampleIndex,
+ state,
+ ref filter0,
+ ref filter1,
+ biquadFilterState0,
+ biquadFilterState1,
+ previousBiquadFilterState0,
+ previousBiquadFilterState1,
+ needInitialization0,
+ needInitialization1,
+ hasVolumeRamp,
+ isFirstMixBuffer,
+ nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
///
/// Generate a new .
///
@@ -268,7 +382,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// The buffer count.
/// The node id associated to this command.
/// The target sample rate in use.
- public void GenerateDepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
+ public void GenerateDepopForMixBuffers(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
{
DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
index ae8f699f3..d798230c1 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
@@ -12,6 +12,7 @@ using Ryujinx.Audio.Renderer.Server.Voice;
using Ryujinx.Audio.Renderer.Utils;
using System;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server
{
@@ -46,12 +47,13 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
- _commandBuffer.GenerateDepopPrepare(dspState,
- _rendererContext.DepopBuffer,
- mix.BufferCount,
- mix.BufferOffset,
- voiceState.NodeId,
- voiceState.WasPlaying);
+ _commandBuffer.GenerateDepopPrepare(
+ dspState,
+ _rendererContext.DepopBuffer,
+ mix.BufferCount,
+ mix.BufferOffset,
+ voiceState.NodeId,
+ voiceState.WasPlaying);
}
else if (voiceState.SplitterId != Constants.UnusedSplitterId)
{
@@ -59,15 +61,13 @@ namespace Ryujinx.Audio.Renderer.Server
while (true)
{
- Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
+ SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
- if (destinationSpan.IsEmpty)
+ if (destination.IsNull)
{
break;
}
- ref SplitterDestination destination = ref destinationSpan[0];
-
if (destination.IsConfigured())
{
int mixId = destination.DestinationId;
@@ -76,12 +76,13 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState mix = ref _mixContext.GetState(mixId);
- _commandBuffer.GenerateDepopPrepare(dspState,
- _rendererContext.DepopBuffer,
- mix.BufferCount,
- mix.BufferOffset,
- voiceState.NodeId,
- voiceState.WasPlaying);
+ _commandBuffer.GenerateDepopPrepare(
+ dspState,
+ _rendererContext.DepopBuffer,
+ mix.BufferCount,
+ mix.BufferOffset,
+ voiceState.NodeId,
+ voiceState.WasPlaying);
destination.MarkAsNeedToUpdateInternalState();
}
@@ -95,35 +96,39 @@ namespace Ryujinx.Audio.Renderer.Server
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
{
- _commandBuffer.GenerateDataSourceVersion2(ref voiceState,
- dspState,
- (ushort)_rendererContext.MixBufferCount,
- (ushort)channelIndex,
- voiceState.NodeId);
+ _commandBuffer.GenerateDataSourceVersion2(
+ ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
}
else
{
switch (voiceState.SampleFormat)
{
case SampleFormat.PcmInt16:
- _commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState,
- dspState,
- (ushort)_rendererContext.MixBufferCount,
- (ushort)channelIndex,
- voiceState.NodeId);
+ _commandBuffer.GeneratePcmInt16DataSourceVersion1(
+ ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
break;
case SampleFormat.PcmFloat:
- _commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState,
- dspState,
- (ushort)_rendererContext.MixBufferCount,
- (ushort)channelIndex,
- voiceState.NodeId);
+ _commandBuffer.GeneratePcmFloatDataSourceVersion1(
+ ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ (ushort)channelIndex,
+ voiceState.NodeId);
break;
case SampleFormat.Adpcm:
- _commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState,
- dspState,
- (ushort)_rendererContext.MixBufferCount,
- voiceState.NodeId);
+ _commandBuffer.GenerateAdpcmDataSourceVersion1(
+ ref voiceState,
+ dspState,
+ (ushort)_rendererContext.MixBufferCount,
+ voiceState.NodeId);
break;
default:
throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
@@ -134,14 +139,14 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory state, int baseIndex, int bufferOffset, int nodeId)
{
- bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported();
+ bool supportsOptimizedPath = _rendererContext.BehaviourContext.UseMultiTapBiquadFilterProcessing();
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
{
- Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
+ Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(Unsafe.SizeOf() * Constants.VoiceBiquadFilterCount)];
Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory);
- _commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
+ _commandBuffer.GenerateMultiTapBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
}
else
{
@@ -151,33 +156,134 @@ namespace Ryujinx.Audio.Renderer.Server
if (filter.Enable)
{
- Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
-
+ Memory biquadStateRawMemory = SpanMemoryManager.Cast(state)[..(Unsafe.SizeOf() * Constants.VoiceBiquadFilterCount)];
Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory);
- _commandBuffer.GenerateBiquadFilter(baseIndex,
- ref filter,
- stateMemory.Slice(i, 1),
- bufferOffset,
- bufferOffset,
- !voiceState.BiquadFilterNeedInitialization[i],
- nodeId);
+ _commandBuffer.GenerateBiquadFilter(
+ baseIndex,
+ ref filter,
+ stateMemory.Slice(i, 1),
+ bufferOffset,
+ bufferOffset,
+ !voiceState.BiquadFilterNeedInitialization[i],
+ nodeId);
}
}
}
}
- private void GenerateVoiceMix(Span mixVolumes, Span previousMixVolumes, Memory state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
+ private void GenerateVoiceMixWithSplitter(
+ SplitterDestination destination,
+ Memory state,
+ uint bufferOffset,
+ uint bufferCount,
+ uint bufferIndex,
+ int nodeId)
+ {
+ ReadOnlySpan mixVolumes = destination.MixBufferVolume;
+ ReadOnlySpan previousMixVolumes = destination.PreviousMixBufferVolume;
+
+ ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
+ ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
+
+ Memory bqfState = _splitterContext.GetBiquadFilterState(destination);
+
+ bool isFirstMixBuffer = true;
+
+ for (int i = 0; i < bufferCount; i++)
+ {
+ float previousMixVolume = previousMixVolumes[i];
+ float mixVolume = mixVolumes[i];
+
+ if (mixVolume != 0.0f || previousMixVolume != 0.0f)
+ {
+ if (bqf0.Enable && bqf1.Enable)
+ {
+ _commandBuffer.GenerateMultiTapBiquadFilterAndMix(
+ previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ ref bqf0,
+ ref bqf1,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ bqfState.Slice(2, 1),
+ bqfState.Slice(3, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ !destination.IsBiquadFilterEnabledPrev(),
+ true,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(0);
+ destination.UpdateBiquadFilterEnabledPrev(1);
+ }
+ else if (bqf0.Enable)
+ {
+ _commandBuffer.GenerateBiquadFilterAndMix(
+ previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ ref bqf0,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ true,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(0);
+ }
+ else if (bqf1.Enable)
+ {
+ _commandBuffer.GenerateBiquadFilterAndMix(
+ previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ ref bqf1,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ true,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(1);
+ }
+
+ isFirstMixBuffer = false;
+ }
+ }
+ }
+
+ private void GenerateVoiceMix(
+ ReadOnlySpan mixVolumes,
+ ReadOnlySpan previousMixVolumes,
+ Memory state,
+ uint bufferOffset,
+ uint bufferCount,
+ uint bufferIndex,
+ int nodeId)
{
if (bufferCount > Constants.VoiceChannelCountMax)
{
- _commandBuffer.GenerateMixRampGrouped(bufferCount,
- bufferIndex,
- bufferOffset,
- previousMixVolumes,
- mixVolumes,
- state,
- nodeId);
+ _commandBuffer.GenerateMixRampGrouped(
+ bufferCount,
+ bufferIndex,
+ bufferOffset,
+ previousMixVolumes,
+ mixVolumes,
+ state,
+ nodeId);
}
else
{
@@ -188,13 +294,14 @@ namespace Ryujinx.Audio.Renderer.Server
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
{
- _commandBuffer.GenerateMixRamp(previousMixVolume,
- mixVolume,
- bufferIndex,
- bufferOffset + (uint)i,
- i,
- state,
- nodeId);
+ _commandBuffer.GenerateMixRamp(
+ previousMixVolume,
+ mixVolume,
+ bufferIndex,
+ bufferOffset + (uint)i,
+ i,
+ state,
+ nodeId);
}
}
}
@@ -271,10 +378,11 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
}
- _commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume,
- voiceState.Volume,
- _rendererContext.MixBufferCount + (uint)channelIndex,
- nodeId);
+ _commandBuffer.GenerateVolumeRamp(
+ voiceState.PreviousVolume,
+ voiceState.Volume,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
if (performanceInitialized)
{
@@ -291,15 +399,13 @@ namespace Ryujinx.Audio.Renderer.Server
while (true)
{
- Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
+ SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
- if (destinationSpan.IsEmpty)
+ if (destination.IsNull)
{
break;
}
- ref SplitterDestination destination = ref destinationSpan[0];
-
destinationId += (int)channelsCount;
if (destination.IsConfigured())
@@ -310,13 +416,27 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState mix = ref _mixContext.GetState(mixId);
- GenerateVoiceMix(destination.MixBufferVolume,
- destination.PreviousMixBufferVolume,
- dspStateMemory,
- mix.BufferOffset,
- mix.BufferCount,
- _rendererContext.MixBufferCount + (uint)channelIndex,
- nodeId);
+ if (destination.IsBiquadFilterEnabled())
+ {
+ GenerateVoiceMixWithSplitter(
+ destination,
+ dspStateMemory,
+ mix.BufferOffset,
+ mix.BufferCount,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
+ }
+ else
+ {
+ GenerateVoiceMix(
+ destination.MixBufferVolume,
+ destination.PreviousMixBufferVolume,
+ dspStateMemory,
+ mix.BufferOffset,
+ mix.BufferCount,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
+ }
destination.MarkAsNeedToUpdateInternalState();
}
@@ -337,13 +457,14 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
}
- GenerateVoiceMix(channelResource.Mix.AsSpan(),
- channelResource.PreviousMix.AsSpan(),
- dspStateMemory,
- mix.BufferOffset,
- mix.BufferCount,
- _rendererContext.MixBufferCount + (uint)channelIndex,
- nodeId);
+ GenerateVoiceMix(
+ channelResource.Mix.AsSpan(),
+ channelResource.PreviousMix.AsSpan(),
+ dspStateMemory,
+ mix.BufferOffset,
+ mix.BufferCount,
+ _rendererContext.MixBufferCount + (uint)channelIndex,
+ nodeId);
if (performanceInitialized)
{
@@ -409,10 +530,11 @@ namespace Ryujinx.Audio.Renderer.Server
{
if (effect.Parameter.Volumes[i] != 0.0f)
{
- _commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i],
- (uint)bufferOffset + effect.Parameter.Output[i],
- nodeId,
- effect.Parameter.Volumes[i]);
+ _commandBuffer.GenerateMix(
+ (uint)bufferOffset + effect.Parameter.Input[i],
+ (uint)bufferOffset + effect.Parameter.Output[i],
+ nodeId,
+ effect.Parameter.Volumes[i]);
}
}
}
@@ -447,17 +569,18 @@ namespace Ryujinx.Audio.Renderer.Server
updateCount = newUpdateCount;
}
- _commandBuffer.GenerateAuxEffect(bufferOffset,
- effect.Parameter.Input[i],
- effect.Parameter.Output[i],
- ref effect.State,
- effect.IsEnabled,
- effect.Parameter.BufferStorageSize,
- effect.State.SendBufferInfoBase,
- effect.State.ReturnBufferInfoBase,
- updateCount,
- writeOffset,
- nodeId);
+ _commandBuffer.GenerateAuxEffect(
+ bufferOffset,
+ effect.Parameter.Input[i],
+ effect.Parameter.Output[i],
+ ref effect.State,
+ effect.IsEnabled,
+ effect.Parameter.BufferStorageSize,
+ effect.State.SendBufferInfoBase,
+ effect.State.ReturnBufferInfoBase,
+ updateCount,
+ writeOffset,
+ nodeId);
writeOffset = newUpdateCount;
@@ -500,7 +623,7 @@ namespace Ryujinx.Audio.Renderer.Server
if (effect.IsEnabled)
{
bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
- (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
+ (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed());
BiquadFilterParameter parameter = new()
{
@@ -512,11 +635,14 @@ namespace Ryujinx.Audio.Renderer.Server
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
{
- _commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1),
- effect.Parameter.Input[i],
- effect.Parameter.Output[i],
- needInitialization,
- nodeId);
+ _commandBuffer.GenerateBiquadFilter(
+ (int)bufferOffset,
+ ref parameter,
+ effect.State.Slice(i, 1),
+ effect.Parameter.Input[i],
+ effect.Parameter.Output[i],
+ needInitialization,
+ nodeId);
}
}
else
@@ -591,15 +717,16 @@ namespace Ryujinx.Audio.Renderer.Server
updateCount = newUpdateCount;
}
- _commandBuffer.GenerateCaptureEffect(bufferOffset,
- effect.Parameter.Input[i],
- effect.State.SendBufferInfo,
- effect.IsEnabled,
- effect.Parameter.BufferStorageSize,
- effect.State.SendBufferInfoBase,
- updateCount,
- writeOffset,
- nodeId);
+ _commandBuffer.GenerateCaptureEffect(
+ bufferOffset,
+ effect.Parameter.Input[i],
+ effect.State.SendBufferInfo,
+ effect.IsEnabled,
+ effect.Parameter.BufferStorageSize,
+ effect.State.SendBufferInfoBase,
+ updateCount,
+ writeOffset,
+ nodeId);
writeOffset = newUpdateCount;
@@ -612,11 +739,12 @@ namespace Ryujinx.Audio.Renderer.Server
{
Debug.Assert(effect.Type == EffectType.Compressor);
- _commandBuffer.GenerateCompressorEffect(bufferOffset,
- effect.Parameter,
- effect.State,
- effect.IsEnabled,
- nodeId);
+ _commandBuffer.GenerateCompressorEffect(
+ bufferOffset,
+ effect.Parameter,
+ effect.State,
+ effect.IsEnabled,
+ nodeId);
}
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
@@ -629,8 +757,11 @@ namespace Ryujinx.Audio.Renderer.Server
bool performanceInitialized = false;
- if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(),
- isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId))
+ if (_performanceManager != null && _performanceManager.GetNextEntry(
+ out performanceEntry,
+ effect.GetPerformanceDetailType(),
+ isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix,
+ nodeId))
{
performanceInitialized = true;
@@ -706,6 +837,85 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
+ private void GenerateMixWithSplitter(
+ uint inputBufferIndex,
+ uint outputBufferIndex,
+ float volume,
+ SplitterDestination destination,
+ ref bool isFirstMixBuffer,
+ int nodeId)
+ {
+ ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
+ ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
+
+ Memory bqfState = _splitterContext.GetBiquadFilterState(destination);
+
+ if (bqf0.Enable && bqf1.Enable)
+ {
+ _commandBuffer.GenerateMultiTapBiquadFilterAndMix(
+ 0f,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ 0,
+ Memory.Empty,
+ ref bqf0,
+ ref bqf1,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ bqfState.Slice(2, 1),
+ bqfState.Slice(3, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ !destination.IsBiquadFilterEnabledPrev(),
+ false,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(0);
+ destination.UpdateBiquadFilterEnabledPrev(1);
+ }
+ else if (bqf0.Enable)
+ {
+ _commandBuffer.GenerateBiquadFilterAndMix(
+ 0f,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ 0,
+ Memory.Empty,
+ ref bqf0,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ false,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(0);
+ }
+ else if (bqf1.Enable)
+ {
+ _commandBuffer.GenerateBiquadFilterAndMix(
+ 0f,
+ volume,
+ inputBufferIndex,
+ outputBufferIndex,
+ 0,
+ Memory.Empty,
+ ref bqf1,
+ bqfState[..1],
+ bqfState.Slice(1, 1),
+ !destination.IsBiquadFilterEnabledPrev(),
+ false,
+ isFirstMixBuffer,
+ nodeId);
+
+ destination.UpdateBiquadFilterEnabledPrev(1);
+ }
+
+ isFirstMixBuffer = false;
+ }
+
private void GenerateMix(ref MixState mix)
{
if (mix.HasAnyDestination())
@@ -722,15 +932,13 @@ namespace Ryujinx.Audio.Renderer.Server
{
int destinationIndex = destinationId++;
- Span destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
+ SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
- if (destinationSpan.IsEmpty)
+ if (destination.IsNull)
{
break;
}
- ref SplitterDestination destination = ref destinationSpan[0];
-
if (destination.IsConfigured())
{
int mixId = destination.DestinationId;
@@ -741,16 +949,32 @@ namespace Ryujinx.Audio.Renderer.Server
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
+ bool isFirstMixBuffer = true;
+
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
{
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
if (volume != 0.0f)
{
- _commandBuffer.GenerateMix(inputBufferIndex,
- destinationMix.BufferOffset + bufferDestinationIndex,
- mix.NodeId,
- volume);
+ if (destination.IsBiquadFilterEnabled())
+ {
+ GenerateMixWithSplitter(
+ inputBufferIndex,
+ destinationMix.BufferOffset + bufferDestinationIndex,
+ volume,
+ destination,
+ ref isFirstMixBuffer,
+ mix.NodeId);
+ }
+ else
+ {
+ _commandBuffer.GenerateMix(
+ inputBufferIndex,
+ destinationMix.BufferOffset + bufferDestinationIndex,
+ mix.NodeId,
+ volume);
+ }
}
}
}
@@ -770,10 +994,11 @@ namespace Ryujinx.Audio.Renderer.Server
if (volume != 0.0f)
{
- _commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex,
- destinationMix.BufferOffset + bufferDestinationIndex,
- mix.NodeId,
- volume);
+ _commandBuffer.GenerateMix(
+ mix.BufferOffset + bufferIndex,
+ destinationMix.BufferOffset + bufferDestinationIndex,
+ mix.NodeId,
+ volume);
}
}
}
@@ -783,11 +1008,12 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateSubMix(ref MixState subMix)
{
- _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
- subMix.BufferOffset,
- subMix.BufferCount,
- subMix.NodeId,
- subMix.SampleRate);
+ _commandBuffer.GenerateDepopForMixBuffers(
+ _rendererContext.DepopBuffer,
+ subMix.BufferOffset,
+ subMix.BufferCount,
+ subMix.NodeId,
+ subMix.SampleRate);
GenerateEffects(ref subMix);
@@ -847,11 +1073,12 @@ namespace Ryujinx.Audio.Renderer.Server
{
ref MixState finalMix = ref _mixContext.GetFinalState();
- _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
- finalMix.BufferOffset,
- finalMix.BufferCount,
- finalMix.NodeId,
- finalMix.SampleRate);
+ _commandBuffer.GenerateDepopForMixBuffers(
+ _rendererContext.DepopBuffer,
+ finalMix.BufferOffset,
+ finalMix.BufferCount,
+ finalMix.NodeId,
+ finalMix.SampleRate);
GenerateEffects(ref finalMix);
@@ -882,9 +1109,10 @@ namespace Ryujinx.Audio.Renderer.Server
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
}
- _commandBuffer.GenerateVolume(finalMix.Volume,
- finalMix.BufferOffset + bufferIndex,
- nodeId);
+ _commandBuffer.GenerateVolume(
+ finalMix.Volume,
+ finalMix.BufferOffset + bufferIndex,
+ nodeId);
if (performanceSubInitialized)
{
@@ -938,41 +1166,45 @@ namespace Ryujinx.Audio.Renderer.Server
if (useCustomDownMixingCommand)
{
- _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
- sink.Parameter.Input.AsSpan(),
- sink.Parameter.Input.AsSpan(),
- sink.DownMixCoefficients,
- Constants.InvalidNodeId);
+ _commandBuffer.GenerateDownMixSurroundToStereo(
+ finalMix.BufferOffset,
+ sink.Parameter.Input.AsSpan(),
+ sink.Parameter.Input.AsSpan(),
+ sink.DownMixCoefficients,
+ Constants.InvalidNodeId);
}
// NOTE: We do the downmixing at the DSP level as it's easier that way.
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
{
- _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
- sink.Parameter.Input.AsSpan(),
- sink.Parameter.Input.AsSpan(),
- Constants.DefaultSurroundToStereoCoefficients,
- Constants.InvalidNodeId);
+ _commandBuffer.GenerateDownMixSurroundToStereo(
+ finalMix.BufferOffset,
+ sink.Parameter.Input.AsSpan(),
+ sink.Parameter.Input.AsSpan(),
+ Constants.DefaultSurroundToStereoCoefficients,
+ Constants.InvalidNodeId);
}
CommandList commandList = _commandBuffer.CommandList;
if (sink.UpsamplerState != null)
{
- _commandBuffer.GenerateUpsample(finalMix.BufferOffset,
- sink.UpsamplerState,
- sink.Parameter.InputCount,
- sink.Parameter.Input.AsSpan(),
- commandList.BufferCount,
- commandList.SampleCount,
- commandList.SampleRate,
- Constants.InvalidNodeId);
+ _commandBuffer.GenerateUpsample(
+ finalMix.BufferOffset,
+ sink.UpsamplerState,
+ sink.Parameter.InputCount,
+ sink.Parameter.Input.AsSpan(),
+ commandList.BufferCount,
+ commandList.SampleCount,
+ commandList.SampleRate,
+ Constants.InvalidNodeId);
}
- _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset,
- sink,
- _rendererContext.SessionId,
- commandList.Buffers,
- Constants.InvalidNodeId);
+ _commandBuffer.GenerateDeviceSink(
+ finalMix.BufferOffset,
+ sink,
+ _rendererContext.SessionId,
+ commandList.Buffers,
+ Constants.InvalidNodeId);
}
private void GenerateSink(BaseSink sink, ref MixState finalMix)
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
index d95e9aa71..cff754b82 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
@@ -170,7 +170,7 @@ namespace Ryujinx.Audio.Renderer.Server
return 0;
}
- public uint Estimate(GroupedBiquadFilterCommand command)
+ public uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -184,5 +184,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
index 929aaf383..ef1326924 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
@@ -462,7 +462,7 @@ namespace Ryujinx.Audio.Renderer.Server
return 0;
}
- public uint Estimate(GroupedBiquadFilterCommand command)
+ public uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -476,5 +476,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
index 8ae4bc059..31a5347b4 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
@@ -632,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
};
}
- public virtual uint Estimate(GroupedBiquadFilterCommand command)
+ public virtual uint Estimate(MultiTapBiquadFilterCommand command)
{
return 0;
}
@@ -646,5 +646,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public virtual uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
+
+ public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
index 25bc67cd9..fb357120d 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
@@ -10,7 +10,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
- public override uint Estimate(GroupedBiquadFilterCommand command)
+ public override uint Estimate(MultiTapBiquadFilterCommand command)
{
Debug.Assert(SampleCount == 160 || SampleCount == 240);
diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
index 7135c1c4f..06f135a88 100644
--- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
@@ -210,5 +210,53 @@ namespace Ryujinx.Audio.Renderer.Server
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
};
}
+
+ public override uint Estimate(BiquadFilterAndMixCommand command)
+ {
+ Debug.Assert(SampleCount == 160 || SampleCount == 240);
+
+ if (command.HasVolumeRamp)
+ {
+ if (SampleCount == 160)
+ {
+ return 5204;
+ }
+
+ return 6683;
+ }
+ else
+ {
+ if (SampleCount == 160)
+ {
+ return 3427;
+ }
+
+ return 4752;
+ }
+ }
+
+ public override uint Estimate(MultiTapBiquadFilterAndMixCommand command)
+ {
+ Debug.Assert(SampleCount == 160 || SampleCount == 240);
+
+ if (command.HasVolumeRamp)
+ {
+ if (SampleCount == 160)
+ {
+ return 7939;
+ }
+
+ return 10669;
+ }
+ else
+ {
+ if (SampleCount == 160)
+ {
+ return 6256;
+ }
+
+ return 8683;
+ }
+ }
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
index 27b22363a..9c4312ad6 100644
--- a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
@@ -33,8 +33,10 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(UpsampleCommand command);
uint Estimate(LimiterCommandVersion1 command);
uint Estimate(LimiterCommandVersion2 command);
- uint Estimate(GroupedBiquadFilterCommand command);
+ uint Estimate(MultiTapBiquadFilterCommand command);
uint Estimate(CaptureBufferCommand command);
uint Estimate(CompressorCommand command);
+ uint Estimate(BiquadFilterAndMixCommand command);
+ uint Estimate(MultiTapBiquadFilterAndMixCommand command);
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
index b90574da9..5ba58ea5b 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
@@ -225,11 +225,11 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
for (int i = 0; i < splitter.DestinationCount; i++)
{
- Span destination = splitter.GetData(i);
+ SplitterDestination destination = splitter.GetData(i);
- if (!destination.IsEmpty)
+ if (!destination.IsNull)
{
- int destinationMixId = destination[0].DestinationId;
+ int destinationMixId = destination.DestinationId;
if (destinationMixId != UnusedMixId)
{
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
index 3efa783c3..a7b82a6bd 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
@@ -1,4 +1,5 @@
using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Utils;
using Ryujinx.Common;
@@ -15,15 +16,35 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public class SplitterContext
{
+ ///
+ /// Amount of biquad filter states per splitter destination.
+ ///
+ public const int BqfStatesPerDestination = 4;
+
///
/// Storage for .
///
private Memory _splitters;
///
- /// Storage for .
+ /// Storage for .
///
- private Memory _splitterDestinations;
+ private Memory _splitterDestinationsV1;
+
+ ///
+ /// Storage for .
+ ///
+ private Memory _splitterDestinationsV2;
+
+ ///
+ /// Splitter biquad filtering states.
+ ///
+ private Memory _splitterBqfStates;
+
+ ///
+ /// Version of the splitter context that is being used, currently can be 1 or 2.
+ ///
+ public int Version { get; private set; }
///
/// If set to true, trust the user destination count in .
@@ -36,12 +57,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// The behaviour context.
/// The audio renderer configuration.
/// The .
+ /// Memory to store the biquad filtering state for splitters during processing.
/// Return true if the initialization was successful.
- public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
+ public bool Initialize(
+ ref BehaviourContext behaviourContext,
+ ref AudioRendererConfiguration parameter,
+ WorkBufferAllocator workBufferAllocator,
+ Memory splitterBqfStates)
{
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
{
- Setup(Memory.Empty, Memory.Empty, false);
+ Setup(Memory.Empty, Memory.Empty, Memory.Empty, false);
return true;
}
@@ -60,23 +86,62 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
splitter = new SplitterState(splitterId++);
}
- Memory splitterDestinations = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
- SplitterDestination.Alignment);
+ Memory splitterDestinationsV1 = Memory.Empty;
+ Memory splitterDestinationsV2 = Memory.Empty;
- if (splitterDestinations.IsEmpty)
+ if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
{
- return false;
+ Version = 1;
+
+ splitterDestinationsV1 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
+ SplitterDestinationVersion1.Alignment);
+
+ if (splitterDestinationsV1.IsEmpty)
+ {
+ return false;
+ }
+
+ int splitterDestinationId = 0;
+ foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span)
+ {
+ data = new SplitterDestinationVersion1(splitterDestinationId++);
+ }
}
-
- int splitterDestinationId = 0;
- foreach (ref SplitterDestination data in splitterDestinations.Span)
+ else
{
- data = new SplitterDestination(splitterDestinationId++);
+ Version = 2;
+
+ splitterDestinationsV2 = workBufferAllocator.Allocate(parameter.SplitterDestinationCount,
+ SplitterDestinationVersion2.Alignment);
+
+ if (splitterDestinationsV2.IsEmpty)
+ {
+ return false;
+ }
+
+ int splitterDestinationId = 0;
+ foreach (ref SplitterDestinationVersion2 data in splitterDestinationsV2.Span)
+ {
+ data = new SplitterDestinationVersion2(splitterDestinationId++);
+ }
+
+ if (parameter.SplitterDestinationCount > 0)
+ {
+ // Official code stores it in the SplitterDestinationVersion2 struct,
+ // but we don't to avoid using unsafe code.
+
+ splitterBqfStates.Span.Clear();
+ _splitterBqfStates = splitterBqfStates;
+ }
+ else
+ {
+ _splitterBqfStates = Memory.Empty;
+ }
}
SplitterState.InitializeSplitters(splitters.Span);
- Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed());
+ Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
return true;
}
@@ -93,7 +158,15 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
if (behaviourContext.IsSplitterSupported())
{
size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment);
- size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
+
+ if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
+ {
+ size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment);
+ }
+ else
+ {
+ size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment);
+ }
if (behaviourContext.IsSplitterBugFixed())
{
@@ -110,12 +183,18 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Setup the instance.
///
/// The storage.
- /// The storage.
+ /// The storage.
+ /// The storage.
/// If set to true, trust the user destination count in .
- private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed)
+ private void Setup(
+ Memory splitters,
+ Memory splitterDestinationsV1,
+ Memory splitterDestinationsV2,
+ bool isBugFixed)
{
_splitters = splitters;
- _splitterDestinations = splitterDestinations;
+ _splitterDestinationsV1 = splitterDestinationsV1;
+ _splitterDestinationsV2 = splitterDestinationsV2;
IsBugFixed = isBugFixed;
}
@@ -141,7 +220,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
return 0;
}
- return _splitterDestinations.Length / _splitters.Length;
+ int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
+
+ return length / _splitters.Length;
}
///
@@ -178,7 +259,39 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
///
- /// Update one or multiple from user parameters.
+ /// Update one splitter destination data from user parameters.
+ ///
+ /// The raw data after the splitter header.
+ /// True if the update was successful, false otherwise
+ private bool UpdateData(ref SequenceReader input) where T : unmanaged, ISplitterDestinationInParameter
+ {
+ ref readonly T parameter = ref input.GetRefOrRefToCopy(out _);
+
+ Debug.Assert(parameter.IsMagicValid());
+
+ if (parameter.IsMagicValid())
+ {
+ int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
+
+ if (parameter.Id >= 0 && parameter.Id < length)
+ {
+ SplitterDestination destination = GetDestination(parameter.Id);
+
+ destination.Update(parameter);
+ }
+
+ return true;
+ }
+ else
+ {
+ input.Rewind(Unsafe.SizeOf());
+
+ return false;
+ }
+ }
+
+ ///
+ /// Update one or multiple splitter destination data from user parameters.
///
/// The splitter header.
/// The raw data after the splitter header.
@@ -186,23 +299,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
{
- ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy(out _);
-
- Debug.Assert(parameter.IsMagicValid());
-
- if (parameter.IsMagicValid())
+ if (Version == 1)
{
- if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
+ if (!UpdateData(ref input))
{
- ref SplitterDestination destination = ref GetDestination(parameter.Id);
-
- destination.Update(parameter);
+ break;
+ }
+ }
+ else if (Version == 2)
+ {
+ if (!UpdateData(ref input))
+ {
+ break;
}
}
else
{
- input.Rewind(Unsafe.SizeOf());
- break;
+ Debug.Fail($"Invalid splitter context version {Version}.");
}
}
}
@@ -214,7 +327,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// Return true if the update was successful.
public bool Update(ref SequenceReader input)
{
- if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
+ if (!UsingSplitter())
{
return true;
}
@@ -251,45 +364,52 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
///
- /// Get a reference to a at the given .
+ /// Get a reference to the splitter destination data at the given .
///
/// The index to use.
- /// A reference to a at the given .
- public ref SplitterDestination GetDestination(int id)
+ /// A reference to the splitter destination data at the given .
+ public SplitterDestination GetDestination(int id)
{
- return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
+ if (_splitterDestinationsV2.IsEmpty)
+ {
+ return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV1, id, (uint)_splitterDestinationsV1.Length));
+ }
+ else
+ {
+ return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV2, id, (uint)_splitterDestinationsV2.Length));
+ }
}
///
- /// Get a at the given .
- ///
- /// The index to use.
- /// A at the given .
- public Memory GetDestinationMemory(int id)
- {
- return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
- }
-
- ///
- /// Get a in the at and pass to .
+ /// Get a in the at and pass to .
///
/// The index to use to get the .
/// The index of the .
- /// A .
- public Span GetDestination(int id, int destinationId)
+ /// A .
+ public SplitterDestination GetDestination(int id, int destinationId)
{
ref SplitterState splitter = ref GetState(id);
return splitter.GetData(destinationId);
}
+ ///
+ /// Gets the biquad filter state for a given splitter destination.
+ ///
+ /// The splitter destination.
+ /// Biquad filter state for the specified destination.
+ public Memory GetBiquadFilterState(SplitterDestination destination)
+ {
+ return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination);
+ }
+
///
/// Return true if the audio renderer has any splitters.
///
/// True if the audio renderer has any splitters.
public bool UsingSplitter()
{
- return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
+ return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty);
}
///
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
index 1faf7921f..36dfa5e41 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
@@ -1,115 +1,198 @@
using Ryujinx.Audio.Renderer.Parameter;
-using Ryujinx.Common.Utilities;
using System;
using System.Diagnostics;
-using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
namespace Ryujinx.Audio.Renderer.Server.Splitter
{
///
/// Server state for a splitter destination.
///
- [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
- public struct SplitterDestination
+ public ref struct SplitterDestination
{
- public const int Alignment = 0x10;
+ private ref SplitterDestinationVersion1 _v1;
+ private ref SplitterDestinationVersion2 _v2;
///
- /// The unique id of this .
+ /// Checks if the splitter destination data reference is null.
///
- public int Id;
+ public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2);
///
- /// The mix to output the result of the splitter.
+ /// The splitter unique id.
///
- public int DestinationId;
-
- ///
- /// Mix buffer volumes storage.
- ///
- private MixArray _mix;
- private MixArray _previousMix;
-
- ///
- /// Pointer to the next linked element.
- ///
- private unsafe SplitterDestination* _next;
-
- ///
- /// Set to true if in use.
- ///
- [MarshalAs(UnmanagedType.I1)]
- public bool IsUsed;
-
- ///
- /// Set to true if the internal state need to be updated.
- ///
- [MarshalAs(UnmanagedType.I1)]
- public bool NeedToUpdateInternalState;
-
- [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
- private struct MixArray { }
-
- ///
- /// Mix buffer volumes.
- ///
- /// Used when a splitter id is specified in the mix.
- public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix);
-
- ///
- /// Previous mix buffer volumes.
- ///
- /// Used when a splitter id is specified in the mix.
- public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix);
-
- ///
- /// Get the of the next element or if not present.
- ///
- public readonly Span Next
+ public int Id
{
get
{
- unsafe
+ if (Unsafe.IsNullRef(ref _v2))
{
- return _next != null ? new Span(_next, 1) : Span.Empty;
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return 0;
+ }
+ else
+ {
+ return _v1.Id;
+ }
+ }
+ else
+ {
+ return _v2.Id;
}
}
}
///
- /// Create a new .
+ /// The mix to output the result of the splitter.
///
- /// The unique id of this .
- public SplitterDestination(int id) : this()
+ public int DestinationId
{
- Id = id;
- DestinationId = Constants.UnusedMixId;
-
- ClearVolumes();
+ get
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return 0;
+ }
+ else
+ {
+ return _v1.DestinationId;
+ }
+ }
+ else
+ {
+ return _v2.DestinationId;
+ }
+ }
}
///
- /// Update the from user parameter.
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span MixBufferVolume
+ {
+ get
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return Span.Empty;
+ }
+ else
+ {
+ return _v1.MixBufferVolume;
+ }
+ }
+ else
+ {
+ return _v2.MixBufferVolume;
+ }
+ }
+ }
+
+ ///
+ /// Previous mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span PreviousMixBufferVolume
+ {
+ get
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return Span.Empty;
+ }
+ else
+ {
+ return _v1.PreviousMixBufferVolume;
+ }
+ }
+ else
+ {
+ return _v2.PreviousMixBufferVolume;
+ }
+ }
+ }
+
+ ///
+ /// Get the of the next element or null if not present.
+ ///
+ public readonly SplitterDestination Next
+ {
+ get
+ {
+ unsafe
+ {
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ if (Unsafe.IsNullRef(ref _v1))
+ {
+ return new SplitterDestination();
+ }
+ else
+ {
+ return new SplitterDestination(ref _v1.Next);
+ }
+ }
+ else
+ {
+ return new SplitterDestination(ref _v2.Next);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Creates a new splitter destination wrapper for the version 1 splitter destination data.
+ ///
+ /// Version 1 splitter destination data
+ public SplitterDestination(ref SplitterDestinationVersion1 v1)
+ {
+ _v1 = ref v1;
+ _v2 = ref Unsafe.NullRef();
+ }
+
+ ///
+ /// Creates a new splitter destination wrapper for the version 2 splitter destination data.
+ ///
+ /// Version 2 splitter destination data
+ public SplitterDestination(ref SplitterDestinationVersion2 v2)
+ {
+
+ _v1 = ref Unsafe.NullRef();
+ _v2 = ref v2;
+ }
+
+ ///
+ /// Creates a new splitter destination wrapper for the splitter destination data.
+ ///
+ /// Version 1 splitter destination data
+ /// Version 2 splitter destination data
+ public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2)
+ {
+ _v1 = ref Unsafe.AsRef(v1);
+ _v2 = ref Unsafe.AsRef(v2);
+ }
+
+ ///
+ /// Update the splitter destination data from user parameter.
///
/// The user parameter.
- public void Update(SplitterDestinationInParameter parameter)
+ public void Update(in T parameter) where T : ISplitterDestinationInParameter
{
- Debug.Assert(Id == parameter.Id);
-
- if (parameter.IsMagicValid() && Id == parameter.Id)
+ if (Unsafe.IsNullRef(ref _v2))
{
- DestinationId = parameter.DestinationId;
-
- parameter.MixBufferVolume.CopyTo(MixBufferVolume);
-
- if (!IsUsed && parameter.IsUsed)
- {
- MixBufferVolume.CopyTo(PreviousMixBufferVolume);
-
- NeedToUpdateInternalState = false;
- }
-
- IsUsed = parameter.IsUsed;
+ _v1.Update(parameter);
+ }
+ else
+ {
+ _v2.Update(parameter);
}
}
@@ -118,12 +201,14 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public void UpdateInternalState()
{
- if (IsUsed && NeedToUpdateInternalState)
+ if (Unsafe.IsNullRef(ref _v2))
{
- MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+ _v1.UpdateInternalState();
+ }
+ else
+ {
+ _v2.UpdateInternalState();
}
-
- NeedToUpdateInternalState = false;
}
///
@@ -131,16 +216,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public void MarkAsNeedToUpdateInternalState()
{
- NeedToUpdateInternalState = true;
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ _v1.MarkAsNeedToUpdateInternalState();
+ }
+ else
+ {
+ _v2.MarkAsNeedToUpdateInternalState();
+ }
}
///
- /// Return true if the is used and has a destination.
+ /// Return true if the splitter destination is used and has a destination.
///
- /// True if the is used and has a destination.
+ /// True if the splitter destination is used and has a destination.
public readonly bool IsConfigured()
{
- return IsUsed && DestinationId != Constants.UnusedMixId;
+ return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured();
}
///
@@ -150,9 +242,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
/// The volume for the given destination.
public float GetMixVolume(int destinationIndex)
{
- Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+ return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolume(destinationIndex) : _v2.GetMixVolume(destinationIndex);
+ }
- return MixBufferVolume[destinationIndex];
+ ///
+ /// Get the previous volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolumePrev(int destinationIndex)
+ {
+ return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex);
}
///
@@ -160,22 +260,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public void ClearVolumes()
{
- MixBufferVolume.Clear();
- PreviousMixBufferVolume.Clear();
+ if (Unsafe.IsNullRef(ref _v2))
+ {
+ _v1.ClearVolumes();
+ }
+ else
+ {
+ _v2.ClearVolumes();
+ }
}
///
- /// Link the next element to the given .
+ /// Link the next element to the given splitter destination.
///
- /// The given to link.
- public void Link(ref SplitterDestination next)
+ /// The given splitter destination to link.
+ public void Link(SplitterDestination next)
{
- unsafe
+ if (Unsafe.IsNullRef(ref _v2))
{
- fixed (SplitterDestination* nextPtr = &next)
- {
- _next = nextPtr;
- }
+ Debug.Assert(!Unsafe.IsNullRef(ref next._v1));
+
+ _v1.Link(ref next._v1);
+ }
+ else
+ {
+ Debug.Assert(!Unsafe.IsNullRef(ref next._v2));
+
+ _v2.Link(ref next._v2);
}
}
@@ -184,10 +295,74 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public void Unlink()
{
- unsafe
+ if (Unsafe.IsNullRef(ref _v2))
{
- _next = null;
+ _v1.Unlink();
}
+ else
+ {
+ _v2.Unlink();
+ }
+ }
+
+ ///
+ /// Checks if any biquad filter is enabled.
+ ///
+ /// True if any biquad filter is enabled.
+ public bool IsBiquadFilterEnabled()
+ {
+ return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled();
+ }
+
+ ///
+ /// Checks if any biquad filter was previously enabled.
+ ///
+ /// True if any biquad filter was previously enabled.
+ public bool IsBiquadFilterEnabledPrev()
+ {
+ return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev();
+ }
+
+ ///
+ /// Gets the biquad filter parameters.
+ ///
+ /// Biquad filter index (0 or 1).
+ /// Biquad filter parameters.
+ public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
+ {
+ Debug.Assert(!Unsafe.IsNullRef(ref _v2));
+
+ return ref _v2.GetBiquadFilterParameter(index);
+ }
+
+ ///
+ /// Checks if any biquad filter was previously enabled.
+ ///
+ /// Biquad filter index (0 or 1).
+ public void UpdateBiquadFilterEnabledPrev(int index)
+ {
+ if (!Unsafe.IsNullRef(ref _v2))
+ {
+ _v2.UpdateBiquadFilterEnabledPrev(index);
+ }
+ }
+
+ ///
+ /// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null.
+ ///
+ /// Reference for the version 1 splitter destination data.
+ public ref SplitterDestinationVersion1 GetV1RefOrNull()
+ {
+ return ref _v1;
+ }
+
+ ///
+ /// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null.
+ ///
+ /// Reference for the version 2 splitter destination data.
+ public ref SplitterDestinationVersion2 GetV2RefOrNull()
+ {
+ return ref _v2;
}
}
}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
new file mode 100644
index 000000000..5d2b8fb0f
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs
@@ -0,0 +1,206 @@
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ ///
+ /// Server state for a splitter destination (version 1).
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
+ public struct SplitterDestinationVersion1
+ {
+ public const int Alignment = 0x10;
+
+ ///
+ /// The unique id of this .
+ ///
+ public int Id;
+
+ ///
+ /// The mix to output the result of the splitter.
+ ///
+ public int DestinationId;
+
+ ///
+ /// Mix buffer volumes storage.
+ ///
+ private MixArray _mix;
+ private MixArray _previousMix;
+
+ ///
+ /// Pointer to the next linked element.
+ ///
+ private unsafe SplitterDestinationVersion1* _next;
+
+ ///
+ /// Set to true if in use.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ ///
+ /// Set to true if the internal state need to be updated.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool NeedToUpdateInternalState;
+
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixArray { }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix);
+
+ ///
+ /// Previous mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix);
+
+ ///
+ /// Get the reference of the next element or null if not present.
+ ///
+ public readonly ref SplitterDestinationVersion1 Next
+ {
+ get
+ {
+ unsafe
+ {
+ return ref Unsafe.AsRef(_next);
+ }
+ }
+ }
+
+ ///
+ /// Create a new .
+ ///
+ /// The unique id of this .
+ public SplitterDestinationVersion1(int id) : this()
+ {
+ Id = id;
+ DestinationId = Constants.UnusedMixId;
+
+ ClearVolumes();
+ }
+
+ ///
+ /// Update the from user parameter.
+ ///
+ /// The user parameter.
+ public void Update(in T parameter) where T : ISplitterDestinationInParameter
+ {
+ Debug.Assert(Id == parameter.Id);
+
+ if (parameter.IsMagicValid() && Id == parameter.Id)
+ {
+ DestinationId = parameter.DestinationId;
+
+ parameter.MixBufferVolume.CopyTo(MixBufferVolume);
+
+ if (!IsUsed && parameter.IsUsed)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+
+ NeedToUpdateInternalState = false;
+ }
+
+ IsUsed = parameter.IsUsed;
+ }
+ }
+
+ ///
+ /// Update the internal state of the instance.
+ ///
+ public void UpdateInternalState()
+ {
+ if (IsUsed && NeedToUpdateInternalState)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+ }
+
+ NeedToUpdateInternalState = false;
+ }
+
+ ///
+ /// Set the update internal state marker.
+ ///
+ public void MarkAsNeedToUpdateInternalState()
+ {
+ NeedToUpdateInternalState = true;
+ }
+
+ ///
+ /// Return true if the is used and has a destination.
+ ///
+ /// True if the is used and has a destination.
+ public readonly bool IsConfigured()
+ {
+ return IsUsed && DestinationId != Constants.UnusedMixId;
+ }
+
+ ///
+ /// Get the volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolume(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return MixBufferVolume[destinationIndex];
+ }
+
+ ///
+ /// Get the previous volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolumePrev(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return PreviousMixBufferVolume[destinationIndex];
+ }
+
+ ///
+ /// Clear the volumes.
+ ///
+ public void ClearVolumes()
+ {
+ MixBufferVolume.Clear();
+ PreviousMixBufferVolume.Clear();
+ }
+
+ ///
+ /// Link the next element to the given .
+ ///
+ /// The given to link.
+ public void Link(ref SplitterDestinationVersion1 next)
+ {
+ unsafe
+ {
+ fixed (SplitterDestinationVersion1* nextPtr = &next)
+ {
+ _next = nextPtr;
+ }
+ }
+ }
+
+ ///
+ /// Remove the link to the next element.
+ ///
+ public void Unlink()
+ {
+ unsafe
+ {
+ _next = null;
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
new file mode 100644
index 000000000..f9487909d
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs
@@ -0,0 +1,250 @@
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Server.Splitter
+{
+ ///
+ /// Server state for a splitter destination (version 2).
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)]
+ public struct SplitterDestinationVersion2
+ {
+ public const int Alignment = 0x10;
+
+ ///
+ /// The unique id of this .
+ ///
+ public int Id;
+
+ ///
+ /// The mix to output the result of the splitter.
+ ///
+ public int DestinationId;
+
+ ///
+ /// Mix buffer volumes storage.
+ ///
+ private MixArray _mix;
+ private MixArray _previousMix;
+
+ ///
+ /// Pointer to the next linked element.
+ ///
+ private unsafe SplitterDestinationVersion2* _next;
+
+ ///
+ /// Set to true if in use.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ ///
+ /// Set to true if the internal state need to be updated.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool NeedToUpdateInternalState;
+
+ [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
+ private struct MixArray { }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix);
+
+ ///
+ /// Previous mix buffer volumes.
+ ///
+ /// Used when a splitter id is specified in the mix.
+ public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix);
+
+ ///
+ /// Get the reference of the next element or null if not present.
+ ///
+ public readonly ref SplitterDestinationVersion2 Next
+ {
+ get
+ {
+ unsafe
+ {
+ return ref Unsafe.AsRef(_next);
+ }
+ }
+ }
+
+ private Array2 _biquadFilters;
+
+ private Array2 _isPreviousBiquadFilterEnabled;
+
+ ///
+ /// Create a new .
+ ///
+ /// The unique id of this .
+ public SplitterDestinationVersion2(int id) : this()
+ {
+ Id = id;
+ DestinationId = Constants.UnusedMixId;
+
+ ClearVolumes();
+ }
+
+ ///
+ /// Update the from user parameter.
+ ///
+ /// The user parameter.
+ public void Update(in T parameter) where T : ISplitterDestinationInParameter
+ {
+ Debug.Assert(Id == parameter.Id);
+
+ if (parameter.IsMagicValid() && Id == parameter.Id)
+ {
+ DestinationId = parameter.DestinationId;
+
+ parameter.MixBufferVolume.CopyTo(MixBufferVolume);
+
+ _biquadFilters = parameter.BiquadFilters;
+
+ if (!IsUsed && parameter.IsUsed)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+
+ NeedToUpdateInternalState = false;
+ }
+
+ IsUsed = parameter.IsUsed;
+ }
+ }
+
+ ///
+ /// Update the internal state of the instance.
+ ///
+ public void UpdateInternalState()
+ {
+ if (IsUsed && NeedToUpdateInternalState)
+ {
+ MixBufferVolume.CopyTo(PreviousMixBufferVolume);
+ }
+
+ NeedToUpdateInternalState = false;
+ }
+
+ ///
+ /// Set the update internal state marker.
+ ///
+ public void MarkAsNeedToUpdateInternalState()
+ {
+ NeedToUpdateInternalState = true;
+ }
+
+ ///
+ /// Return true if the is used and has a destination.
+ ///
+ /// True if the is used and has a destination.
+ public readonly bool IsConfigured()
+ {
+ return IsUsed && DestinationId != Constants.UnusedMixId;
+ }
+
+ ///
+ /// Get the volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolume(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return MixBufferVolume[destinationIndex];
+ }
+
+ ///
+ /// Get the previous volume for a given destination.
+ ///
+ /// The destination index to use.
+ /// The volume for the given destination.
+ public float GetMixVolumePrev(int destinationIndex)
+ {
+ Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
+
+ return PreviousMixBufferVolume[destinationIndex];
+ }
+
+ ///
+ /// Clear the volumes.
+ ///
+ public void ClearVolumes()
+ {
+ MixBufferVolume.Clear();
+ PreviousMixBufferVolume.Clear();
+ }
+
+ ///
+ /// Link the next element to the given .
+ ///
+ /// The given to link.
+ public void Link(ref SplitterDestinationVersion2 next)
+ {
+ unsafe
+ {
+ fixed (SplitterDestinationVersion2* nextPtr = &next)
+ {
+ _next = nextPtr;
+ }
+ }
+ }
+
+ ///
+ /// Remove the link to the next element.
+ ///
+ public void Unlink()
+ {
+ unsafe
+ {
+ _next = null;
+ }
+ }
+
+ ///
+ /// Checks if any biquad filter is enabled.
+ ///
+ /// True if any biquad filter is enabled.
+ public bool IsBiquadFilterEnabled()
+ {
+ return _biquadFilters[0].Enable || _biquadFilters[1].Enable;
+ }
+
+ ///
+ /// Checks if any biquad filter was previously enabled.
+ ///
+ /// True if any biquad filter was previously enabled.
+ public bool IsBiquadFilterEnabledPrev()
+ {
+ return _isPreviousBiquadFilterEnabled[0];
+ }
+
+ ///
+ /// Gets the biquad filter parameters.
+ ///
+ /// Biquad filter index (0 or 1).
+ /// Biquad filter parameters.
+ public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
+ {
+ return ref _biquadFilters[index];
+ }
+
+ ///
+ /// Checks if any biquad filter was previously enabled.
+ ///
+ /// Biquad filter index (0 or 1).
+ public void UpdateBiquadFilterEnabledPrev(int index)
+ {
+ _isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable;
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
index 944f092d2..3e7dce559 100644
--- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
+++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
@@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
public const int Alignment = 0x10;
+ private delegate void SplitterDestinationAction(SplitterDestination destination, int index);
+
///
/// The unique id of this .
///
@@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
public uint SampleRate;
///
- /// Count of splitter destinations ().
+ /// Count of splitter destinations.
///
public int DestinationCount;
@@ -37,20 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
public bool HasNewConnection;
///
- /// Linked list of .
+ /// Linked list of .
///
- private unsafe SplitterDestination* _destinationsData;
+ private unsafe SplitterDestinationVersion1* _destinationDataV1;
///
- /// Span to the first element of the linked list of .
+ /// Linked list of .
///
- public readonly Span Destinations
+ private unsafe SplitterDestinationVersion2* _destinationDataV2;
+
+ ///
+ /// First element of the linked list of splitter destinations data.
+ ///
+ public readonly SplitterDestination Destination
{
get
{
unsafe
{
- return (IntPtr)_destinationsData != IntPtr.Zero ? new Span(_destinationsData, 1) : Span.Empty;
+ return new SplitterDestination(_destinationDataV1, _destinationDataV2);
}
}
}
@@ -64,20 +71,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
Id = id;
}
- public readonly Span GetData(int index)
+ public readonly SplitterDestination GetData(int index)
{
int i = 0;
- Span result = Destinations;
+ SplitterDestination result = Destination;
while (i < index)
{
- if (result.IsEmpty)
+ if (result.IsNull)
{
break;
}
- result = result[0].Next;
+ result = result.Next;
i++;
}
@@ -93,25 +100,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
///
- /// Utility function to apply a given to all .
+ /// Utility function to apply an action to all .
///
/// The action to execute on each elements.
- private readonly void ForEachDestination(SpanAction action)
+ private readonly void ForEachDestination(SplitterDestinationAction action)
{
- Span temp = Destinations;
+ SplitterDestination temp = Destination;
int i = 0;
while (true)
{
- if (temp.IsEmpty)
+ if (temp.IsNull)
{
break;
}
- Span next = temp[0].Next;
+ SplitterDestination next = temp.Next;
- action.Invoke(temp, i++);
+ action(temp, i++);
temp = next;
}
@@ -142,9 +149,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
input.ReadLittleEndian(out int destinationId);
- Memory destination = context.GetDestinationMemory(destinationId);
+ SplitterDestination destination = context.GetDestination(destinationId);
- SetDestination(ref destination.Span[0]);
+ SetDestination(destination);
DestinationCount = destinationCount;
@@ -152,9 +159,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
input.ReadLittleEndian(out destinationId);
- Memory nextDestination = context.GetDestinationMemory(destinationId);
+ SplitterDestination nextDestination = context.GetDestination(destinationId);
- destination.Span[0].Link(ref nextDestination.Span[0]);
+ destination.Link(nextDestination);
destination = nextDestination;
}
}
@@ -174,16 +181,21 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
}
///
- /// Set the head of the linked list of .
+ /// Set the head of the linked list of .
///
- /// A reference to a .
- public void SetDestination(ref SplitterDestination newValue)
+ /// New destination value.
+ public void SetDestination(SplitterDestination newValue)
{
unsafe
{
- fixed (SplitterDestination* newValuePtr = &newValue)
+ fixed (SplitterDestinationVersion1* newValuePtr = &newValue.GetV1RefOrNull())
{
- _destinationsData = newValuePtr;
+ _destinationDataV1 = newValuePtr;
+ }
+
+ fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull())
+ {
+ _destinationDataV2 = newValuePtr;
}
}
}
@@ -193,19 +205,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
///
public readonly void UpdateInternalState()
{
- ForEachDestination((destination, _) => destination[0].UpdateInternalState());
+ ForEachDestination((destination, _) => destination.UpdateInternalState());
}
///
- /// Clear all links from the .
+ /// Clear all links from the .
///
public void ClearLinks()
{
- ForEachDestination((destination, _) => destination[0].Unlink());
+ ForEachDestination((destination, _) => destination.Unlink());
unsafe
{
- _destinationsData = (SplitterDestination*)IntPtr.Zero;
+ _destinationDataV1 = null;
+ _destinationDataV2 = null;
}
}
@@ -219,7 +232,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
{
unsafe
{
- splitter._destinationsData = (SplitterDestination*)IntPtr.Zero;
+ splitter._destinationDataV1 = null;
+ splitter._destinationDataV2 = null;
}
splitter.DestinationCount = 0;
diff --git a/src/Ryujinx.Common/Memory/MemoryOwner.cs b/src/Ryujinx.Common/Memory/MemoryOwner.cs
new file mode 100644
index 000000000..b7fe1db77
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/MemoryOwner.cs
@@ -0,0 +1,140 @@
+#nullable enable
+using System;
+using System.Buffers;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// An implementation with an embedded length and fast
+ /// accessor, with memory allocated from .
+ ///
+ /// The type of item to store.
+ public sealed class MemoryOwner : IMemoryOwner
+ {
+ private readonly int _length;
+ private T[]? _array;
+
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The length of the new memory buffer to use
+ private MemoryOwner(int length)
+ {
+ _length = length;
+ _array = ArrayPool.Shared.Rent(length);
+ }
+
+ ///
+ /// Creates a new instance with the specified length.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryOwner Rent(int length) => new(length);
+
+ ///
+ /// Creates a new instance with the specified length and the content cleared.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length and the content cleared
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryOwner RentCleared(int length)
+ {
+ MemoryOwner result = new(length);
+
+ result._array.AsSpan(0, length).Clear();
+
+ return result;
+ }
+
+ ///
+ /// Creates a new instance with the content copied from the specified buffer.
+ ///
+ /// The buffer to copy
+ /// A instance with the same length and content as
+ public static MemoryOwner RentCopy(ReadOnlySpan buffer)
+ {
+ MemoryOwner result = new(buffer.Length);
+
+ buffer.CopyTo(result._array);
+
+ return result;
+ }
+
+ ///
+ /// Gets the number of items in the current instance.
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+
+ ///
+ public Memory Memory
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = _array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return new(array, 0, _length);
+ }
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ ///
+ /// Uses a trick made possible by the .NET 6+ runtime array layout.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = _array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array);
+
+ return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ T[]? array = Interlocked.Exchange(ref _array, null);
+
+ if (array is not null)
+ {
+ ArrayPool.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences());
+ }
+ }
+
+ ///
+ /// Throws an when is .
+ ///
+ [DoesNotReturn]
+ private static void ThrowObjectDisposedException()
+ {
+ throw new ObjectDisposedException(nameof(MemoryOwner), "The buffer has already been disposed.");
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Memory/SpanOwner.cs b/src/Ryujinx.Common/Memory/SpanOwner.cs
new file mode 100644
index 000000000..acb20bcad
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/SpanOwner.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// A stack-only type that rents a buffer of a specified length from .
+ /// It does not implement to avoid being boxed, but should still be disposed. This
+ /// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method.
+ /// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals.
+ /// For all these reasons, all usage should be with a `using` block or statement.
+ ///
+ /// The type of item to store.
+ public readonly ref struct SpanOwner
+ {
+ private readonly int _length;
+ private readonly T[] _array;
+
+ ///
+ /// Initializes a new instance of the struct with the specified parameters.
+ ///
+ /// The length of the new memory buffer to use
+ private SpanOwner(int length)
+ {
+ _length = length;
+ _array = ArrayPool.Shared.Rent(length);
+ }
+
+ ///
+ /// Gets an empty instance.
+ ///
+ public static SpanOwner Empty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new(0);
+ }
+
+ ///
+ /// Creates a new instance with the specified length.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanOwner Rent(int length) => new(length);
+
+ ///
+ /// Creates a new instance with the length and the content cleared.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length and the content cleared
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanOwner RentCleared(int length)
+ {
+ SpanOwner result = new(length);
+
+ result._array.AsSpan(0, length).Clear();
+
+ return result;
+ }
+
+ ///
+ /// Creates a new instance with the content copied from the specified buffer.
+ ///
+ /// The buffer to copy
+ /// A instance with the same length and content as
+ public static SpanOwner RentCopy(ReadOnlySpan buffer)
+ {
+ SpanOwner result = new(buffer.Length);
+
+ buffer.CopyTo(result._array);
+
+ return result;
+ }
+
+ ///
+ /// Gets the number of items in the current instance
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ ///
+ /// Uses a trick made possible by the .NET 6+ runtime array layout.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
+
+ return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
+ }
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ ArrayPool.Shared.Return(_array, RuntimeHelpers.IsReferenceOrContainsReferences());
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/BufferAccess.cs b/src/Ryujinx.Graphics.GAL/BufferAccess.cs
index faefa5188..1e7736f8f 100644
--- a/src/Ryujinx.Graphics.GAL/BufferAccess.cs
+++ b/src/Ryujinx.Graphics.GAL/BufferAccess.cs
@@ -6,8 +6,13 @@ namespace Ryujinx.Graphics.GAL
public enum BufferAccess
{
Default = 0,
- FlushPersistent = 1 << 0,
- Stream = 1 << 1,
- SparseCompatible = 1 << 2,
+ HostMemory = 1,
+ DeviceMemory = 2,
+ DeviceMemoryMapped = 3,
+
+ MemoryTypeMask = 0xf,
+
+ Stream = 1 << 4,
+ SparseCompatible = 1 << 5,
}
}
diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs
index 779ce5b5d..a5c6eb5c8 100644
--- a/src/Ryujinx.Graphics.GAL/Capabilities.cs
+++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs
@@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.GAL
{
public readonly TargetApi Api;
public readonly string VendorName;
+ public readonly SystemMemoryType MemoryType;
public readonly bool HasFrontFacingBug;
public readonly bool HasVectorIndexingBug;
@@ -50,6 +51,13 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsIndirectParameters;
public readonly bool SupportsDepthClipControl;
+ public readonly int UniformBufferSetIndex;
+ public readonly int StorageBufferSetIndex;
+ public readonly int TextureSetIndex;
+ public readonly int ImageSetIndex;
+ public readonly int ExtraSetBaseIndex;
+ public readonly int MaximumExtraSets;
+
public readonly uint MaximumUniformBuffersPerStage;
public readonly uint MaximumStorageBuffersPerStage;
public readonly uint MaximumTexturesPerStage;
@@ -66,6 +74,7 @@ namespace Ryujinx.Graphics.GAL
public Capabilities(
TargetApi api,
string vendorName,
+ SystemMemoryType memoryType,
bool hasFrontFacingBug,
bool hasVectorIndexingBug,
bool needsFragmentOutputSpecialization,
@@ -107,6 +116,12 @@ namespace Ryujinx.Graphics.GAL
bool supportsViewportSwizzle,
bool supportsIndirectParameters,
bool supportsDepthClipControl,
+ int uniformBufferSetIndex,
+ int storageBufferSetIndex,
+ int textureSetIndex,
+ int imageSetIndex,
+ int extraSetBaseIndex,
+ int maximumExtraSets,
uint maximumUniformBuffersPerStage,
uint maximumStorageBuffersPerStage,
uint maximumTexturesPerStage,
@@ -120,6 +135,7 @@ namespace Ryujinx.Graphics.GAL
{
Api = api;
VendorName = vendorName;
+ MemoryType = memoryType;
HasFrontFacingBug = hasFrontFacingBug;
HasVectorIndexingBug = hasVectorIndexingBug;
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
@@ -161,6 +177,12 @@ namespace Ryujinx.Graphics.GAL
SupportsViewportSwizzle = supportsViewportSwizzle;
SupportsIndirectParameters = supportsIndirectParameters;
SupportsDepthClipControl = supportsDepthClipControl;
+ UniformBufferSetIndex = uniformBufferSetIndex;
+ StorageBufferSetIndex = storageBufferSetIndex;
+ TextureSetIndex = textureSetIndex;
+ ImageSetIndex = imageSetIndex;
+ ExtraSetBaseIndex = extraSetBaseIndex;
+ MaximumExtraSets = maximumExtraSets;
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
MaximumTexturesPerStage = maximumTexturesPerStage;
diff --git a/src/Ryujinx.Graphics.GAL/Format.cs b/src/Ryujinx.Graphics.GAL/Format.cs
index bd3b38a9a..17c42d2d4 100644
--- a/src/Ryujinx.Graphics.GAL/Format.cs
+++ b/src/Ryujinx.Graphics.GAL/Format.cs
@@ -711,5 +711,36 @@ namespace Ryujinx.Graphics.GAL
{
return format.IsUint() || format.IsSint();
}
+
+ ///
+ /// Checks if the texture format is a float or sRGB color format.
+ ///
+ ///
+ /// Does not include normalized, compressed or depth formats.
+ /// Float and sRGB formats do not participate in logical operations.
+ ///
+ /// Texture format
+ /// True if the format is a float or sRGB color format, false otherwise
+ public static bool IsFloatOrSrgb(this Format format)
+ {
+ switch (format)
+ {
+ case Format.R8G8B8A8Srgb:
+ case Format.B8G8R8A8Srgb:
+ case Format.R16Float:
+ case Format.R16G16Float:
+ case Format.R16G16B16Float:
+ case Format.R16G16B16A16Float:
+ case Format.R32Float:
+ case Format.R32G32Float:
+ case Format.R32G32B32Float:
+ case Format.R32G32B32A32Float:
+ case Format.R11G11B10Float:
+ case Format.R9G9B9E5Float:
+ return true;
+ }
+
+ return false;
+ }
}
}
diff --git a/src/Ryujinx.Graphics.GAL/IImageArray.cs b/src/Ryujinx.Graphics.GAL/IImageArray.cs
index 30cff50b1..d119aa9fb 100644
--- a/src/Ryujinx.Graphics.GAL/IImageArray.cs
+++ b/src/Ryujinx.Graphics.GAL/IImageArray.cs
@@ -1,6 +1,8 @@
+using System;
+
namespace Ryujinx.Graphics.GAL
{
- public interface IImageArray
+ public interface IImageArray : IDisposable
{
void SetFormats(int index, Format[] imageFormats);
void SetImages(int index, ITexture[] images);
diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs
index 9efb9e3e8..cbf1bc3a2 100644
--- a/src/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -60,6 +60,7 @@ namespace Ryujinx.Graphics.GAL
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
+ void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array);
void SetLineParameters(float width, bool smooth);
@@ -91,6 +92,7 @@ namespace Ryujinx.Graphics.GAL
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
+ void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array);
void SetTransformFeedbackBuffers(ReadOnlySpan buffers);
void SetUniformBuffers(ReadOnlySpan buffers);
diff --git a/src/Ryujinx.Graphics.GAL/IRenderer.cs b/src/Ryujinx.Graphics.GAL/IRenderer.cs
index a3466e396..85d0bd729 100644
--- a/src/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/src/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -17,7 +17,6 @@ namespace Ryujinx.Graphics.GAL
void BackgroundContextAction(Action action, bool alwaysBackground = false);
BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default);
- BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint);
BufferHandle CreateBuffer(nint pointer, int size);
BufferHandle CreateBufferSparse(ReadOnlySpan storageBuffers);
diff --git a/src/Ryujinx.Graphics.GAL/ITextureArray.cs b/src/Ryujinx.Graphics.GAL/ITextureArray.cs
index 35c2116b5..9ee79dacb 100644
--- a/src/Ryujinx.Graphics.GAL/ITextureArray.cs
+++ b/src/Ryujinx.Graphics.GAL/ITextureArray.cs
@@ -1,6 +1,8 @@
+using System;
+
namespace Ryujinx.Graphics.GAL
{
- public interface ITextureArray
+ public interface ITextureArray : IDisposable
{
void SetSamplers(int index, ISampler[] samplers);
void SetTextures(int index, ITexture[] textures);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
index fd2919be4..ef227d4a5 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
@@ -44,7 +44,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
}
Register(CommandType.Action);
- Register(CommandType.CreateBuffer);
Register(CommandType.CreateBufferAccess);
Register(CommandType.CreateBufferSparse);
Register(CommandType.CreateHostBuffer);
@@ -67,6 +66,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.CounterEventDispose);
Register(CommandType.CounterEventFlush);
+ Register(CommandType.ImageArrayDispose);
Register(CommandType.ImageArraySetFormats);
Register(CommandType.ImageArraySetImages);
@@ -89,6 +89,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.TextureSetDataSliceRegion);
Register(CommandType.TextureSetStorage);
+ Register(CommandType.TextureArrayDispose);
Register(CommandType.TextureArraySetSamplers);
Register(CommandType.TextureArraySetTextures);
@@ -125,6 +126,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.SetUniformBuffers);
Register(CommandType.SetImage);
Register(CommandType.SetImageArray);
+ Register(CommandType.SetImageArraySeparate);
Register(CommandType.SetIndexBuffer);
Register(CommandType.SetLineParameters);
Register(CommandType.SetLogicOpState);
@@ -142,6 +144,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Register(CommandType.SetStencilTest);
Register(CommandType.SetTextureAndSampler);
Register(CommandType.SetTextureArray);
+ Register(CommandType.SetTextureArraySeparate);
Register(CommandType.SetUserClipDistance);
Register(CommandType.SetVertexAttribs);
Register(CommandType.SetVertexBuffers);
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
index a5e7336cd..cf3f5d6c1 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
@@ -3,7 +3,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
enum CommandType : byte
{
Action,
- CreateBuffer,
CreateBufferAccess,
CreateBufferSparse,
CreateHostBuffer,
@@ -27,6 +26,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
CounterEventDispose,
CounterEventFlush,
+ ImageArrayDispose,
ImageArraySetFormats,
ImageArraySetImages,
@@ -49,6 +49,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
TextureSetDataSliceRegion,
TextureSetStorage,
+ TextureArrayDispose,
TextureArraySetSamplers,
TextureArraySetTextures,
@@ -85,6 +86,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetUniformBuffers,
SetImage,
SetImageArray,
+ SetImageArraySeparate,
SetIndexBuffer,
SetLineParameters,
SetLogicOpState,
@@ -102,6 +104,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
SetStencilTest,
SetTextureAndSampler,
SetTextureArray,
+ SetTextureArraySeparate,
SetUserClipDistance,
SetVertexAttribs,
SetVertexBuffers,
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs
new file mode 100644
index 000000000..ac2ac933b
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/ImageArray/ImageArrayDisposeCommand.cs
@@ -0,0 +1,21 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
+{
+ struct ImageArrayDisposeCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.ImageArrayDispose;
+ private TableRef _imageArray;
+
+ public void Set(TableRef imageArray)
+ {
+ _imageArray = imageArray;
+ }
+
+ public static void Run(ref ImageArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._imageArray.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
deleted file mode 100644
index 60a6e4bf4..000000000
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
-{
- struct CreateBufferCommand : IGALCommand, IGALCommand
- {
- public readonly CommandType CommandType => CommandType.CreateBuffer;
- private BufferHandle _threadedHandle;
- private int _size;
- private BufferAccess _access;
- private BufferHandle _storageHint;
-
- public void Set(BufferHandle threadedHandle, int size, BufferAccess access, BufferHandle storageHint)
- {
- _threadedHandle = threadedHandle;
- _size = size;
- _access = access;
- _storageHint = storageHint;
- }
-
- public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
- {
- BufferHandle hint = BufferHandle.Null;
-
- if (command._storageHint != BufferHandle.Null)
- {
- hint = threaded.Buffers.MapBuffer(command._storageHint);
- }
-
- threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, command._access, hint));
- }
- }
-}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs
new file mode 100644
index 000000000..abeb58a06
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageArraySeparateCommand.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetImageArraySeparateCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.SetImageArraySeparate;
+ private ShaderStage _stage;
+ private int _setIndex;
+ private TableRef _array;
+
+ public void Set(ShaderStage stage, int setIndex, TableRef array)
+ {
+ _stage = stage;
+ _setIndex = setIndex;
+ _array = array;
+ }
+
+ public static void Run(ref SetImageArraySeparateCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetImageArraySeparate(command._stage, command._setIndex, command._array.GetAs(threaded)?.Base);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs
new file mode 100644
index 000000000..b179f2e70
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureArraySeparateCommand.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands
+{
+ struct SetTextureArraySeparateCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.SetTextureArraySeparate;
+ private ShaderStage _stage;
+ private int _setIndex;
+ private TableRef _array;
+
+ public void Set(ShaderStage stage, int setIndex, TableRef array)
+ {
+ _stage = stage;
+ _setIndex = setIndex;
+ _array = array;
+ }
+
+ public static void Run(ref SetTextureArraySeparateCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ renderer.Pipeline.SetTextureArraySeparate(command._stage, command._setIndex, command._array.GetAs(threaded)?.Base);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs
new file mode 100644
index 000000000..fec1c48f0
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArrayDisposeCommand.cs
@@ -0,0 +1,21 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
+{
+ struct TextureArrayDisposeCommand : IGALCommand, IGALCommand
+ {
+ public readonly CommandType CommandType => CommandType.TextureArrayDispose;
+ private TableRef _textureArray;
+
+ public void Set(TableRef textureArray)
+ {
+ _textureArray = textureArray;
+ }
+
+ public static void Run(ref TextureArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
+ {
+ command._textureArray.Get(threaded).Base.Dispose();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs
similarity index 100%
rename from src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetSamplersCommand.cs
rename to src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetSamplersCommand.cs
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs
similarity index 100%
rename from src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureAndSamplerArray/TextureArraySetTexturesCommand.cs
rename to src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureArray/TextureArraySetTexturesCommand.cs
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
index d26ee1fbd..19bc6f233 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedImageArray.cs
@@ -21,6 +21,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef(_renderer, reference);
}
+ public void Dispose()
+ {
+ _renderer.New().Set(Ref(this));
+ _renderer.QueueCommand();
+ }
+
public void SetFormats(int index, Format[] imageFormats)
{
_renderer.New().Set(Ref(this), index, Ref(imageFormats));
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
index 82405a1f6..4334c7048 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTextureArray.cs
@@ -22,6 +22,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
return new TableRef(_renderer, reference);
}
+ public void Dispose()
+ {
+ _renderer.New().Set(Ref(this));
+ _renderer.QueueCommand();
+ }
+
public void SetSamplers(int index, ISampler[] samplers)
{
_renderer.New().Set(Ref(this), index, Ref(samplers.ToArray()));
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
index 697894eb5..edd79d8a0 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs
@@ -189,6 +189,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
+ public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
+ {
+ _renderer.New().Set(stage, setIndex, Ref(array));
+ _renderer.QueueCommand();
+ }
+
public void SetIndexBuffer(BufferRange buffer, IndexType type)
{
_renderer.New().Set(buffer, type);
@@ -297,6 +303,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
+ public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
+ {
+ _renderer.New().Set(stage, setIndex, Ref(array));
+ _renderer.QueueCommand();
+ }
+
public void SetTransformFeedbackBuffers(ReadOnlySpan buffers)
{
_renderer.New().Set(_renderer.CopySpan(buffers));
diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
index 5e17bcd2c..cc3d2e5c1 100644
--- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
+++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs
@@ -272,15 +272,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return handle;
}
- public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint)
- {
- BufferHandle handle = Buffers.CreateBufferHandle();
- New().Set(handle, size, access, storageHint);
- QueueCommand();
-
- return handle;
- }
-
public BufferHandle CreateBuffer(nint pointer, int size)
{
BufferHandle handle = Buffers.CreateBufferHandle();
diff --git a/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs b/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs
new file mode 100644
index 000000000..532921298
--- /dev/null
+++ b/src/Ryujinx.Graphics.GAL/SystemMemoryType.cs
@@ -0,0 +1,29 @@
+namespace Ryujinx.Graphics.GAL
+{
+ public enum SystemMemoryType
+ {
+ ///
+ /// The backend manages the ownership of memory. This mode never supports host imported memory.
+ ///
+ BackendManaged,
+
+ ///
+ /// Device memory has similar performance to host memory, usually because it's shared between CPU/GPU.
+ /// Use host memory whenever possible.
+ ///
+ UnifiedMemory,
+
+ ///
+ /// GPU storage to host memory goes though a slow interconnect, but it would still be preferable to use it if the data is flushed back often.
+ /// Assumes constant buffer access to host memory is rather fast.
+ ///
+ DedicatedMemory,
+
+ ///
+ /// GPU storage to host memory goes though a slow interconnect, that is very slow when doing access from storage.
+ /// When frequently accessed, copy buffers to host memory using DMA.
+ /// Assumes constant buffer access to host memory is rather fast.
+ ///
+ DedicatedMemorySlowStorage
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
index 7f3772f44..475d1ee4e 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
@@ -5,6 +5,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Types;
+using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
@@ -495,8 +496,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
- MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize);
- MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4);
+ MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
+ MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
_processor.ThreedClass.DrawIndirect(
topology,
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs
index f9cb40b0d..6de50fb2e 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeContext.cs
@@ -438,7 +438,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
ReadOnlySpan dataBytes = MemoryMarshal.Cast(data);
- BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length);
+ BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length, BufferAccess.DeviceMemory);
_context.Renderer.SetBufferData(buffer, 0, dataBytes);
return new IndexBuffer(buffer, count, dataBytes.Length);
@@ -529,7 +529,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{
if (_dummyBuffer == BufferHandle.Null)
{
- _dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize);
+ _dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory);
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
}
@@ -550,7 +550,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.DeleteBuffer(_sequentialIndexBuffer);
}
- _sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint));
+ _sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint), BufferAccess.DeviceMemory);
_sequentialIndexBufferCount = count;
Span data = new int[count];
@@ -583,7 +583,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
_context.Renderer.DeleteBuffer(buffer.Handle);
}
- buffer.Handle = _context.Renderer.CreateBuffer(newSize);
+ buffer.Handle = _context.Renderer.CreateBuffer(newSize, BufferAccess.DeviceMemory);
buffer.Size = newSize;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
index 6324e6a15..73682866b 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/ComputeDraw/VtgAsComputeState.cs
@@ -3,6 +3,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
@@ -370,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
{
var memoryManager = _channel.MemoryManager;
- BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size));
+ BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address, size), BufferStage.VertexBuffer);
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
bufferTexture.SetStorage(range);
@@ -412,7 +413,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
var memoryManager = _channel.MemoryManager;
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
- BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign));
+ BufferRange range = memoryManager.Physical.BufferCache.GetBufferRange(
+ memoryManager.GetPhysicalRegions(address + indexOffset - misalign, size + misalign),
+ BufferStage.IndexBuffer);
misalignedOffset = (int)misalign >> shift;
SetIndexBufferTexture(reservations, range, format);
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
index d8de14de0..56ef64c6e 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
@@ -684,8 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (hasCount)
{
- var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange);
- var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange);
+ var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
+ var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
if (indexed)
{
@@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
}
else
{
- var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange);
+ var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
if (indexed)
{
diff --git a/src/Ryujinx.Graphics.Gpu/GpuContext.cs b/src/Ryujinx.Graphics.Gpu/GpuContext.cs
index 53ea8cb27..048d32fb7 100644
--- a/src/Ryujinx.Graphics.Gpu/GpuContext.cs
+++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs
@@ -393,17 +393,18 @@ namespace Ryujinx.Graphics.Gpu
if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
{
- Renderer.CreateSync(SyncNumber, strict);
-
foreach (var action in SyncActions)
{
action.SyncPreAction(syncpoint);
}
+
foreach (var action in SyncpointActions)
{
action.SyncPreAction(syncpoint);
}
+ Renderer.CreateSync(SyncNumber, strict);
+
SyncNumber++;
SyncActions.RemoveAll(action => action.SyncAction(syncpoint));
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
index ba895c60a..31abc21e8 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
@@ -19,6 +19,11 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public Format Format { get; }
+ ///
+ /// Shader texture host set index.
+ ///
+ public int Set { get; }
+
///
/// Shader texture host binding point.
///
@@ -54,15 +59,17 @@ namespace Ryujinx.Graphics.Gpu.Image
///
/// The shader sampler target type
/// Format of the image as declared on the shader
+ /// Shader texture host set index
/// The shader texture binding point
/// For array of textures, this indicates the length of the array. A value of one indicates it is not an array
/// Constant buffer slot where the texture handle is located
/// The shader texture handle (read index into the texture constant buffer)
/// The texture's usage flags, indicating how it is used in the shader
- public TextureBindingInfo(Target target, Format format, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
+ public TextureBindingInfo(Target target, Format format, int set, int binding, int arrayLength, int cbufSlot, int handle, TextureUsageFlags flags)
{
Target = target;
Format = format;
+ Set = set;
Binding = binding;
ArrayLength = arrayLength;
CbufSlot = cbufSlot;
@@ -74,6 +81,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Constructs the texture binding information structure.
///
/// The shader sampler target type
+ /// Shader texture host set index
/// The shader texture binding point
/// For array of textures, this indicates the length of the array. A value of one indicates it is not an array
/// Constant buffer slot where the texture handle is located
@@ -82,12 +90,13 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Indicates that the binding is for a sampler
public TextureBindingInfo(
Target target,
+ int set,
int binding,
int arrayLength,
int cbufSlot,
int handle,
TextureUsageFlags flags,
- bool isSamplerOnly) : this(target, 0, binding, arrayLength, cbufSlot, handle, flags)
+ bool isSamplerOnly) : this(target, 0, set, binding, arrayLength, cbufSlot, handle, flags)
{
IsSamplerOnly = isSamplerOnly;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
index 7e486e0a8..01e34c777 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureBindingsArrayCache.cs
@@ -566,7 +566,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int stageIndex,
int textureBufferIndex,
SamplerIndex samplerIndex,
- TextureBindingInfo bindingInfo)
+ in TextureBindingInfo bindingInfo)
{
Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo);
}
@@ -579,7 +579,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Shader stage index where the array is used
/// Texture constant buffer index
/// Array binding information
- public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, TextureBindingInfo bindingInfo)
+ public void UpdateImageArray(TexturePool texturePool, ShaderStage stage, int stageIndex, int textureBufferIndex, in TextureBindingInfo bindingInfo)
{
Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo);
}
@@ -603,7 +603,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int textureBufferIndex,
bool isImage,
SamplerIndex samplerIndex,
- TextureBindingInfo bindingInfo)
+ in TextureBindingInfo bindingInfo)
{
if (IsDirectHandleType(bindingInfo.Handle))
{
@@ -623,7 +623,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// Shader stage where the array is used
/// Whether the array is a image or texture array
/// Array binding information
- private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, TextureBindingInfo bindingInfo)
+ private void UpdateFromPool(TexturePool texturePool, SamplerPool samplerPool, ShaderStage stage, bool isImage, in TextureBindingInfo bindingInfo)
{
CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry);
@@ -638,11 +638,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage)
{
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
return;
@@ -708,11 +708,11 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format;
}
- _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
}
else
{
- _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
}
}
else if (isImage)
@@ -737,14 +737,14 @@ namespace Ryujinx.Graphics.Gpu.Image
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures);
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
}
@@ -767,7 +767,7 @@ namespace Ryujinx.Graphics.Gpu.Image
int textureBufferIndex,
bool isImage,
SamplerIndex samplerIndex,
- TextureBindingInfo bindingInfo)
+ in TextureBindingInfo bindingInfo)
{
(textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
@@ -800,11 +800,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage)
{
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
return;
@@ -829,11 +829,11 @@ namespace Ryujinx.Graphics.Gpu.Image
if (isImage)
{
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
}
return;
@@ -921,11 +921,11 @@ namespace Ryujinx.Graphics.Gpu.Image
format = texture.Format;
}
- _channel.BufferManager.SetBufferTextureStorage(entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.ImageArray, hostTexture, texture.Range, bindingInfo, index, format);
}
else
{
- _channel.BufferManager.SetBufferTextureStorage(entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
+ _channel.BufferManager.SetBufferTextureStorage(stage, entry.TextureArray, hostTexture, texture.Range, bindingInfo, index, format);
}
}
else if (isImage)
@@ -950,14 +950,50 @@ namespace Ryujinx.Graphics.Gpu.Image
entry.ImageArray.SetFormats(0, formats);
entry.ImageArray.SetImages(0, textures);
- _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
+ SetImageArray(stage, bindingInfo, entry.ImageArray);
}
else
{
entry.TextureArray.SetSamplers(0, samplers);
entry.TextureArray.SetTextures(0, textures);
- _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
+ SetTextureArray(stage, bindingInfo, entry.TextureArray);
+ }
+ }
+
+ ///
+ /// Updates a texture array binding on the host.
+ ///
+ /// Shader stage where the array is used
+ /// Array binding information
+ /// Texture array
+ private void SetTextureArray(ShaderStage stage, in TextureBindingInfo bindingInfo, ITextureArray array)
+ {
+ if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0)
+ {
+ _context.Renderer.Pipeline.SetTextureArraySeparate(stage, bindingInfo.Set, array);
+ }
+ else
+ {
+ _context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, array);
+ }
+ }
+
+ ///
+ /// Updates a image array binding on the host.
+ ///
+ /// Shader stage where the array is used
+ /// Array binding information
+ /// Image array
+ private void SetImageArray(ShaderStage stage, in TextureBindingInfo bindingInfo, IImageArray array)
+ {
+ if (bindingInfo.Set >= _context.Capabilities.ExtraSetBaseIndex && _context.Capabilities.MaximumExtraSets != 0)
+ {
+ _context.Renderer.Pipeline.SetImageArraySeparate(stage, bindingInfo.Set, array);
+ }
+ else
+ {
+ _context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, array);
}
}
@@ -973,7 +1009,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private CacheEntry GetOrAddEntry(
TexturePool texturePool,
SamplerPool samplerPool,
- TextureBindingInfo bindingInfo,
+ in TextureBindingInfo bindingInfo,
bool isImage,
out bool isNew)
{
@@ -1015,7 +1051,7 @@ namespace Ryujinx.Graphics.Gpu.Image
private CacheEntryFromBuffer GetOrAddEntry(
TexturePool texturePool,
SamplerPool samplerPool,
- TextureBindingInfo bindingInfo,
+ in TextureBindingInfo bindingInfo,
bool isImage,
ref BufferBounds textureBufferBounds,
out bool isNew)
@@ -1077,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();
+ }
}
}
@@ -1088,11 +1133,20 @@ namespace Ryujinx.Graphics.Gpu.Image
{
List keysToRemove = null;
- foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys)
+ foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool)
{
if (key.MatchesPool(pool))
{
(keysToRemove ??= new()).Add(key);
+
+ if (key.IsImage)
+ {
+ entry.ImageArray.Dispose();
+ }
+ else
+ {
+ entry.TextureArray.Dispose();
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index 4e1133d1a..06ca2c599 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -645,7 +645,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
else
{
- _flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.FlushPersistent);
+ _flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.HostMemory);
_flushBufferImported = false;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
index d293060b5..e060e0b4f 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -10,6 +10,8 @@ using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
{
+ delegate void BufferFlushAction(ulong address, ulong size, ulong syncNumber);
+
///
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
///
@@ -23,7 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Host buffer handle.
///
- public BufferHandle Handle { get; }
+ public BufferHandle Handle { get; private set; }
///
/// Start address of the buffer in guest memory.
@@ -60,6 +62,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
private BufferModifiedRangeList _modifiedRanges = null;
+ ///
+ /// A structure that is used to flush buffer data back to a host mapped buffer for cached readback.
+ /// Only used if the buffer data is explicitly owned by device local memory.
+ ///
+ private BufferPreFlush _preFlush = null;
+
+ ///
+ /// Usage tracking state that determines what type of backing the buffer should use.
+ ///
+ public BufferBackingState BackingState;
+
private readonly MultiRegionHandle _memoryTrackingGranular;
private readonly RegionHandle _memoryTracking;
@@ -87,6 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Physical memory where the buffer is mapped
/// Start address of the buffer
/// Size of the buffer in bytes
+ /// The type of usage that created the buffer
/// Indicates if the buffer can be used in a sparse buffer mapping
/// Buffers which this buffer contains, and will inherit tracking handles from
public Buffer(
@@ -94,6 +108,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
PhysicalMemory physicalMemory,
ulong address,
ulong size,
+ BufferStage stage,
bool sparseCompatible,
IEnumerable baseBuffers = null)
{
@@ -103,9 +118,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
Size = size;
SparseCompatible = sparseCompatible;
- BufferAccess access = sparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default;
+ BackingState = new BufferBackingState(_context, this, stage, baseBuffers);
- Handle = context.Renderer.CreateBuffer((int)size, access, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
+ BufferAccess access = BackingState.SwitchAccess(this);
+
+ Handle = context.Renderer.CreateBuffer((int)size, access);
_useGranular = size > GranularBufferThreshold;
@@ -161,6 +178,29 @@ namespace Ryujinx.Graphics.Gpu.Memory
_virtualDependenciesLock = new ReaderWriterLockSlim();
}
+ ///
+ /// Recreates the backing buffer based on the desired access type
+ /// reported by the backing state struct.
+ ///
+ private void ChangeBacking()
+ {
+ BufferAccess access = BackingState.SwitchAccess(this);
+
+ BufferHandle newHandle = _context.Renderer.CreateBuffer((int)Size, access);
+
+ _context.Renderer.Pipeline.CopyBuffer(Handle, newHandle, 0, 0, (int)Size);
+
+ _modifiedRanges?.SelfMigration();
+
+ // If swtiching from device local to host mapped, pre-flushing data no longer makes sense.
+ // This is set to null and disposed when the migration fully completes.
+ _preFlush = null;
+
+ Handle = newHandle;
+
+ _physicalMemory.BufferCache.BufferBackingChanged(this);
+ }
+
///
/// Gets a sub-range from the buffer, from a start address til a page boundary after the given size.
///
@@ -246,6 +286,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
+ BackingState.RecordSet();
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
CopyToDependantVirtualBuffers();
}
@@ -283,15 +324,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush);
}
+ ///
+ /// Checks if a backing change is deemed necessary from the given usage.
+ /// If it is, queues a backing change to happen on the next sync action.
+ ///
+ /// Buffer stage that can change backing type
+ private void TryQueueBackingChange(BufferStage stage)
+ {
+ if (BackingState.ShouldChangeBacking(stage))
+ {
+ if (!_syncActionRegistered)
+ {
+ _context.RegisterSyncAction(this);
+ _syncActionRegistered = true;
+ }
+ }
+ }
+
///
/// Signal that the given region of the buffer has been modified.
///
/// The start address of the modified region
/// The size of the modified region
- public void SignalModified(ulong address, ulong size)
+ /// Buffer stage that triggered the modification
+ public void SignalModified(ulong address, ulong size, BufferStage stage)
{
EnsureRangeList();
+ TryQueueBackingChange(stage);
+
_modifiedRanges.SignalModified(address, size);
if (!_syncActionRegistered)
@@ -311,6 +372,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.Clear(address, size);
}
+ ///
+ /// Action to be performed immediately before sync is created.
+ /// This will copy any buffer ranges designated for pre-flushing.
+ ///
+ /// True if the action is a guest syncpoint
+ public void SyncPreAction(bool syncpoint)
+ {
+ if (_referenceCount == 0)
+ {
+ return;
+ }
+
+ if (BackingState.ShouldChangeBacking())
+ {
+ ChangeBacking();
+ }
+
+ if (BackingState.IsDeviceLocal)
+ {
+ _preFlush ??= new BufferPreFlush(_context, this, FlushImpl);
+
+ if (_preFlush.ShouldCopy)
+ {
+ _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, (address, size) =>
+ {
+ _preFlush.CopyModified(address, size);
+ });
+ }
+ }
+ }
+
///
/// Action to be performed when a syncpoint is reached after modification.
/// This will register read/write tracking to flush the buffer from GPU when its memory is used.
@@ -466,6 +558,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size of the modified region
private void LoadRegion(ulong mAddress, ulong mSize)
{
+ BackingState.RecordSet();
+
int offset = (int)(mAddress - Address);
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
@@ -539,18 +633,84 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Flushes a range of the buffer.
/// This writes the range data back into guest memory.
///
+ /// Buffer handle to flush data from
/// Start address of the range
/// Size in bytes of the range
- public void Flush(ulong address, ulong size)
+ private void FlushImpl(BufferHandle handle, ulong address, ulong size)
{
int offset = (int)(address - Address);
- using PinnedSpan data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
+ using PinnedSpan data = _context.Renderer.GetBufferData(handle, offset, (int)size);
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
}
+ ///
+ /// Flushes a range of the buffer.
+ /// This writes the range data back into guest memory.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ private void FlushImpl(ulong address, ulong size)
+ {
+ FlushImpl(Handle, address, size);
+ }
+
+ ///
+ /// Flushes a range of the buffer from the most optimal source.
+ /// This writes the range data back into guest memory.
+ ///
+ /// Start address of the range
+ /// Size in bytes of the range
+ /// Sync number waited for before flushing the data
+ public void Flush(ulong address, ulong size, ulong syncNumber)
+ {
+ BackingState.RecordFlush();
+
+ BufferPreFlush preFlush = _preFlush;
+
+ if (preFlush != null)
+ {
+ preFlush.FlushWithAction(address, size, syncNumber);
+ }
+ else
+ {
+ FlushImpl(address, size);
+ }
+ }
+ ///
+ /// Gets an action that disposes the backing buffer using its current handle.
+ /// Useful for deleting an old copy of the buffer after the handle changes.
+ ///
+ /// An action that flushes data from the specified range, using the buffer handle at the time the method is generated
+ public Action GetSnapshotDisposeAction()
+ {
+ BufferHandle handle = Handle;
+ BufferPreFlush preFlush = _preFlush;
+
+ return () =>
+ {
+ _context.Renderer.DeleteBuffer(handle);
+ preFlush?.Dispose();
+ };
+ }
+
+ ///
+ /// Gets an action that flushes a range of the buffer using its current handle.
+ /// Useful for flushing data from old copies of the buffer after the handle changes.
+ ///
+ /// An action that flushes data from the specified range, using the buffer handle at the time the method is generated
+ public BufferFlushAction GetSnapshotFlushAction()
+ {
+ BufferHandle handle = Handle;
+
+ return (ulong address, ulong size, ulong _) =>
+ {
+ FlushImpl(handle, address, size);
+ };
+ }
+
///
/// Align a given address and size region to page boundaries.
///
@@ -857,6 +1017,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
_modifiedRanges?.Clear();
_context.Renderer.DeleteBuffer(Handle);
+ _preFlush?.Dispose();
+ _preFlush = null;
UnmappedSequence++;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
new file mode 100644
index 000000000..3f65131e6
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
@@ -0,0 +1,294 @@
+using Ryujinx.Graphics.GAL;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Type of backing memory.
+ /// In ascending order of priority when merging multiple buffer backing states.
+ ///
+ internal enum BufferBackingType
+ {
+ HostMemory,
+ DeviceMemory,
+ DeviceMemoryWithFlush
+ }
+
+ ///
+ /// Keeps track of buffer usage to decide what memory heap that buffer memory is placed on.
+ /// Dedicated GPUs prefer certain types of resources to be device local,
+ /// and if we need data to be read back, we might prefer that they're in host memory.
+ ///
+ /// The measurements recorded here compare to a set of heruristics (thresholds and conditions)
+ /// that appear to produce good performance in most software.
+ ///
+ internal struct BufferBackingState
+ {
+ private const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
+
+ private const int SetCountThreshold = 100;
+ private const int WriteCountThreshold = 50;
+ private const int FlushCountThreshold = 5;
+ private const int DeviceLocalForceExpiry = 100;
+
+ public readonly bool IsDeviceLocal => _activeType != BufferBackingType.HostMemory;
+
+ private readonly SystemMemoryType _systemMemoryType;
+ private BufferBackingType _activeType;
+ private BufferBackingType _desiredType;
+
+ private bool _canSwap;
+
+ private int _setCount;
+ private int _writeCount;
+ private int _flushCount;
+ private int _flushTemp;
+ private int _lastFlushWrite;
+ private int _deviceLocalForceCount;
+
+ private readonly int _size;
+
+ ///
+ /// Initialize the buffer backing state for a given parent buffer.
+ ///
+ /// GPU context
+ /// Parent buffer
+ /// Initial buffer stage
+ /// Buffers to inherit state from
+ public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, IEnumerable baseBuffers = null)
+ {
+ _size = (int)parent.Size;
+ _systemMemoryType = context.Capabilities.MemoryType;
+
+ // Backend managed is always auto, unified memory is always host.
+ _desiredType = BufferBackingType.HostMemory;
+ _canSwap = _systemMemoryType != SystemMemoryType.BackendManaged && _systemMemoryType != SystemMemoryType.UnifiedMemory;
+
+ if (_canSwap)
+ {
+ // Might want to start certain buffers as being device local,
+ // and the usage might also lock those buffers into being device local.
+
+ BufferStage storageFlags = stage & BufferStage.StorageMask;
+
+ if (parent.Size > DeviceLocalSizeThreshold && baseBuffers == null)
+ {
+ _desiredType = BufferBackingType.DeviceMemory;
+ }
+
+ if (storageFlags != 0)
+ {
+ // Storage buffer bindings may require special treatment.
+
+ var rawStage = stage & BufferStage.StageMask;
+
+ if (rawStage == BufferStage.Fragment)
+ {
+ // Fragment read should start device local.
+
+ _desiredType = BufferBackingType.DeviceMemory;
+
+ if (storageFlags != BufferStage.StorageRead)
+ {
+ // Fragment write should stay device local until the use doesn't happen anymore.
+
+ _deviceLocalForceCount = DeviceLocalForceExpiry;
+ }
+ }
+
+ // TODO: Might be nice to force atomic access to be device local for any stage.
+ }
+
+ if (baseBuffers != null)
+ {
+ foreach (Buffer buffer in baseBuffers)
+ {
+ CombineState(buffer.BackingState);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Combine buffer backing types, selecting the one with highest priority.
+ ///
+ /// First buffer backing type
+ /// Second buffer backing type
+ /// Combined buffer backing type
+ private static BufferBackingType CombineTypes(BufferBackingType left, BufferBackingType right)
+ {
+ return (BufferBackingType)Math.Max((int)left, (int)right);
+ }
+
+ ///
+ /// Combine the state from the given buffer backing state with this one,
+ /// so that the state isn't lost when migrating buffers.
+ ///
+ /// Buffer state to combine into this state
+ private void CombineState(BufferBackingState oldState)
+ {
+ _setCount += oldState._setCount;
+ _writeCount += oldState._writeCount;
+ _flushCount += oldState._flushCount;
+ _flushTemp += oldState._flushTemp;
+ _lastFlushWrite = -1;
+ _deviceLocalForceCount = Math.Max(_deviceLocalForceCount, oldState._deviceLocalForceCount);
+
+ _canSwap &= oldState._canSwap;
+
+ _desiredType = CombineTypes(_desiredType, oldState._desiredType);
+ }
+
+ ///
+ /// Get the buffer access for the desired backing type, and record that type as now being active.
+ ///
+ /// Parent buffer
+ /// Buffer access
+ public BufferAccess SwitchAccess(Buffer parent)
+ {
+ BufferAccess access = parent.SparseCompatible ? BufferAccess.SparseCompatible : BufferAccess.Default;
+
+ bool isBackendManaged = _systemMemoryType == SystemMemoryType.BackendManaged;
+
+ if (!isBackendManaged)
+ {
+ switch (_desiredType)
+ {
+ case BufferBackingType.HostMemory:
+ access |= BufferAccess.HostMemory;
+ break;
+ case BufferBackingType.DeviceMemory:
+ access |= BufferAccess.DeviceMemory;
+ break;
+ case BufferBackingType.DeviceMemoryWithFlush:
+ access |= BufferAccess.DeviceMemoryMapped;
+ break;
+ }
+ }
+
+ _activeType = _desiredType;
+
+ return access;
+ }
+
+ ///
+ /// Record when data has been uploaded to the buffer.
+ ///
+ public void RecordSet()
+ {
+ _setCount++;
+
+ ConsiderUseCounts();
+ }
+
+ ///
+ /// Record when data has been flushed from the buffer.
+ ///
+ public void RecordFlush()
+ {
+ if (_lastFlushWrite != _writeCount)
+ {
+ // If it's on the same page as the last flush, ignore it.
+ _lastFlushWrite = _writeCount;
+ _flushCount++;
+ }
+ }
+
+ ///
+ /// Determine if the buffer backing should be changed.
+ ///
+ /// True if the desired backing type is different from the current type
+ public readonly bool ShouldChangeBacking()
+ {
+ return _desiredType != _activeType;
+ }
+
+ ///
+ /// Determine if the buffer backing should be changed, considering a new use with the given buffer stage.
+ ///
+ /// Buffer stage for the use
+ /// True if the desired backing type is different from the current type
+ public bool ShouldChangeBacking(BufferStage stage)
+ {
+ if (!_canSwap)
+ {
+ return false;
+ }
+
+ BufferStage storageFlags = stage & BufferStage.StorageMask;
+
+ if (storageFlags != 0)
+ {
+ if (storageFlags != BufferStage.StorageRead)
+ {
+ // Storage write.
+ _writeCount++;
+
+ var rawStage = stage & BufferStage.StageMask;
+
+ if (rawStage == BufferStage.Fragment)
+ {
+ // Switch to device memory, swap back only if this use disappears.
+
+ _desiredType = CombineTypes(_desiredType, BufferBackingType.DeviceMemory);
+ _deviceLocalForceCount = DeviceLocalForceExpiry;
+
+ // TODO: Might be nice to force atomic access to be device local for any stage.
+ }
+ }
+
+ ConsiderUseCounts();
+ }
+
+ return _desiredType != _activeType;
+ }
+
+ ///
+ /// Evaluate the current counts to determine what the buffer's desired backing type is.
+ /// This method depends on heuristics devised by testing a variety of software.
+ ///
+ private void ConsiderUseCounts()
+ {
+ if (_canSwap)
+ {
+ if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
+ {
+ if (_deviceLocalForceCount > 0 && --_deviceLocalForceCount != 0)
+ {
+ // Some buffer usage demanded that the buffer stay device local.
+ // The desired type was selected when this counter was set.
+ }
+ else if (_flushCount > 0 || _flushTemp-- > 0)
+ {
+ // Buffers that flush should ideally be mapped in host address space for easy copies.
+ // If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
+ // If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
+ _desiredType = _size > DeviceLocalSizeThreshold ? BufferBackingType.DeviceMemoryWithFlush : BufferBackingType.HostMemory;
+ }
+ else if (_writeCount >= WriteCountThreshold)
+ {
+ // Buffers that are written often should ideally be in the device local heap. (Storage buffers)
+ _desiredType = BufferBackingType.DeviceMemory;
+ }
+ else if (_setCount > SetCountThreshold)
+ {
+ // Buffers that have their data set often should ideally be host mapped. (Constant buffers)
+ _desiredType = BufferBackingType.HostMemory;
+ }
+
+ // It's harder for a buffer that is flushed to revert to another type of mapping.
+ if (_flushCount > 0)
+ {
+ _flushTemp = 1000;
+ }
+
+ _lastFlushWrite = -1;
+ _flushCount = 0;
+ _writeCount = 0;
+ _setCount = 0;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
index c6284780d..66d2cdb62 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
@@ -107,8 +107,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// GPU memory manager where the buffer is mapped
/// Start GPU virtual address of the buffer
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Contiguous physical range of the buffer, after address translation
- public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size)
+ public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{
if (gpuVa == 0)
{
@@ -119,7 +120,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (address != MemoryManager.PteUnmapped)
{
- CreateBuffer(address, size);
+ CreateBuffer(address, size, stage);
}
return new MultiRange(address, size);
@@ -132,8 +133,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// GPU memory manager where the buffer is mapped
/// Start GPU virtual address of the buffer
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Physical ranges of the buffer, after address translation
- public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size)
+ public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{
if (gpuVa == 0)
{
@@ -149,7 +151,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
return range;
}
- CreateBuffer(range);
+ CreateBuffer(range, stage);
return range;
}
@@ -161,8 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// GPU memory manager where the buffer is mapped
/// Start GPU virtual address of the buffer
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Physical ranges of the buffer, after address translation
- public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size)
+ public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
{
if (gpuVa == 0)
{
@@ -186,11 +189,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (range.Count > 1)
{
- CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
+ CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
}
else
{
- CreateBuffer(subRange.Address, subRange.Size);
+ CreateBuffer(subRange.Address, subRange.Size, stage);
}
}
}
@@ -203,11 +206,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// This can be used to ensure the existance of a buffer.
///
/// Physical ranges of memory where the buffer data is located
- public void CreateBuffer(MultiRange range)
+ /// The type of usage that created the buffer
+ public void CreateBuffer(MultiRange range, BufferStage stage)
{
if (range.Count > 1)
{
- CreateMultiRangeBuffer(range);
+ CreateMultiRangeBuffer(range, stage);
}
else
{
@@ -215,7 +219,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped)
{
- CreateBuffer(subRange.Address, subRange.Size);
+ CreateBuffer(subRange.Address, subRange.Size, stage);
}
}
}
@@ -226,7 +230,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in memory
/// Size of the buffer in bytes
- public void CreateBuffer(ulong address, ulong size)
+ /// The type of usage that created the buffer
+ public void CreateBuffer(ulong address, ulong size, BufferStage stage)
{
ulong endAddress = address + size;
@@ -239,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
alignedEndAddress += BufferAlignmentSize;
}
- CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress);
+ CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage);
}
///
@@ -248,8 +253,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in memory
/// Size of the buffer in bytes
+ /// The type of usage that created the buffer
/// Alignment of the start address of the buffer in bytes
- public void CreateBuffer(ulong address, ulong size, ulong alignment)
+ public void CreateBuffer(ulong address, ulong size, BufferStage stage, ulong alignment)
{
ulong alignmentMask = alignment - 1;
ulong pageAlignmentMask = BufferAlignmentMask;
@@ -264,7 +270,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
alignedEndAddress += pageAlignmentMask;
}
- CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, alignment);
+ CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage, alignment);
}
///
@@ -272,7 +278,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// if it does not exist yet.
///
/// Physical ranges of memory
- private void CreateMultiRangeBuffer(MultiRange range)
+ /// The type of usage that created the buffer
+ private void CreateMultiRangeBuffer(MultiRange range, BufferStage stage)
{
// Ensure all non-contiguous buffer we might use are sparse aligned.
for (int i = 0; i < range.Count; i++)
@@ -281,7 +288,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (subRange.Address != MemoryManager.PteUnmapped)
{
- CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
+ CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
}
}
@@ -431,9 +438,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
result.EndGpuAddress < gpuVa + size ||
result.UnmappedSequence != result.Buffer.UnmappedSequence)
{
- MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size);
+ MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size, BufferStage.Internal);
ulong address = range.GetSubRange(0).Address;
- result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size));
+ result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size, BufferStage.Internal));
_dirtyCache[gpuVa] = result;
}
@@ -466,9 +473,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
result.EndGpuAddress < alignedEndGpuVa ||
result.UnmappedSequence != result.Buffer.UnmappedSequence)
{
- MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size);
+ MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size, BufferStage.None);
ulong address = range.GetSubRange(0).Address;
- result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size));
+ result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size, BufferStage.None));
_modifiedCache[alignedGpuVa] = result;
}
@@ -485,7 +492,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in guest memory
/// Size in bytes of the buffer
- private void CreateBufferAligned(ulong address, ulong size)
+ /// The type of usage that created the buffer
+ private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{
Buffer[] overlaps = _bufferOverlaps;
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
@@ -546,13 +554,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address;
- CreateBufferAligned(address, newSize, anySparseCompatible, overlaps, overlapsCount);
+ CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps, overlapsCount);
}
}
else
{
// No overlap, just create a new buffer.
- Buffer buffer = new(_context, _physicalMemory, address, size, sparseCompatible: false);
+ Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false);
lock (_buffers)
{
@@ -570,8 +578,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in guest memory
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Alignment of the start address of the buffer
- private void CreateBufferAligned(ulong address, ulong size, ulong alignment)
+ private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{
Buffer[] overlaps = _bufferOverlaps;
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
@@ -624,13 +633,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong newSize = endAddress - address;
- CreateBufferAligned(address, newSize, sparseAligned, overlaps, overlapsCount);
+ CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps, overlapsCount);
}
}
else
{
// No overlap, just create a new buffer.
- Buffer buffer = new(_context, _physicalMemory, address, size, sparseAligned);
+ Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned);
lock (_buffers)
{
@@ -648,12 +657,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Address of the buffer in guest memory
/// Size in bytes of the buffer
+ /// The type of usage that created the buffer
/// Indicates if the buffer can be used in a sparse buffer mapping
/// Buffers overlapping the range
/// Total of overlaps
- private void CreateBufferAligned(ulong address, ulong size, bool sparseCompatible, Buffer[] overlaps, int overlapsCount)
+ private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps, int overlapsCount)
{
- Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, sparseCompatible, overlaps.Take(overlapsCount));
+ Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps.Take(overlapsCount));
lock (_buffers)
{
@@ -704,7 +714,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int index = 0; index < overlapCount; index++)
{
- CreateMultiRangeBuffer(overlaps[index].Range);
+ CreateMultiRangeBuffer(overlaps[index].Range, BufferStage.None);
}
}
@@ -731,8 +741,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the copy
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
{
- MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size);
- MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size);
+ MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
+ MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
if (srcRange.Count == 1 && dstRange.Count == 1)
{
@@ -788,8 +798,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the copy
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
{
- Buffer srcBuffer = GetBuffer(srcAddress, size);
- Buffer dstBuffer = GetBuffer(dstAddress, size);
+ Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
+ Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy);
int srcOffset = (int)(srcAddress - srcBuffer.Address);
int dstOffset = (int)(dstAddress - dstBuffer.Address);
@@ -803,7 +813,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (srcBuffer.IsModified(srcAddress, size))
{
- dstBuffer.SignalModified(dstAddress, size);
+ dstBuffer.SignalModified(dstAddress, size, BufferStage.Copy);
}
else
{
@@ -828,12 +838,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Value to be written into the buffer
public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
{
- MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size);
+ MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size, BufferStage.Copy);
for (int index = 0; index < range.Count; index++)
{
MemoryRange subRange = range.GetSubRange(index);
- Buffer buffer = GetBuffer(subRange.Address, subRange.Size);
+ Buffer buffer = GetBuffer(subRange.Address, subRange.Size, BufferStage.Copy);
int offset = (int)(subRange.Address - buffer.Address);
@@ -849,18 +859,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary.
///
/// Physical regions of memory where the buffer is mapped
+ /// Buffer stage that triggered the access
/// Whether the buffer will be written to by this use
/// The buffer sub-range starting at the given memory address
- public BufferRange GetBufferRangeAligned(MultiRange range, bool write = false)
+ public BufferRange GetBufferRangeAligned(MultiRange range, BufferStage stage, bool write = false)
{
if (range.Count > 1)
{
- return GetBuffer(range, write).GetRange(range);
+ return GetBuffer(range, stage, write).GetRange(range);
}
else
{
MemoryRange subRange = range.GetSubRange(0);
- return GetBuffer(subRange.Address, subRange.Size, write).GetRangeAligned(subRange.Address, subRange.Size, write);
+ return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRangeAligned(subRange.Address, subRange.Size, write);
}
}
@@ -868,18 +879,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Gets a buffer sub-range for a given memory range.
///
/// Physical regions of memory where the buffer is mapped
+ /// Buffer stage that triggered the access
/// Whether the buffer will be written to by this use
/// The buffer sub-range for the given range
- public BufferRange GetBufferRange(MultiRange range, bool write = false)
+ public BufferRange GetBufferRange(MultiRange range, BufferStage stage, bool write = false)
{
if (range.Count > 1)
{
- return GetBuffer(range, write).GetRange(range);
+ return GetBuffer(range, stage, write).GetRange(range);
}
else
{
MemoryRange subRange = range.GetSubRange(0);
- return GetBuffer(subRange.Address, subRange.Size, write).GetRange(subRange.Address, subRange.Size, write);
+ return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRange(subRange.Address, subRange.Size, write);
}
}
@@ -888,9 +900,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// A buffer overlapping with the specified range is assumed to already exist on the cache.
///
/// Physical regions of memory where the buffer is mapped
+ /// Buffer stage that triggered the access
/// Whether the buffer will be written to by this use
/// The buffer where the range is fully contained
- private MultiRangeBuffer GetBuffer(MultiRange range, bool write = false)
+ private MultiRangeBuffer GetBuffer(MultiRange range, BufferStage stage, bool write = false)
{
for (int i = 0; i < range.Count; i++)
{
@@ -902,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (write)
{
- subBuffer.SignalModified(subRange.Address, subRange.Size);
+ subBuffer.SignalModified(subRange.Address, subRange.Size, stage);
}
}
@@ -935,9 +948,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Start address of the memory range
/// Size in bytes of the memory range
+ /// Buffer stage that triggered the access
/// Whether the buffer will be written to by this use
/// The buffer where the range is fully contained
- private Buffer GetBuffer(ulong address, ulong size, bool write = false)
+ private Buffer GetBuffer(ulong address, ulong size, BufferStage stage, bool write = false)
{
Buffer buffer;
@@ -950,7 +964,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (write)
{
- buffer.SignalModified(address, size);
+ buffer.SignalModified(address, size, stage);
}
}
else
@@ -1004,6 +1018,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Signal that the given buffer's handle has changed,
+ /// forcing rebind and any overlapping multi-range buffers to be recreated.
+ ///
+ /// The buffer that has changed handle
+ public void BufferBackingChanged(Buffer buffer)
+ {
+ NotifyBuffersModified?.Invoke();
+
+ RecreateMultiRangeBuffers(buffer.Address, buffer.Size);
+ }
+
///
/// Prune any invalid entries from a quick access dictionary.
///
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
index 8f2201e0a..26d9501c6 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs
@@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Type of each index buffer element
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.IndexBuffer);
_indexBuffer.Range = range;
_indexBuffer.Type = type;
@@ -186,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Vertex divisor of the buffer, for instanced draws
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.VertexBuffer);
_vertexBuffers[index].Range = range;
_vertexBuffers[index].Stride = stride;
@@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the transform feedback buffer
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStage.TransformFeedback);
_transformFeedbackBuffers[index] = new BufferBounds(range);
_transformFeedbackBuffersDirty = true;
@@ -260,7 +260,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.ComputeStorage(flags));
_cpStorageBuffers.SetBounds(index, range, flags);
}
@@ -284,7 +284,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
gpuVa = BitUtils.AlignDown(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateMultiBuffers(_channel.MemoryManager, gpuVa, size, BufferStageUtils.GraphicsStorage(stage, flags));
if (!buffers.Buffers[index].Range.Equals(range))
{
@@ -303,7 +303,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the storage buffer
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStage.Compute);
_cpUniformBuffers.SetBounds(index, range);
}
@@ -318,7 +318,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Size in bytes of the storage buffer
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
{
- MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size);
+ MultiRange range = _channel.MemoryManager.Physical.BufferCache.TranslateAndCreateBuffer(_channel.MemoryManager, gpuVa, size, BufferStageUtils.FromShaderStage(stage));
_gpUniformBuffers[stage].SetBounds(index, range);
_gpUniformBuffersDirty = true;
@@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferTextures)
{
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
- var range = bufferCache.GetBufferRange(binding.Range, isStore);
+ var range = bufferCache.GetBufferRange(binding.Range, BufferStageUtils.TextureBuffer(binding.Stage, binding.BindingInfo.Flags), isStore);
binding.Texture.SetStorage(range);
// The texture must be rebound to use the new storage if it was updated.
@@ -526,7 +526,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferTextureArrays)
{
- var range = bufferCache.GetBufferRange(binding.Range);
+ var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
@@ -536,7 +536,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
foreach (var binding in _bufferImageArrays)
{
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
- var range = bufferCache.GetBufferRange(binding.Range, isStore);
+ var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None, isStore);
binding.Texture.SetStorage(range);
textureArray[0] = binding.Texture;
@@ -565,7 +565,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (!_indexBuffer.Range.IsUnmapped)
{
- BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range);
+ BufferRange buffer = bufferCache.GetBufferRange(_indexBuffer.Range, BufferStage.IndexBuffer);
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
}
@@ -597,7 +597,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
- BufferRange buffer = bufferCache.GetBufferRange(vb.Range);
+ BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
}
@@ -637,7 +637,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
continue;
}
- tfbs[index] = bufferCache.GetBufferRange(tfb.Range, write: true);
+ tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
}
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
@@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
- buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, write: true));
+ buffers[index] = new BufferAssignment(index, bufferCache.GetBufferRange(range, BufferStage.TransformFeedback, write: true));
}
}
@@ -751,6 +751,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{
ref var buffers = ref bindings[(int)stage - 1];
+ BufferStage bufferStage = BufferStageUtils.FromShaderStage(stage);
for (int index = 0; index < buffers.Count; index++)
{
@@ -762,8 +763,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage
- ? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite)
- : bufferCache.GetBufferRange(bounds.Range);
+ ? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
+ : bufferCache.GetBufferRange(bounds.Range, bufferStage);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
@@ -799,8 +800,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
var range = isStorage
- ? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite)
- : bufferCache.GetBufferRange(bounds.Range);
+ ? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
+ : bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
@@ -875,7 +876,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
Format format,
bool isImage)
{
- _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
+ _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage));
}
@@ -883,6 +884,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
///
+ /// Shader stage accessing the texture
/// Texture array where the element will be inserted
/// Buffer texture
/// Physical ranges of memory where the buffer texture data is located
@@ -890,6 +892,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Index of the binding on the array
/// Format of the buffer texture
public void SetBufferTextureStorage(
+ ShaderStage stage,
ITextureArray array,
ITexture texture,
MultiRange range,
@@ -897,7 +900,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int index,
Format format)
{
- _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
+ _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferTextureArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format));
}
@@ -905,6 +908,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
///
+ /// Shader stage accessing the texture
/// Image array where the element will be inserted
/// Buffer texture
/// Physical ranges of memory where the buffer texture data is located
@@ -912,6 +916,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Index of the binding on the array
/// Format of the buffer texture
public void SetBufferTextureStorage(
+ ShaderStage stage,
IImageArray array,
ITexture texture,
MultiRange range,
@@ -919,7 +924,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
int index,
Format format)
{
- _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
+ _channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
_bufferImageArrays.Add(new BufferTextureArrayBinding(array, texture, range, bindingInfo, index, format));
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
index 0a5268031..ce9985318 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
@@ -1,37 +1,21 @@
using System;
+using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
{
///
- /// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete.
- /// Keeps the source buffer alive for data flushes until the migration is complete.
+ /// A record of when buffer data was copied from multiple buffers to one migration target,
+ /// along with the SyncNumber when the migration will be complete.
+ /// Keeps the source buffers alive for data flushes until the migration is complete.
+ /// All spans cover the full range of the "destination" buffer.
///
internal class BufferMigration : IDisposable
{
///
- /// The offset for the migrated region.
+ /// Ranges from source buffers that were copied as part of this migration.
+ /// Ordered by increasing base address.
///
- private readonly ulong _offset;
-
- ///
- /// The size for the migrated region.
- ///
- private readonly ulong _size;
-
- ///
- /// The buffer that was migrated from.
- ///
- private readonly Buffer _buffer;
-
- ///
- /// The source range action, to be called on overlap with an unreached sync number.
- ///
- private readonly Action _sourceRangeAction;
-
- ///
- /// The source range list.
- ///
- private readonly BufferModifiedRangeList _source;
+ public BufferMigrationSpan[] Spans { get; private set; }
///
/// The destination range list. This range list must be updated when flushing the source.
@@ -43,55 +27,193 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
public readonly ulong SyncNumber;
+ ///
+ /// Number of active users there are traversing this migration's spans.
+ ///
+ private int _refCount;
+
+ ///
+ /// Create a new buffer migration.
+ ///
+ /// Source spans for the migration
+ /// Destination buffer range list
+ /// Sync number where this migration will be complete
+ public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber)
+ {
+ Spans = spans;
+ Destination = destination;
+ SyncNumber = syncNumber;
+ }
+
+ ///
+ /// Add a span to the migration. Allocates a new array with the target size, and replaces it.
+ ///
+ ///
+ /// The base address for the span is assumed to be higher than all other spans in the migration,
+ /// to keep the span array ordered.
+ ///
+ public void AddSpanToEnd(BufferMigrationSpan span)
+ {
+ BufferMigrationSpan[] oldSpans = Spans;
+
+ BufferMigrationSpan[] newSpans = new BufferMigrationSpan[oldSpans.Length + 1];
+
+ oldSpans.CopyTo(newSpans, 0);
+
+ newSpans[oldSpans.Length] = span;
+
+ Spans = newSpans;
+ }
+
+ ///
+ /// Performs the given range action, or one from a migration that overlaps and has not synced yet.
+ ///
+ /// The offset to pass to the action
+ /// The size to pass to the action
+ /// The sync number that has been reached
+ /// The action to perform
+ public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
+ {
+ long syncDiff = (long)(syncNumber - SyncNumber);
+
+ if (syncDiff >= 0)
+ {
+ // The migration has completed. Run the parent action.
+ rangeAction(offset, size, syncNumber);
+ }
+ else
+ {
+ Interlocked.Increment(ref _refCount);
+
+ ulong prevAddress = offset;
+ ulong endAddress = offset + size;
+
+ foreach (BufferMigrationSpan span in Spans)
+ {
+ if (!span.Overlaps(offset, size))
+ {
+ continue;
+ }
+
+ if (span.Address > prevAddress)
+ {
+ // There's a gap between this span and the last (or the start address). Flush the range using the parent action.
+
+ rangeAction(prevAddress, span.Address - prevAddress, syncNumber);
+ }
+
+ span.RangeActionWithMigration(offset, size, syncNumber);
+
+ prevAddress = span.Address + span.Size;
+ }
+
+ if (endAddress > prevAddress)
+ {
+ // There's a gap at the end of the range with no migration. Flush the range using the parent action.
+ rangeAction(prevAddress, endAddress - prevAddress, syncNumber);
+ }
+
+ Interlocked.Decrement(ref _refCount);
+ }
+ }
+
+ ///
+ /// Dispose the buffer migration. This removes the reference from the destination range list,
+ /// and runs all the dispose buffers for the migration spans. (typically disposes the source buffer)
+ ///
+ public void Dispose()
+ {
+ while (Volatile.Read(ref _refCount) > 0)
+ {
+ // Coming into this method, the sync for the migration will be met, so nothing can increment the ref count.
+ // However, an existing traversal of the spans for data flush could still be in progress.
+ // Spin if this is ever the case, so they don't get disposed before the operation is complete.
+ }
+
+ Destination.RemoveMigration(this);
+
+ foreach (BufferMigrationSpan span in Spans)
+ {
+ span.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// A record of when buffer data was copied from one buffer to another, for a specific range in a source buffer.
+ /// Keeps the source buffer alive for data flushes until the migration is complete.
+ ///
+ internal readonly struct BufferMigrationSpan : IDisposable
+ {
+ ///
+ /// The offset for the migrated region.
+ ///
+ public readonly ulong Address;
+
+ ///
+ /// The size for the migrated region.
+ ///
+ public readonly ulong Size;
+
+ ///
+ /// The action to perform when the migration isn't needed anymore.
+ ///
+ private readonly Action _disposeAction;
+
+ ///
+ /// The source range action, to be called on overlap with an unreached sync number.
+ ///
+ private readonly BufferFlushAction _sourceRangeAction;
+
+ ///
+ /// Optional migration for the source data. Can chain together if many migrations happen in a short time.
+ /// If this is null, then _sourceRangeAction will always provide up to date data.
+ ///
+ private readonly BufferMigration _source;
+
///
/// Creates a record for a buffer migration.
///
/// The source buffer for this migration
+ /// The action to perform when the migration isn't needed anymore
/// The flush action for the source buffer
- /// The modified range list for the source buffer
- /// The modified range list for the destination buffer
- /// The sync number for when the migration is complete
- public BufferMigration(
+ /// Pending migration for the source buffer
+ public BufferMigrationSpan(
Buffer buffer,
- Action sourceRangeAction,
- BufferModifiedRangeList source,
- BufferModifiedRangeList dest,
- ulong syncNumber)
+ Action disposeAction,
+ BufferFlushAction sourceRangeAction,
+ BufferMigration source)
{
- _offset = buffer.Address;
- _size = buffer.Size;
- _buffer = buffer;
+ Address = buffer.Address;
+ Size = buffer.Size;
+ _disposeAction = disposeAction;
_sourceRangeAction = sourceRangeAction;
_source = source;
- Destination = dest;
- SyncNumber = syncNumber;
}
+ ///
+ /// Creates a record for a buffer migration, using the default buffer dispose action.
+ ///
+ /// The source buffer for this migration
+ /// The flush action for the source buffer
+ /// Pending migration for the source buffer
+ public BufferMigrationSpan(
+ Buffer buffer,
+ BufferFlushAction sourceRangeAction,
+ BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { }
+
///
/// Determine if the given range overlaps this migration, and has not been completed yet.
///
/// Start offset
/// Range size
- /// The sync number that was waited on
/// True if overlapping and in progress, false otherwise
- public bool Overlaps(ulong offset, ulong size, ulong syncNumber)
+ public bool Overlaps(ulong offset, ulong size)
{
ulong end = offset + size;
- ulong destEnd = _offset + _size;
- long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed.
+ ulong destEnd = Address + Size;
- return !(end <= _offset || offset >= destEnd) && syncDiff < 0;
- }
-
- ///
- /// Determine if the given range matches this migration.
- ///
- /// Start offset
- /// Range size
- /// True if the range exactly matches, false otherwise
- public bool FullyMatches(ulong offset, ulong size)
- {
- return _offset == offset && _size == size;
+ return !(end <= Address || offset >= destEnd);
}
///
@@ -100,26 +222,30 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// Start offset
/// Range size
/// Current sync number
- /// The modified range list that originally owned this range
- public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
+ public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber)
{
ulong end = offset + size;
- end = Math.Min(_offset + _size, end);
- offset = Math.Max(_offset, offset);
+ end = Math.Min(Address + Size, end);
+ offset = Math.Max(Address, offset);
size = end - offset;
- _source.RangeActionWithMigration(offset, size, syncNumber, parent, _sourceRangeAction);
+ if (_source != null)
+ {
+ _source.RangeActionWithMigration(offset, size, syncNumber, _sourceRangeAction);
+ }
+ else
+ {
+ _sourceRangeAction(offset, size, syncNumber);
+ }
}
///
- /// Removes this reference to the range list, potentially allowing for the source buffer to be disposed.
+ /// Removes this migration span, potentially allowing for the source buffer to be disposed.
///
public void Dispose()
{
- Destination.RemoveMigration(this);
-
- _buffer.DecrementReferenceCount();
+ _disposeAction();
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
index 6ada8a4b2..d330de638 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs
@@ -1,7 +1,6 @@
using Ryujinx.Common.Pools;
using Ryujinx.Memory.Range;
using System;
-using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Graphics.Gpu.Memory
@@ -72,10 +71,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context;
private readonly Buffer _parent;
- private readonly Action _flushAction;
+ private readonly BufferFlushAction _flushAction;
- private List _sources;
- private BufferMigration _migrationTarget;
+ private BufferMigration _source;
+ private BufferModifiedRangeList _migrationTarget;
private readonly object _lock = new();
@@ -99,7 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// GPU context that the buffer range list belongs to
/// The parent buffer that owns this range list
/// The flush action for the parent buffer
- public BufferModifiedRangeList(GpuContext context, Buffer parent, Action flushAction) : base(BackingInitialSize)
+ public BufferModifiedRangeList(GpuContext context, Buffer parent, BufferFlushAction flushAction) : base(BackingInitialSize)
{
_context = context;
_parent = parent;
@@ -199,6 +198,36 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Gets modified ranges within the specified region, and then fires the given action for each range individually.
+ ///
+ /// Start address to query
+ /// Size to query
+ /// Sync number required for a range to be signalled
+ /// The action to call for each modified range
+ public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action rangeAction)
+ {
+ int count = 0;
+
+ ref var overlaps = ref ThreadStaticArray.Get();
+
+ // Range list must be consistent for this operation.
+ lock (_lock)
+ {
+ count = FindOverlapsNonOverlapping(address, size, ref overlaps);
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ BufferModifiedRange overlap = overlaps[i];
+
+ if (overlap.SyncNumber == syncNumber)
+ {
+ rangeAction(overlap.Address, overlap.Size);
+ }
+ }
+ }
+
///
/// Gets modified ranges within the specified region, and then fires the given action for each range individually.
///
@@ -245,41 +274,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// The offset to pass to the action
/// The size to pass to the action
/// The sync number that has been reached
- /// The modified range list that originally owned this range
/// The action to perform
- public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action rangeAction)
+ public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
{
- bool firstSource = true;
-
- if (parent != this)
+ if (_source != null)
{
- lock (_lock)
- {
- if (_sources != null)
- {
- foreach (BufferMigration source in _sources)
- {
- if (source.Overlaps(offset, size, syncNumber))
- {
- if (firstSource && !source.FullyMatches(offset, size))
- {
- // Perform this buffer's action first. The migrations will run after.
- rangeAction(offset, size);
- }
-
- source.RangeActionWithMigration(offset, size, syncNumber, parent);
-
- firstSource = false;
- }
- }
- }
- }
+ _source.RangeActionWithMigration(offset, size, syncNumber, rangeAction);
}
-
- if (firstSource)
+ else
{
- // No overlapping migrations, or they are not meant for this range, flush the data using the given action.
- rangeAction(offset, size);
+ rangeAction(offset, size, syncNumber);
}
}
@@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ClearPart(overlap, clampAddress, clampEnd);
- RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, overlap.Parent, _flushAction);
+ RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
}
}
@@ -329,7 +333,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
// There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine.
- _migrationTarget.Destination.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
+ _migrationTarget.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress);
}
///
@@ -367,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (rangeCount == -1)
{
- _migrationTarget.Destination.WaitForAndFlushRanges(address, size);
+ _migrationTarget.WaitForAndFlushRanges(address, size);
return;
}
@@ -407,6 +411,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
///
/// Inherit ranges from another modified range list.
///
+ ///
+ /// Assumes that ranges will be inherited in address ascending order.
+ ///
/// The range list to inherit from
/// The action to call for each modified range
public void InheritRanges(BufferModifiedRangeList ranges, Action registerRangeAction)
@@ -415,18 +422,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
lock (ranges._lock)
{
- BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber);
-
- ranges._parent.IncrementReferenceCount();
- ranges._migrationTarget = migration;
-
- _context.RegisterBufferMigration(migration);
-
inheritRanges = ranges.ToArray();
lock (_lock)
{
- (_sources ??= new List()).Add(migration);
+ // Copy over the migration from the previous range list
+
+ BufferMigration oldMigration = ranges._source;
+
+ BufferMigrationSpan span = new BufferMigrationSpan(ranges._parent, ranges._flushAction, oldMigration);
+ ranges._parent.IncrementReferenceCount();
+
+ if (_source == null)
+ {
+ // Create a new migration.
+ _source = new BufferMigration(new BufferMigrationSpan[] { span }, this, _context.SyncNumber);
+
+ _context.RegisterBufferMigration(_source);
+ }
+ else
+ {
+ // Extend the migration
+ _source.AddSpanToEnd(span);
+ }
+
+ ranges._migrationTarget = this;
foreach (BufferModifiedRange range in inheritRanges)
{
@@ -445,6 +465,27 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Register a migration from previous buffer storage. This migration is from a snapshot of the buffer's
+ /// current handle to its handle in the future, and is assumed to be complete when the sync action completes.
+ /// When the migration completes, the handle is disposed.
+ ///
+ public void SelfMigration()
+ {
+ lock (_lock)
+ {
+ BufferMigrationSpan span = new(_parent, _parent.GetSnapshotDisposeAction(), _parent.GetSnapshotFlushAction(), _source);
+ BufferMigration migration = new(new BufferMigrationSpan[] { span }, this, _context.SyncNumber);
+
+ // Migration target is used to redirect flush actions to the latest range list,
+ // so we don't need to set it here. (this range list is still the latest)
+
+ _context.RegisterBufferMigration(migration);
+
+ _source = migration;
+ }
+ }
+
///
/// Removes a source buffer migration, indicating its copy has completed.
///
@@ -453,7 +494,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
lock (_lock)
{
- _sources.Remove(migration);
+ if (_source == migration)
+ {
+ _source = null;
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs
new file mode 100644
index 000000000..d58b9ea66
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs
@@ -0,0 +1,295 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Manages flushing ranges from buffers in advance for easy access, if they are flushed often.
+ /// Typically, from device local memory to a host mapped target for cached access.
+ ///
+ internal class BufferPreFlush : IDisposable
+ {
+ private const ulong PageSize = MemoryManager.PageSize;
+
+ ///
+ /// Threshold for the number of copies without a flush required to disable preflush on a page.
+ ///
+ private const int DeactivateCopyThreshold = 200;
+
+ ///
+ /// Value that indicates whether a page has been flushed or copied before.
+ ///
+ private enum PreFlushState
+ {
+ None,
+ HasFlushed,
+ HasCopied
+ }
+
+ ///
+ /// Flush state for each page of the buffer.
+ /// Controls whether data should be copied to the flush buffer, what sync is expected
+ /// and unflushed copy counting for stopping copies that are no longer needed.
+ ///
+ private struct PreFlushPage
+ {
+ public PreFlushState State;
+ public ulong FirstActivatedSync;
+ public ulong LastCopiedSync;
+ public int CopyCount;
+ }
+
+ ///
+ /// True if there are ranges that should copy to the flush buffer, false otherwise.
+ ///
+ public bool ShouldCopy { get; private set; }
+
+ private readonly GpuContext _context;
+ private readonly Buffer _buffer;
+ private readonly PreFlushPage[] _pages;
+ private readonly ulong _address;
+ private readonly ulong _size;
+ private readonly ulong _misalignment;
+ private readonly Action _flushAction;
+
+ private BufferHandle _flushBuffer;
+
+ public BufferPreFlush(GpuContext context, Buffer parent, Action flushAction)
+ {
+ _context = context;
+ _buffer = parent;
+ _address = parent.Address;
+ _size = parent.Size;
+ _pages = new PreFlushPage[BitUtils.DivRoundUp(_size, PageSize)];
+ _misalignment = _address & (PageSize - 1);
+
+ _flushAction = flushAction;
+ }
+
+ ///
+ /// Ensure that the flush buffer exists.
+ ///
+ private void EnsureFlushBuffer()
+ {
+ if (_flushBuffer == BufferHandle.Null)
+ {
+ _flushBuffer = _context.Renderer.CreateBuffer((int)_size, BufferAccess.HostMemory);
+ }
+ }
+
+ ///
+ /// Gets a page range from an address and size byte range.
+ ///
+ /// Range address
+ /// Range size
+ /// A page index and count
+ private (int index, int count) GetPageRange(ulong address, ulong size)
+ {
+ ulong offset = address - _address;
+ ulong endOffset = offset + size;
+
+ int basePage = (int)(offset / PageSize);
+ int endPage = (int)((endOffset - 1) / PageSize);
+
+ return (basePage, 1 + endPage - basePage);
+ }
+
+ ///
+ /// Gets an offset and size range in the parent buffer from a page index and count.
+ ///
+ /// Range start page
+ /// Range page count
+ /// Offset and size range
+ private (int offset, int size) GetOffset(int startPage, int count)
+ {
+ int offset = (int)((ulong)startPage * PageSize - _misalignment);
+ int endOffset = (int)((ulong)(startPage + count) * PageSize - _misalignment);
+
+ offset = Math.Max(0, offset);
+ endOffset = Math.Min((int)_size, endOffset);
+
+ return (offset, endOffset - offset);
+ }
+
+ ///
+ /// Copy a range of pages from the parent buffer into the flush buffer.
+ ///
+ /// Range start page
+ /// Range page count
+ private void CopyPageRange(int startPage, int count)
+ {
+ (int offset, int size) = GetOffset(startPage, count);
+
+ EnsureFlushBuffer();
+
+ _context.Renderer.Pipeline.CopyBuffer(_buffer.Handle, _flushBuffer, offset, offset, size);
+ }
+
+ ///
+ /// Copy a modified range into the flush buffer if it's marked as flushed.
+ /// Any pages the range overlaps are copied, and copies aren't repeated in the same sync number.
+ ///
+ /// Range address
+ /// Range size
+ public void CopyModified(ulong address, ulong size)
+ {
+ (int baseIndex, int count) = GetPageRange(address, size);
+ ulong syncNumber = _context.SyncNumber;
+
+ int startPage = -1;
+
+ for (int i = 0; i < count; i++)
+ {
+ int pageIndex = baseIndex + i;
+ ref PreFlushPage page = ref _pages[pageIndex];
+
+ if (page.State > PreFlushState.None)
+ {
+ // Perform the copy, and update the state of each page.
+ if (startPage == -1)
+ {
+ startPage = pageIndex;
+ }
+
+ if (page.State != PreFlushState.HasCopied)
+ {
+ page.FirstActivatedSync = syncNumber;
+ page.State = PreFlushState.HasCopied;
+ }
+ else if (page.CopyCount++ >= DeactivateCopyThreshold)
+ {
+ page.CopyCount = 0;
+ page.State = PreFlushState.None;
+ }
+
+ if (page.LastCopiedSync != syncNumber)
+ {
+ page.LastCopiedSync = syncNumber;
+ }
+ }
+ else if (startPage != -1)
+ {
+ CopyPageRange(startPage, pageIndex - startPage);
+
+ startPage = -1;
+ }
+ }
+
+ if (startPage != -1)
+ {
+ CopyPageRange(startPage, (baseIndex + count) - startPage);
+ }
+ }
+
+ ///
+ /// Flush the given page range back into guest memory, optionally using data from the flush buffer.
+ /// The actual flushed range is an intersection of the page range and the address range.
+ ///
+ /// Address range start
+ /// Address range size
+ /// Page range start
+ /// Page range count
+ /// True if the data should come from the flush buffer
+ private void FlushPageRange(ulong address, ulong size, int startPage, int count, bool preFlush)
+ {
+ (int pageOffset, int pageSize) = GetOffset(startPage, count);
+
+ int offset = (int)(address - _address);
+ int end = offset + (int)size;
+
+ offset = Math.Max(offset, pageOffset);
+ end = Math.Min(end, pageOffset + pageSize);
+
+ if (end >= offset)
+ {
+ BufferHandle handle = preFlush ? _flushBuffer : _buffer.Handle;
+ _flushAction(handle, _address + (ulong)offset, (ulong)(end - offset));
+ }
+ }
+
+ ///
+ /// Flush the given address range back into guest memory, optionally using data from the flush buffer.
+ /// When a copy has been performed on or before the waited sync number, the data can come from the flush buffer.
+ /// Otherwise, it flushes the parent buffer directly.
+ ///
+ /// Range address
+ /// Range size
+ /// Sync number that has been waited for
+ public void FlushWithAction(ulong address, ulong size, ulong syncNumber)
+ {
+ // Copy the parts of the range that have pre-flush copies that have been completed.
+ // Run the flush action for ranges that don't have pre-flush copies.
+
+ // If a range doesn't have a pre-flush copy, consider adding one.
+
+ (int baseIndex, int count) = GetPageRange(address, size);
+
+ bool rangePreFlushed = false;
+ int startPage = -1;
+
+ for (int i = 0; i < count; i++)
+ {
+ int pageIndex = baseIndex + i;
+ ref PreFlushPage page = ref _pages[pageIndex];
+
+ bool flushPage = false;
+ page.CopyCount = 0;
+
+ if (page.State == PreFlushState.HasCopied)
+ {
+ if (syncNumber >= page.FirstActivatedSync)
+ {
+ // After the range is first activated, its data will always be copied to the preflush buffer on each sync.
+ flushPage = true;
+ }
+ }
+ else if (page.State == PreFlushState.None)
+ {
+ page.State = PreFlushState.HasFlushed;
+ ShouldCopy = true;
+ }
+
+ if (flushPage)
+ {
+ if (!rangePreFlushed || startPage == -1)
+ {
+ if (startPage != -1)
+ {
+ FlushPageRange(address, size, startPage, pageIndex - startPage, false);
+ }
+
+ rangePreFlushed = true;
+ startPage = pageIndex;
+ }
+ }
+ else if (rangePreFlushed || startPage == -1)
+ {
+ if (startPage != -1)
+ {
+ FlushPageRange(address, size, startPage, pageIndex - startPage, true);
+ }
+
+ rangePreFlushed = false;
+ startPage = pageIndex;
+ }
+ }
+
+ if (startPage != -1)
+ {
+ FlushPageRange(address, size, startPage, (baseIndex + count) - startPage, rangePreFlushed);
+ }
+ }
+
+ ///
+ /// Dispose the flush buffer, if present.
+ ///
+ public void Dispose()
+ {
+ if (_flushBuffer != BufferHandle.Null)
+ {
+ _context.Renderer.DeleteBuffer(_flushBuffer);
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs
new file mode 100644
index 000000000..d56abda28
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs
@@ -0,0 +1,99 @@
+using Ryujinx.Graphics.Shader;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+ ///
+ /// Pipeline stages that can modify buffer data, as well as flags indicating storage usage.
+ /// Must match ShaderStage for the shader stages, though anything after that can be in any order.
+ ///
+ internal enum BufferStage : byte
+ {
+ Compute,
+ Vertex,
+ TessellationControl,
+ TessellationEvaluation,
+ Geometry,
+ Fragment,
+
+ Indirect,
+ VertexBuffer,
+ IndexBuffer,
+ Copy,
+ TransformFeedback,
+ Internal,
+ None,
+
+ StageMask = 0x3f,
+ StorageMask = 0xc0,
+
+ StorageRead = 0x40,
+ StorageWrite = 0x80,
+
+#pragma warning disable CA1069 // Enums values should not be duplicated
+ StorageAtomic = 0xc0
+#pragma warning restore CA1069 // Enums values should not be duplicated
+ }
+
+ ///
+ /// Utility methods to convert shader stages and binding flags into buffer stages.
+ ///
+ internal static class BufferStageUtils
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage FromShaderStage(ShaderStage stage)
+ {
+ return (BufferStage)stage;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage FromShaderStage(int stageIndex)
+ {
+ return (BufferStage)(stageIndex + 1);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage FromUsage(BufferUsageFlags flags)
+ {
+ if (flags.HasFlag(BufferUsageFlags.Write))
+ {
+ return BufferStage.StorageWrite;
+ }
+ else
+ {
+ return BufferStage.StorageRead;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage FromUsage(TextureUsageFlags flags)
+ {
+ if (flags.HasFlag(TextureUsageFlags.ImageStore))
+ {
+ return BufferStage.StorageWrite;
+ }
+ else
+ {
+ return BufferStage.StorageRead;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage TextureBuffer(ShaderStage shaderStage, TextureUsageFlags flags)
+ {
+ return FromShaderStage(shaderStage) | FromUsage(flags);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage GraphicsStorage(int stageIndex, BufferUsageFlags flags)
+ {
+ return FromShaderStage(stageIndex) | FromUsage(flags);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferStage ComputeStorage(BufferUsageFlags flags)
+ {
+ return BufferStage.Compute | FromUsage(flags);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
index a80dcbc87..51be00b6e 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs
@@ -62,6 +62,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
var result = new TextureBindingInfo(
target,
+ descriptor.Set,
descriptor.Binding,
descriptor.ArrayLength,
descriptor.CbufSlot,
@@ -90,6 +91,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
var result = new TextureBindingInfo(
target,
format,
+ descriptor.Set,
descriptor.Binding,
descriptor.ArrayLength,
descriptor.CbufSlot,
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
index b08c44d67..3837092c9 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs
@@ -125,9 +125,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
CompressionAlgorithm algorithm = CompressionAlgorithm.None;
Read(ref algorithm);
- if (algorithm == CompressionAlgorithm.Deflate)
+ switch (algorithm)
{
- _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
+ case CompressionAlgorithm.None:
+ break;
+ case CompressionAlgorithm.Deflate:
+ _activeStream = new DeflateStream(_stream, CompressionMode.Decompress, true);
+ break;
+ case CompressionAlgorithm.Brotli:
+ _activeStream = new BrotliStream(_stream, CompressionMode.Decompress, true);
+ break;
+ default:
+ throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
}
}
@@ -139,9 +148,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
{
Write(ref algorithm);
- if (algorithm == CompressionAlgorithm.Deflate)
+ switch (algorithm)
{
- _activeStream = new DeflateStream(_stream, CompressionLevel.SmallestSize, true);
+ case CompressionAlgorithm.None:
+ break;
+ case CompressionAlgorithm.Deflate:
+ _activeStream = new DeflateStream(_stream, CompressionLevel.Fastest, true);
+ break;
+ case CompressionAlgorithm.Brotli:
+ _activeStream = new BrotliStream(_stream, CompressionLevel.Fastest, true);
+ break;
+ default:
+ throw new ArgumentException($"Invalid compression algorithm \"{algorithm}\"");
}
}
@@ -177,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
switch (algorithm)
{
case CompressionAlgorithm.None:
- stream.Read(data);
+ stream.ReadExactly(data);
break;
case CompressionAlgorithm.Deflate:
stream = new DeflateStream(stream, CompressionMode.Decompress, true);
@@ -187,6 +205,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
stream.Dispose();
break;
+ case CompressionAlgorithm.Brotli:
+ stream = new BrotliStream(stream, CompressionMode.Decompress, true);
+ for (int offset = 0; offset < data.Length;)
+ {
+ offset += stream.Read(data[offset..]);
+ }
+ stream.Dispose();
+ break;
}
}
@@ -206,7 +232,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
stream.Write(data);
break;
case CompressionAlgorithm.Deflate:
- stream = new DeflateStream(stream, CompressionLevel.SmallestSize, true);
+ stream = new DeflateStream(stream, CompressionLevel.Fastest, true);
+ stream.Write(data);
+ stream.Dispose();
+ break;
+ case CompressionAlgorithm.Brotli:
+ stream = new BrotliStream(stream, CompressionLevel.Fastest, true);
stream.Write(data);
stream.Dispose();
break;
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs
index 96ddbb513..86d3de07d 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs
@@ -14,5 +14,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Deflate compression (RFC 1951).
///
Deflate,
+
+ ///
+ /// Brotli compression (RFC 7932).
+ ///
+ Brotli,
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
index c4ce0b870..cecfe9acf 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs
@@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
/// Compression algorithm
public static CompressionAlgorithm GetCompressionAlgorithm()
{
- return CompressionAlgorithm.Deflate;
+ return CompressionAlgorithm.Brotli;
}
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
index 59d2cfb3f..08cd3bb02 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs
@@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
}
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
- dataFileStream.Read(cb1Data);
+ dataFileStream.ReadExactly(cb1Data);
BinarySerializer.ReadCompressed(dataFileStream, guestCode);
_cache[index] = (guestCode, cb1Data);
@@ -279,7 +279,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
byte[] cachedCode = new byte[entry.CodeSize];
byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
- dataFileStream.Read(cachedCb1Data);
+ dataFileStream.ReadExactly(cachedCb1Data);
BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index ea54049c2..c4b5a1380 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
- private const uint CodeGenVersion = 5936;
+ private const uint CodeGenVersion = 6921;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
index 0d562b0da..d89eebabf 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs
@@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_reservedImages = rrc.ReservedImages;
}
- public int CreateConstantBufferBinding(int index)
+ public SetBindingPair CreateConstantBufferBinding(int index)
{
int binding;
@@ -64,10 +64,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
binding = _resourceCounts.UniformBuffersCount++;
}
- return binding + _reservedConstantBuffers;
+ return new SetBindingPair(_context.Capabilities.UniformBufferSetIndex, binding + _reservedConstantBuffers);
}
- public int CreateImageBinding(int count, bool isBuffer)
+ public SetBindingPair CreateImageBinding(int count, bool isBuffer)
{
int binding;
@@ -96,10 +96,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
_resourceCounts.ImagesCount += count;
}
- return binding + _reservedImages;
+ return new SetBindingPair(_context.Capabilities.ImageSetIndex, binding + _reservedImages);
}
- public int CreateStorageBufferBinding(int index)
+ public SetBindingPair CreateStorageBufferBinding(int index)
{
int binding;
@@ -112,10 +112,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
binding = _resourceCounts.StorageBuffersCount++;
}
- return binding + _reservedStorageBuffers;
+ return new SetBindingPair(_context.Capabilities.StorageBufferSetIndex, binding + _reservedStorageBuffers);
}
- public int CreateTextureBinding(int count, bool isBuffer)
+ public SetBindingPair CreateTextureBinding(int count, bool isBuffer)
{
int binding;
@@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
_resourceCounts.TexturesCount += count;
}
- return binding + _reservedTextures;
+ return new SetBindingPair(_context.Capabilities.TextureSetIndex, binding + _reservedTextures);
}
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
@@ -183,6 +183,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
return maxPerStage * Constants.ShaderStages;
}
+ public int CreateExtraSet()
+ {
+ if (_resourceCounts.SetsCount >= _context.Capabilities.MaximumExtraSets)
+ {
+ return -1;
+ }
+
+ return _context.Capabilities.ExtraSetBaseIndex + _resourceCounts.SetsCount++;
+ }
+
public int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision;
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs b/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
index 126e3249c..59ab378cf 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs
@@ -24,5 +24,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Total of images used by the shaders.
///
public int ImagesCount;
+
+ ///
+ /// Total of extra sets used by the shaders.
+ ///
+ public int SetsCount;
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
index ed56db3b3..42b2cbb59 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
+using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Shader
@@ -9,13 +10,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
class ShaderInfoBuilder
{
- private const int TotalSets = 4;
-
- private const int UniformSetIndex = 0;
- private const int StorageSetIndex = 1;
- private const int TextureSetIndex = 2;
- private const int ImageSetIndex = 3;
-
private const ResourceStages SupportBufferStages =
ResourceStages.Compute |
ResourceStages.Vertex |
@@ -36,8 +30,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly int _reservedTextures;
private readonly int _reservedImages;
- private readonly List[] _resourceDescriptors;
- private readonly List[] _resourceUsages;
+ private List[] _resourceDescriptors;
+ private List[] _resourceUsages;
///
/// Creates a new shader info builder.
@@ -51,17 +45,27 @@ namespace Ryujinx.Graphics.Gpu.Shader
_fragmentOutputMap = -1;
- _resourceDescriptors = new List[TotalSets];
- _resourceUsages = new List[TotalSets];
+ int uniformSetIndex = context.Capabilities.UniformBufferSetIndex;
+ int storageSetIndex = context.Capabilities.StorageBufferSetIndex;
+ int textureSetIndex = context.Capabilities.TextureSetIndex;
+ int imageSetIndex = context.Capabilities.ImageSetIndex;
- for (int index = 0; index < TotalSets; index++)
+ int totalSets = Math.Max(uniformSetIndex, storageSetIndex);
+ totalSets = Math.Max(totalSets, textureSetIndex);
+ totalSets = Math.Max(totalSets, imageSetIndex);
+ totalSets++;
+
+ _resourceDescriptors = new List[totalSets];
+ _resourceUsages = new List[totalSets];
+
+ for (int index = 0; index < totalSets; index++)
{
_resourceDescriptors[index] = new();
_resourceUsages[index] = new();
}
- AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
- AddUsage(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
+ AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1);
+ AddUsage(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1);
ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
@@ -73,12 +77,20 @@ namespace Ryujinx.Graphics.Gpu.Shader
// TODO: Handle that better? Maybe we should only set the binding that are really needed on each 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.BufferTexture, TextureSetIndex, 0, rrc.ReservedTextures);
- PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ImageSetIndex, 0, rrc.ReservedImages);
+ PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
+ PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers);
+ PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
+ PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages);
}
+ ///
+ /// Populates descriptors and usages for vertex as compute and transform feedback emulation reserved resources.
+ ///
+ /// Shader stages where the resources are used
+ /// Resource type
+ /// Resource set index where the resources are used
+ /// First binding number
+ /// Amount of bindings
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
{
AddDescriptor(stages, type, setIndex, start, count);
@@ -127,18 +139,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2;
int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2;
- AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
- AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
- AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
- AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
+ int uniformSetIndex = _context.Capabilities.UniformBufferSetIndex;
+ int storageSetIndex = _context.Capabilities.StorageBufferSetIndex;
+ int textureSetIndex = _context.Capabilities.TextureSetIndex;
+ int imageSetIndex = _context.Capabilities.ImageSetIndex;
- AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false);
- AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true);
+ AddDescriptor(stages, ResourceType.UniformBuffer, uniformSetIndex, uniformBinding, uniformsPerStage);
+ AddDescriptor(stages, ResourceType.StorageBuffer, storageSetIndex, storageBinding, storagesPerStage);
+ AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, textureSetIndex, textureBinding, texturesPerStage);
+ AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, imageSetIndex, imageBinding, imagesPerStage);
- AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false);
- AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
- AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
- AddUsage(info.Images, stages, ImageSetIndex, isImage: true);
+ AddArrayDescriptors(info.Textures, stages, isImage: false);
+ AddArrayDescriptors(info.Images, stages, isImage: true);
+
+ AddUsage(info.CBuffers, stages, isStorage: false);
+ AddUsage(info.SBuffers, stages, isStorage: true);
+ AddUsage(info.Textures, stages, isImage: false);
+ AddUsage(info.Images, stages, isImage: true);
}
///
@@ -177,9 +194,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Textures to be added
/// Stages where the textures are used
- /// Descriptor set index where the textures will be bound
/// True for images, false for textures
- private void AddArrayDescriptors(IEnumerable textures, ResourceStages stages, int setIndex, bool isImage)
+ private void AddArrayDescriptors(IEnumerable textures, ResourceStages stages, bool isImage)
{
foreach (TextureDescriptor texture in textures)
{
@@ -187,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
{
ResourceType type = GetTextureResourceType(texture, isImage);
- _resourceDescriptors[setIndex].Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
+ GetDescriptors(texture.Set).Add(new ResourceDescriptor(texture.Binding, texture.ArrayLength, type, stages));
}
}
}
@@ -213,13 +229,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Buffers to be added
/// Stages where the buffers are used
- /// Descriptor set index where the buffers will be bound
/// True for storage buffers, false for uniform buffers
- private void AddUsage(IEnumerable buffers, ResourceStages stages, int setIndex, bool isStorage)
+ private void AddUsage(IEnumerable buffers, ResourceStages stages, bool isStorage)
{
foreach (BufferDescriptor buffer in buffers)
{
- _resourceUsages[setIndex].Add(new ResourceUsage(
+ GetUsages(buffer.Set).Add(new ResourceUsage(
buffer.Binding,
1,
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
@@ -232,18 +247,65 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Textures to be added
/// Stages where the textures are used
- /// Descriptor set index where the textures will be bound
/// True for images, false for textures
- private void AddUsage(IEnumerable textures, ResourceStages stages, int setIndex, bool isImage)
+ private void AddUsage(IEnumerable textures, ResourceStages stages, bool isImage)
{
foreach (TextureDescriptor texture in textures)
{
ResourceType type = GetTextureResourceType(texture, isImage);
- _resourceUsages[setIndex].Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
+ GetUsages(texture.Set).Add(new ResourceUsage(texture.Binding, texture.ArrayLength, type, stages));
}
}
+ ///
+ /// Gets the list of resource descriptors for a given set index. A new list will be created if needed.
+ ///
+ /// Resource set index
+ /// List of resource descriptors
+ private List GetDescriptors(int setIndex)
+ {
+ if (_resourceDescriptors.Length <= setIndex)
+ {
+ int oldLength = _resourceDescriptors.Length;
+ Array.Resize(ref _resourceDescriptors, setIndex + 1);
+
+ for (int index = oldLength; index <= setIndex; index++)
+ {
+ _resourceDescriptors[index] = new();
+ }
+ }
+
+ return _resourceDescriptors[setIndex];
+ }
+
+ ///
+ /// Gets the list of resource usages for a given set index. A new list will be created if needed.
+ ///
+ /// Resource set index
+ /// List of resource usages
+ private List GetUsages(int setIndex)
+ {
+ if (_resourceUsages.Length <= setIndex)
+ {
+ int oldLength = _resourceUsages.Length;
+ Array.Resize(ref _resourceUsages, setIndex + 1);
+
+ for (int index = oldLength; index <= setIndex; index++)
+ {
+ _resourceUsages[index] = new();
+ }
+ }
+
+ return _resourceUsages[setIndex];
+ }
+
+ ///
+ /// Gets a resource type from a texture descriptor.
+ ///
+ /// Texture descriptor
+ /// Whether the texture is a image texture (writable) or not (sampled)
+ /// Resource type
private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage)
{
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
@@ -278,10 +340,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Shader information
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
{
- var descriptors = new ResourceDescriptorCollection[TotalSets];
- var usages = new ResourceUsageCollection[TotalSets];
+ int totalSets = _resourceDescriptors.Length;
- for (int index = 0; index < TotalSets; index++)
+ var descriptors = new ResourceDescriptorCollection[totalSets];
+ var usages = new ResourceUsageCollection[totalSets];
+
+ for (int index = 0; index < totalSets; index++)
{
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
index 1c5acedf3..6198823d9 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/ImageArray.cs
@@ -63,5 +63,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
}
+
+ public void Dispose()
+ {
+ }
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
index d70b0a008..41ac058c1 100644
--- a/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Image/TextureArray.cs
@@ -48,5 +48,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
}
}
}
+
+ public void Dispose()
+ {
+ }
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
index 2a39ae446..ba9cd45c6 100644
--- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
+++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs
@@ -61,7 +61,9 @@ namespace Ryujinx.Graphics.OpenGL
{
BufferCount++;
- if (access.HasFlag(GAL.BufferAccess.FlushPersistent))
+ var memType = access & GAL.BufferAccess.MemoryTypeMask;
+
+ if (memType == GAL.BufferAccess.HostMemory)
{
BufferHandle handle = Buffer.CreatePersistent(size);
@@ -75,11 +77,6 @@ namespace Ryujinx.Graphics.OpenGL
}
}
- public BufferHandle CreateBuffer(int size, GAL.BufferAccess access, BufferHandle storageHint)
- {
- return CreateBuffer(size, access);
- }
-
public BufferHandle CreateBuffer(nint pointer, int size)
{
throw new NotSupportedException();
@@ -148,6 +145,7 @@ namespace Ryujinx.Graphics.OpenGL
return new Capabilities(
api: TargetApi.OpenGL,
vendorName: GpuVendor,
+ memoryType: SystemMemoryType.BackendManaged,
hasFrontFacingBug: intelWindows,
hasVectorIndexingBug: amdWindows,
needsFragmentOutputSpecialization: false,
@@ -189,6 +187,12 @@ namespace Ryujinx.Graphics.OpenGL
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
supportsDepthClipControl: true,
+ uniformBufferSetIndex: 0,
+ storageBufferSetIndex: 1,
+ textureSetIndex: 2,
+ imageSetIndex: 3,
+ extraSetBaseIndex: 0,
+ maximumExtraSets: 0,
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
maximumStorageBuffersPerStage: 16,
maximumTexturesPerStage: 32,
diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 6d066bb67..54f6b3f7b 100644
--- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -963,6 +963,11 @@ namespace Ryujinx.Graphics.OpenGL
(array as ImageArray).Bind(binding);
}
+ public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
+ {
+ throw new NotSupportedException("OpenGL does not support descriptor sets.");
+ }
+
public void SetIndexBuffer(BufferRange buffer, IndexType type)
{
_elementsType = type.Convert();
@@ -1312,6 +1317,11 @@ namespace Ryujinx.Graphics.OpenGL
(array as TextureArray).Bind(binding);
}
+ public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
+ {
+ throw new NotSupportedException("OpenGL does not support descriptor sets.");
+ }
+
public void SetTransformFeedbackBuffers(ReadOnlySpan buffers)
{
if (_tfEnabled)
diff --git a/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs b/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs
index ead1c5e67..11d4e3c11 100644
--- a/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs
+++ b/src/Ryujinx.Graphics.Shader/BufferDescriptor.cs
@@ -4,14 +4,16 @@ namespace Ryujinx.Graphics.Shader
{
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
+ public readonly int Set;
public readonly int Binding;
public readonly byte Slot;
public readonly byte SbCbSlot;
public readonly ushort SbCbOffset;
public readonly BufferUsageFlags Flags;
- public BufferDescriptor(int binding, int slot)
+ public BufferDescriptor(int set, int binding, int slot)
{
+ Set = set;
Binding = binding;
Slot = (byte)slot;
SbCbSlot = 0;
@@ -19,8 +21,9 @@ namespace Ryujinx.Graphics.Shader
Flags = BufferUsageFlags.None;
}
- public BufferDescriptor(int binding, int slot, int sbCbSlot, int sbCbOffset, BufferUsageFlags flags)
+ public BufferDescriptor(int set, int binding, int slot, int sbCbSlot, int sbCbOffset, BufferUsageFlags flags)
{
+ Set = set;
Binding = binding;
Slot = (byte)slot;
SbCbSlot = (byte)sbCbSlot;
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
index f0e57b534..4308b08f8 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs
@@ -462,7 +462,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
}
else
{
- context.Properties.Textures.TryGetValue(texOp.Binding, out TextureDefinition definition);
+ context.Properties.Textures.TryGetValue(texOp.GetTextureSetAndBinding(), out TextureDefinition definition);
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
string texCall;
@@ -639,7 +639,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{
- TextureDefinition textureDefinition = context.Properties.Textures[texOp.Binding];
+ TextureDefinition textureDefinition = context.Properties.Textures[texOp.GetTextureSetAndBinding()];
string name = textureDefinition.Name;
if (textureDefinition.ArrayLength != 1)
@@ -649,7 +649,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
if (texOp.IsSeparate)
{
- TextureDefinition samplerDefinition = context.Properties.Textures[texOp.SamplerBinding];
+ TextureDefinition samplerDefinition = context.Properties.Textures[texOp.GetSamplerSetAndBinding()];
string samplerName = samplerDefinition.Name;
if (samplerDefinition.ArrayLength != 1)
@@ -665,7 +665,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
private static string GetImageName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
{
- TextureDefinition definition = context.Properties.Images[texOp.Binding];
+ TextureDefinition definition = context.Properties.Images[texOp.GetTextureSetAndBinding()];
string name = definition.Name;
if (definition.ArrayLength != 1)
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
index 2b1fdf44c..cc7977f84 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs
@@ -33,9 +33,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
public Dictionary LocalMemories { get; } = new();
public Dictionary SharedMemories { get; } = new();
- public Dictionary SamplersTypes { get; } = new();
- public Dictionary Samplers { get; } = new();
- public Dictionary Images { get; } = new();
+ public Dictionary SamplersTypes { get; } = new();
+ public Dictionary Samplers { get; } = new();
+ public Dictionary Images { get; } = new();
public Dictionary Inputs { get; } = new();
public Dictionary Outputs { get; } = new();
@@ -98,11 +98,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
Logger = parameters.Logger;
TargetApi = parameters.TargetApi;
- AddCapability(Capability.Shader);
- AddCapability(Capability.Float64);
-
- SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
-
Delegates = new SpirvDelegates(this);
}
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
index 37df4df80..55d35bf0d 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs
@@ -208,13 +208,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant);
- context.Samplers.Add(sampler.Binding, new SamplerDeclaration(
+ context.Samplers.Add(new(sampler.Set, sampler.Binding), new SamplerDeclaration(
imageType,
sampledImageType,
sampledImagePointerType,
sampledImageVariable,
sampler.ArrayLength != 1));
- context.SamplersTypes.Add(sampler.Binding, sampler.Type);
+ context.SamplersTypes.Add(new(sampler.Set, sampler.Binding), sampler.Type);
context.Name(sampledImageVariable, sampler.Name);
context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
@@ -256,7 +256,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant);
- context.Images.Add(image.Binding, new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1));
+ context.Images.Add(new(image.Set, image.Binding), new ImageDeclaration(imageType, imagePointerType, imageVariable, image.ArrayLength != 1));
context.Name(imageVariable, image.Name);
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
index 34f8532a6..6206985d8 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs
@@ -602,7 +602,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- ImageDeclaration declaration = context.Images[texOp.Binding];
+ ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
SpvInstruction image = declaration.Image;
SpvInstruction resultType = context.GetType(componentType);
@@ -681,7 +681,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- ImageDeclaration declaration = context.Images[texOp.Binding];
+ ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
@@ -738,7 +738,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- ImageDeclaration declaration = context.Images[texOp.Binding];
+ ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
SpvInstruction image = declaration.Image;
if (declaration.IsIndexed)
@@ -837,7 +837,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- SamplerDeclaration declaration = context.Samplers[texOp.Binding];
+ SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
int pCount = texOp.Type.GetDimensions();
@@ -1161,7 +1161,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
return context.Get(type, texOp.GetSource(srcIndex++));
}
- SamplerDeclaration declaration = context.Samplers[texOp.Binding];
+ SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
int coordsCount = texOp.Type.GetDimensions();
@@ -1433,7 +1433,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
int srcIndex = 0;
- SamplerDeclaration declaration = context.Samplers[texOp.Binding];
+ SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
image = context.Image(declaration.ImageType, image);
@@ -1449,7 +1449,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
int srcIndex = 0;
- SamplerDeclaration declaration = context.Samplers[texOp.Binding];
+ SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
image = context.Image(declaration.ImageType, image);
@@ -1460,7 +1460,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
}
else
{
- var type = context.SamplersTypes[texOp.Binding];
+ var type = context.SamplersTypes[texOp.GetTextureSetAndBinding()];
bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions();
@@ -1889,7 +1889,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
{
image = context.Load(declaration.ImageType, image);
- SamplerDeclaration samplerDeclaration = context.Samplers[texOp.SamplerBinding];
+ SamplerDeclaration samplerDeclaration = context.Samplers[texOp.GetSamplerSetAndBinding()];
SpvInstruction sampler = samplerDeclaration.Image;
diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
index ccfdc46d0..b259dde28 100644
--- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
+++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs
@@ -43,6 +43,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
CodeGenContext context = new(info, parameters, instPool, integerPool);
+ context.AddCapability(Capability.Shader);
+
+ context.SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
+
context.AddCapability(Capability.GroupNonUniformBallot);
context.AddCapability(Capability.GroupNonUniformShuffle);
context.AddCapability(Capability.GroupNonUniformVote);
@@ -51,6 +55,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
context.AddCapability(Capability.ImageQuery);
context.AddCapability(Capability.SampledBuffer);
+ if (parameters.HostCapabilities.SupportsShaderFloat64)
+ {
+ context.AddCapability(Capability.Float64);
+ }
+
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
{
context.AddCapability(Capability.TransformFeedback);
@@ -58,7 +67,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
if (parameters.Definitions.Stage == ShaderStage.Fragment)
{
- if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
+ if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)) ||
+ context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.PrimitiveId)))
{
context.AddCapability(Capability.Geometry);
}
diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
index 3dc4ad907..4e6d6edf9 100644
--- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
+++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs
@@ -27,34 +27,43 @@ namespace Ryujinx.Graphics.Shader
ReadOnlySpan GetCode(ulong address, int minimumSize);
///
- /// Queries the binding number of a constant buffer.
+ /// Gets the binding number of a constant buffer.
///
/// Constant buffer index
/// Binding number
- int CreateConstantBufferBinding(int index);
+ SetBindingPair CreateConstantBufferBinding(int index);
///
- /// Queries the binding number of an image.
+ /// Gets the binding number of an image.
///
/// For array of images, the number of elements of the array, otherwise it should be 1
/// Indicates if the image is a buffer image
/// Binding number
- int CreateImageBinding(int count, bool isBuffer);
+ SetBindingPair CreateImageBinding(int count, bool isBuffer);
///
- /// Queries the binding number of a storage buffer.
+ /// Gets the binding number of a storage buffer.
///
/// Storage buffer index
/// Binding number
- int CreateStorageBufferBinding(int index);
+ SetBindingPair CreateStorageBufferBinding(int index);
///
- /// Queries the binding number of a texture.
+ /// Gets the binding number of a texture.
///
/// For array of textures, the number of elements of the array, otherwise it should be 1
/// Indicates if the texture is a buffer texture
/// Binding number
- int CreateTextureBinding(int count, bool isBuffer);
+ SetBindingPair CreateTextureBinding(int count, bool isBuffer);
+
+ ///
+ /// Gets the set index for an additional set, or -1 if there's no extra set available.
+ ///
+ /// Extra set index, or -1 if not available
+ int CreateExtraSet()
+ {
+ return -1;
+ }
///
/// Queries Local Size X for compute shaders.
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
index 630162ade..1d8651254 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs
@@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (op.BVal)
{
- context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
+ context.Copy(dest, context.ConditionalSelect(res, ConstF(1), ConstF(0)));
}
else
{
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
index 0aac0ffa8..383e82c69 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs
@@ -278,7 +278,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageAtomic,
type,
format,
@@ -286,7 +286,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
imm);
- Operand res = context.ImageAtomic(type, format, flags, binding, sources);
+ Operand res = context.ImageAtomic(type, format, flags, setAndBinding, sources);
context.Copy(d, res);
}
@@ -389,7 +389,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureFormat format = isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, handle);
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageLoad,
type,
format,
@@ -397,7 +397,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
handle);
- context.ImageLoad(type, format, flags, binding, (int)componentMask, dests, sources);
+ context.ImageLoad(type, format, flags, setAndBinding, (int)componentMask, dests, sources);
}
else
{
@@ -432,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureFormat format = GetTextureFormat(size);
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageLoad,
type,
format,
@@ -440,7 +440,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
handle);
- context.ImageLoad(type, format, flags, binding, compMask, dests, sources);
+ context.ImageLoad(type, format, flags, setAndBinding, compMask, dests, sources);
switch (size)
{
@@ -552,7 +552,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Bindless;
}
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageAtomic,
type,
format,
@@ -560,7 +560,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
imm);
- context.ImageAtomic(type, format, flags, binding, sources);
+ context.ImageAtomic(type, format, flags, setAndBinding, sources);
}
private static void EmitSust(
@@ -679,7 +679,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= TextureFlags.Coherent;
}
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.ImageStore,
type,
format,
@@ -687,7 +687,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
handle);
- context.ImageStore(type, format, flags, binding, sources);
+ context.ImageStore(type, format, flags, setAndBinding, sources);
}
private static int GetComponentSizeInBytesLog2(SuatomSize size)
diff --git a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
index 06daa26a0..2076262da 100644
--- a/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
+++ b/src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
@@ -885,7 +885,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(dest++, RegisterType.Gpr);
}
- int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.Lod,
type,
TextureFormat.Unknown,
@@ -913,7 +913,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
else
{
// The instruction component order is the inverse of GLSL's.
- Operand res = context.Lod(type, flags, binding, compIndex ^ 1, sources);
+ Operand res = context.Lod(type, flags, setAndBinding, compIndex ^ 1, sources);
res = context.FPMultiply(res, ConstF(256.0f));
@@ -1116,12 +1116,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
- int binding;
+ SetBindingPair setAndBinding;
switch (query)
{
case TexQuery.TexHeaderDimension:
- binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureQuerySize,
type,
TextureFormat.Unknown,
@@ -1140,13 +1140,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
break;
}
- context.Copy(d, context.TextureQuerySize(type, flags, binding, compIndex, sources));
+ context.Copy(d, context.TextureQuerySize(type, flags, setAndBinding, compIndex, sources));
}
}
break;
case TexQuery.TexHeaderTextureType:
- binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureQuerySamples,
type,
TextureFormat.Unknown,
@@ -1171,7 +1171,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (d != null)
{
- context.Copy(d, context.TextureQuerySamples(type, flags, binding, sources));
+ context.Copy(d, context.TextureQuerySamples(type, flags, setAndBinding, sources));
}
}
break;
@@ -1191,7 +1191,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand[] dests,
Operand[] sources)
{
- int binding = flags.HasFlag(TextureFlags.Bindless) ? 0 : context.ResourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = flags.HasFlag(TextureFlags.Bindless) ? default : context.ResourceManager.GetTextureOrImageBinding(
Instruction.TextureSample,
type,
TextureFormat.Unknown,
@@ -1199,7 +1199,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
TextureOperation.DefaultCbufSlot,
handle);
- context.TextureSample(type, flags, binding, componentMask, dests, sources);
+ context.TextureSample(type, flags, setAndBinding, componentMask, dests, sources);
}
private static SamplerType ConvertSamplerType(TexDim dimensions)
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
index 8703e660e..273a38a5b 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs
@@ -156,6 +156,26 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
return false;
}
+ public static bool IsComparison(this Instruction inst)
+ {
+ switch (inst & Instruction.Mask)
+ {
+ case Instruction.CompareEqual:
+ case Instruction.CompareGreater:
+ case Instruction.CompareGreaterOrEqual:
+ case Instruction.CompareGreaterOrEqualU32:
+ case Instruction.CompareGreaterU32:
+ case Instruction.CompareLess:
+ case Instruction.CompareLessOrEqual:
+ case Instruction.CompareLessOrEqualU32:
+ case Instruction.CompareLessU32:
+ case Instruction.CompareNotEqual:
+ return true;
+ }
+
+ return false;
+ }
+
public static bool IsTextureQuery(this Instruction inst)
{
inst &= Instruction.Mask;
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
index 74ec5ca61..7eee8f2e9 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs
@@ -8,7 +8,9 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public TextureFormat Format { get; set; }
public TextureFlags Flags { get; private set; }
+ public int Set { get; private set; }
public int Binding { get; private set; }
+ public int SamplerSet { get; private set; }
public int SamplerBinding { get; private set; }
public TextureOperation(
@@ -16,6 +18,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
SamplerType type,
TextureFormat format,
TextureFlags flags,
+ int set,
int binding,
int compIndex,
Operand[] dests,
@@ -24,24 +27,28 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
Type = type;
Format = format;
Flags = flags;
+ Set = set;
Binding = binding;
+ SamplerSet = -1;
SamplerBinding = -1;
}
- public void TurnIntoArray(int binding)
+ public void TurnIntoArray(SetBindingPair setAndBinding)
{
Flags &= ~TextureFlags.Bindless;
- Binding = binding;
+ Set = setAndBinding.SetIndex;
+ Binding = setAndBinding.Binding;
}
- public void TurnIntoArray(int textureBinding, int samplerBinding)
+ public void TurnIntoArray(SetBindingPair textureSetAndBinding, SetBindingPair samplerSetAndBinding)
{
- TurnIntoArray(textureBinding);
+ TurnIntoArray(textureSetAndBinding);
- SamplerBinding = samplerBinding;
+ SamplerSet = samplerSetAndBinding.SetIndex;
+ SamplerBinding = samplerSetAndBinding.Binding;
}
- public void SetBinding(int binding)
+ public void SetBinding(SetBindingPair setAndBinding)
{
if ((Flags & TextureFlags.Bindless) != 0)
{
@@ -50,7 +57,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
RemoveSource(0);
}
- Binding = binding;
+ Set = setAndBinding.SetIndex;
+ Binding = setAndBinding.Binding;
}
public void SetLodLevelFlag()
diff --git a/src/Ryujinx.Graphics.Shader/SetBindingPair.cs b/src/Ryujinx.Graphics.Shader/SetBindingPair.cs
new file mode 100644
index 000000000..1e8a4f9c6
--- /dev/null
+++ b/src/Ryujinx.Graphics.Shader/SetBindingPair.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.Shader
+{
+ public readonly record struct SetBindingPair(int SetIndex, int Binding);
+}
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
index 4068c4127..867cae853 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs
@@ -8,7 +8,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public TextureFormat Format { get; }
public TextureFlags Flags { get; }
+ public int Set { get; }
public int Binding { get; }
+ public int SamplerSet { get; }
public int SamplerBinding { get; }
public bool IsSeparate => SamplerBinding >= 0;
@@ -18,7 +20,9 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
SamplerType type,
TextureFormat format,
TextureFlags flags,
+ int set,
int binding,
+ int samplerSet,
int samplerBinding,
int index,
params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
@@ -26,8 +30,20 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
Type = type;
Format = format;
Flags = flags;
+ Set = set;
Binding = binding;
+ SamplerSet = samplerSet;
SamplerBinding = samplerBinding;
}
+
+ public SetBindingPair GetTextureSetAndBinding()
+ {
+ return new SetBindingPair(Set, Binding);
+ }
+
+ public SetBindingPair GetSamplerSetAndBinding()
+ {
+ return new SetBindingPair(SamplerSet, SamplerBinding);
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
index 8c12c2aaf..53ed6bfcc 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/ShaderProperties.cs
@@ -6,15 +6,15 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
private readonly Dictionary _constantBuffers;
private readonly Dictionary _storageBuffers;
- private readonly Dictionary _textures;
- private readonly Dictionary _images;
+ private readonly Dictionary _textures;
+ private readonly Dictionary _images;
private readonly Dictionary _localMemories;
private readonly Dictionary _sharedMemories;
public IReadOnlyDictionary ConstantBuffers => _constantBuffers;
public IReadOnlyDictionary StorageBuffers => _storageBuffers;
- public IReadOnlyDictionary Textures => _textures;
- public IReadOnlyDictionary Images => _images;
+ public IReadOnlyDictionary Textures => _textures;
+ public IReadOnlyDictionary Images => _images;
public IReadOnlyDictionary LocalMemories => _localMemories;
public IReadOnlyDictionary SharedMemories => _sharedMemories;
@@ -22,8 +22,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
{
_constantBuffers = new Dictionary();
_storageBuffers = new Dictionary();
- _textures = new Dictionary();
- _images = new Dictionary();
+ _textures = new Dictionary();
+ _images = new Dictionary();
_localMemories = new Dictionary();
_sharedMemories = new Dictionary();
}
@@ -40,12 +40,12 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
public void AddOrUpdateTexture(TextureDefinition definition)
{
- _textures[definition.Binding] = definition;
+ _textures[new(definition.Set, definition.Binding)] = definition;
}
public void AddOrUpdateImage(TextureDefinition definition)
{
- _images[definition.Binding] = definition;
+ _images[new(definition.Set, definition.Binding)] = definition;
}
public int AddLocalMemory(MemoryDefinition definition)
diff --git a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
index c4ebaee73..88053658d 100644
--- a/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
+++ b/src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs
@@ -169,7 +169,17 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
AstTextureOperation GetAstTextureOperation(TextureOperation texOp)
{
- return new AstTextureOperation(inst, texOp.Type, texOp.Format, texOp.Flags, texOp.Binding, texOp.SamplerBinding, texOp.Index, sources);
+ return new AstTextureOperation(
+ inst,
+ texOp.Type,
+ texOp.Format,
+ texOp.Flags,
+ texOp.Set,
+ texOp.Binding,
+ texOp.SamplerSet,
+ texOp.SamplerBinding,
+ texOp.Index,
+ sources);
}
int componentsCount = BitOperations.PopCount((uint)operation.Index);
diff --git a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs
index d287a1aa7..1e387407d 100644
--- a/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs
+++ b/src/Ryujinx.Graphics.Shader/TextureDescriptor.cs
@@ -4,6 +4,7 @@ namespace Ryujinx.Graphics.Shader
{
// New fields should be added to the end of the struct to keep disk shader cache compatibility.
+ public readonly int Set;
public readonly int Binding;
public readonly SamplerType Type;
@@ -18,6 +19,7 @@ namespace Ryujinx.Graphics.Shader
public readonly TextureUsageFlags Flags;
public TextureDescriptor(
+ int set,
int binding,
SamplerType type,
TextureFormat format,
@@ -27,6 +29,7 @@ namespace Ryujinx.Graphics.Shader
bool separate,
TextureUsageFlags flags)
{
+ Set = set;
Binding = binding;
Type = type;
Format = format;
diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
index e1157eea4..5e07b39f1 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs
@@ -124,7 +124,7 @@ namespace Ryujinx.Graphics.Shader.Translation
this.TextureSample(
SamplerType.TextureBuffer,
TextureFlags.IntCoords,
- ResourceManager.Reservations.IndexBufferTextureBinding,
+ ResourceManager.Reservations.GetIndexBufferTextureSetAndBinding(),
1,
new[] { vertexIndexVr },
new[] { this.IAdd(ibBaseOffset, outputVertexOffset) });
@@ -145,7 +145,7 @@ namespace Ryujinx.Graphics.Shader.Translation
this.TextureSample(
SamplerType.TextureBuffer,
TextureFlags.IntCoords,
- ResourceManager.Reservations.TopologyRemapBufferTextureBinding,
+ ResourceManager.Reservations.GetTopologyRemapBufferTextureSetAndBinding(),
1,
new[] { vertexIndex },
new[] { this.IAdd(baseVertex, Const(index)) });
diff --git a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
index 9e314c620..5bdbb0025 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs
@@ -618,12 +618,21 @@ namespace Ryujinx.Graphics.Shader.Translation
SamplerType type,
TextureFormat format,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
Operand[] sources)
{
Operand dest = Local();
- context.Add(new TextureOperation(Instruction.ImageAtomic, type, format, flags, binding, 0, new[] { dest }, sources));
+ context.Add(new TextureOperation(
+ Instruction.ImageAtomic,
+ type,
+ format,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ 0,
+ new[] { dest },
+ sources));
return dest;
}
@@ -633,12 +642,21 @@ namespace Ryujinx.Graphics.Shader.Translation
SamplerType type,
TextureFormat format,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
int compMask,
Operand[] dests,
Operand[] sources)
{
- context.Add(new TextureOperation(Instruction.ImageLoad, type, format, flags, binding, compMask, dests, sources));
+ context.Add(new TextureOperation(
+ Instruction.ImageLoad,
+ type,
+ format,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ compMask,
+ dests,
+ sources));
}
public static void ImageStore(
@@ -646,10 +664,19 @@ namespace Ryujinx.Graphics.Shader.Translation
SamplerType type,
TextureFormat format,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
Operand[] sources)
{
- context.Add(new TextureOperation(Instruction.ImageStore, type, format, flags, binding, 0, null, sources));
+ context.Add(new TextureOperation(
+ Instruction.ImageStore,
+ type,
+ format,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ 0,
+ null,
+ sources));
}
public static Operand IsNan(this EmitterContext context, Operand a, Instruction fpType = Instruction.FP32)
@@ -718,13 +745,22 @@ namespace Ryujinx.Graphics.Shader.Translation
this EmitterContext context,
SamplerType type,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
int compIndex,
Operand[] sources)
{
Operand dest = Local();
- context.Add(new TextureOperation(Instruction.Lod, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
+ context.Add(new TextureOperation(
+ Instruction.Lod,
+ type,
+ TextureFormat.Unknown,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ compIndex,
+ new[] { dest },
+ sources));
return dest;
}
@@ -889,24 +925,42 @@ namespace Ryujinx.Graphics.Shader.Translation
this EmitterContext context,
SamplerType type,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
int compMask,
Operand[] dests,
Operand[] sources)
{
- context.Add(new TextureOperation(Instruction.TextureSample, type, TextureFormat.Unknown, flags, binding, compMask, dests, sources));
+ context.Add(new TextureOperation(
+ Instruction.TextureSample,
+ type,
+ TextureFormat.Unknown,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ compMask,
+ dests,
+ sources));
}
public static Operand TextureQuerySamples(
this EmitterContext context,
SamplerType type,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
Operand[] sources)
{
Operand dest = Local();
- context.Add(new TextureOperation(Instruction.TextureQuerySamples, type, TextureFormat.Unknown, flags, binding, 0, new[] { dest }, sources));
+ context.Add(new TextureOperation(
+ Instruction.TextureQuerySamples,
+ type,
+ TextureFormat.Unknown,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ 0,
+ new[] { dest },
+ sources));
return dest;
}
@@ -915,13 +969,22 @@ namespace Ryujinx.Graphics.Shader.Translation
this EmitterContext context,
SamplerType type,
TextureFlags flags,
- int binding,
+ SetBindingPair setAndBinding,
int compIndex,
Operand[] sources)
{
Operand dest = Local();
- context.Add(new TextureOperation(Instruction.TextureQuerySize, type, TextureFormat.Unknown, flags, binding, compIndex, new[] { dest }, sources));
+ context.Add(new TextureOperation(
+ Instruction.TextureQuerySize,
+ type,
+ TextureFormat.Unknown,
+ flags,
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
+ compIndex,
+ new[] { dest },
+ sources));
return dest;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
index 2523272b0..11fe6599d 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs
@@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.Shader.Translation
public readonly bool SupportsGeometryShaderPassthrough;
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
+ public readonly bool SupportsShaderFloat64;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsViewportMask;
@@ -18,6 +19,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool supportsGeometryShaderPassthrough,
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
+ bool supportsShaderFloat64,
bool supportsTextureShadowLod,
bool supportsViewportMask)
{
@@ -27,6 +29,7 @@ namespace Ryujinx.Graphics.Shader.Translation
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
+ SupportsShaderFloat64 = supportsShaderFloat64;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsViewportMask = supportsViewportMask;
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
index 4128af241..1f2f79a2d 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
@@ -38,6 +38,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// If we can't do bindless elimination, remove the texture operation.
// Set any destination variables to zero.
+ string typeName = texOp.Inst.IsImage()
+ ? texOp.Type.ToGlslImageType(texOp.Format.GetComponentType())
+ : texOp.Type.ToGlslTextureType();
+
+ gpuAccessor.Log($"Failed to find handle source for bindless access of type \"{typeName}\".");
+
for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++)
{
block.Operations.AddBefore(node, new Operation(Instruction.Copy, texOp.GetDest(destIndex), OperandHelper.Const(0)));
@@ -62,17 +68,22 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return false;
}
- Operand nvHandle = texOp.GetSource(0);
+ Operand bindlessHandle = texOp.GetSource(0);
- if (nvHandle.AsgOp is not Operation handleOp ||
- handleOp.Inst != Instruction.Load ||
- (handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
+ if (bindlessHandle.AsgOp is PhiNode phi)
{
- // Right now, we only allow bindless access when the handle comes from a shader input or storage buffer.
- // This is an artificial limitation to prevent it from being used in cases where it
- // would have a large performance impact of loading all textures in the pool.
- // It might be removed in the future, if we can mitigate the performance impact.
+ for (int srcIndex = 0; srcIndex < phi.SourcesCount; srcIndex++)
+ {
+ Operand phiSource = phi.GetSource(srcIndex);
+ if (phiSource.AsgOp is not PhiNode && !IsBindlessAccessAllowed(phiSource))
+ {
+ return false;
+ }
+ }
+ }
+ else if (!IsBindlessAccessAllowed(bindlessHandle))
+ {
return false;
}
@@ -80,8 +91,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
Operand samplerHandle = OperandHelper.Local();
Operand textureIndex = OperandHelper.Local();
- block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, nvHandle, OperandHelper.Const(0xfffff)));
- block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, nvHandle, OperandHelper.Const(20)));
+ block.Operations.AddBefore(node, new Operation(Instruction.BitwiseAnd, textureHandle, bindlessHandle, OperandHelper.Const(0xfffff)));
+ block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, samplerHandle, bindlessHandle, OperandHelper.Const(20)));
int texturePoolLength = Math.Max(BindlessToArray.MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromPool());
@@ -91,7 +102,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
bool hasSampler = !texOp.Inst.IsImage();
- int textureBinding = resourceManager.GetTextureOrImageBinding(
+ SetBindingPair textureSetAndBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
@@ -111,7 +122,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
texOp.InsertSource(1, samplerIndex);
- int samplerBinding = resourceManager.GetTextureOrImageBinding(
+ SetBindingPair samplerSetAndBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
SamplerType.None,
texOp.Format,
@@ -120,11 +131,35 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
TextureHandle.PackOffsets(0, 0, TextureHandleType.Direct),
samplerPoolLength);
- texOp.TurnIntoArray(textureBinding, samplerBinding);
+ texOp.TurnIntoArray(textureSetAndBinding, samplerSetAndBinding);
}
else
{
- texOp.TurnIntoArray(textureBinding);
+ texOp.TurnIntoArray(textureSetAndBinding);
+ }
+
+ return true;
+ }
+
+ private static bool IsBindlessAccessAllowed(Operand bindlessHandle)
+ {
+ if (bindlessHandle.Type == OperandType.ConstantBuffer)
+ {
+ // Bindless access with handles from constant buffer is allowed.
+
+ return true;
+ }
+
+ if (bindlessHandle.AsgOp is not Operation handleOp ||
+ handleOp.Inst != Instruction.Load ||
+ (handleOp.StorageKind != StorageKind.Input && handleOp.StorageKind != StorageKind.StorageBuffer))
+ {
+ // Right now, we only allow bindless access when the handle comes from a shader input or storage buffer.
+ // This is an artificial limitation to prevent it from being used in cases where it
+ // would have a large performance impact of loading all textures in the pool.
+ // It might be removed in the future, if we can mitigate the performance impact.
+
+ return false;
}
return true;
@@ -265,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);
@@ -445,7 +480,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
- int binding = resourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
@@ -453,7 +488,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
cbufSlot,
cbufOffset);
- texOp.SetBinding(binding);
+ texOp.SetBinding(setAndBinding);
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
index f2be7975d..1e0b3b645 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToArray.cs
@@ -126,7 +126,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
- if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp)
+ Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block);
+
+ if (bindlessHandle.AsgOp is not Operation handleAsgOp)
{
continue;
}
@@ -137,8 +139,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (handleAsgOp.Inst == Instruction.BitwiseOr)
{
- Operand src0 = handleAsgOp.GetSource(0);
- Operand src1 = handleAsgOp.GetSource(1);
+ Operand src0 = Utils.FindLastOperation(handleAsgOp.GetSource(0), block);
+ Operand src1 = Utils.FindLastOperation(handleAsgOp.GetSource(1), block);
if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation)
{
@@ -221,7 +223,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length)
{
- int binding = resourceManager.GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = resourceManager.GetTextureOrImageBinding(
texOp.Inst,
texOp.Type,
texOp.Format,
@@ -230,7 +232,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
handleIndex,
length);
- texOp.TurnIntoArray(binding);
+ texOp.TurnIntoArray(setAndBinding);
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
index 49eb3a89b..1be7c5c52 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs
@@ -152,18 +152,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
// If all phi sources are the same, we can propagate it and remove the phi.
- Operand firstSrc = phi.GetSource(0);
-
- for (int index = 1; index < phi.SourcesCount; index++)
+ if (!Utils.AreAllSourcesTheSameOperand(phi))
{
- if (!IsSameOperand(firstSrc, phi.GetSource(index)))
- {
- return false;
- }
+ return false;
}
// All sources are equal, we can propagate the value.
+ Operand firstSrc = phi.GetSource(0);
Operand dest = phi.Dest;
INode[] uses = dest.UseOps.ToArray();
@@ -182,17 +178,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return true;
}
- private static bool IsSameOperand(Operand x, Operand y)
- {
- if (x.Type != y.Type || x.Value != y.Value)
- {
- return false;
- }
-
- // TODO: Handle Load operations with the same storage and the same constant parameters.
- return x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
- }
-
private static bool PropagatePack(Operation packOp)
{
// Propagate pack source operands to uses by unpack
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
index a509fcb42..097c8aa88 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs
@@ -31,6 +31,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
TryEliminateBitwiseOr(operation);
break;
+ case Instruction.CompareNotEqual:
+ TryEliminateCompareNotEqual(operation);
+ break;
+
case Instruction.ConditionalSelect:
TryEliminateConditionalSelect(operation);
break;
@@ -174,6 +178,32 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
+ private static void TryEliminateCompareNotEqual(Operation operation)
+ {
+ // Comparison instruction returns 0 if the result is false, and -1 if true.
+ // Doing a not equal zero comparison on the result is redundant, so we can just copy the first result in this case.
+
+ Operand lhs = operation.GetSource(0);
+ Operand rhs = operation.GetSource(1);
+
+ if (lhs.Type == OperandType.Constant)
+ {
+ (lhs, rhs) = (rhs, lhs);
+ }
+
+ if (rhs.Type != OperandType.Constant || rhs.Value != 0)
+ {
+ return;
+ }
+
+ if (lhs.AsgOp is not Operation compareOp || !compareOp.Inst.IsComparison())
+ {
+ return;
+ }
+
+ operation.TurnIntoCopy(lhs);
+ }
+
private static void TryEliminateConditionalSelect(Operation operation)
{
Operand cond = operation.GetSource(0);
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
index 74a6d5a1e..23180ff82 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs
@@ -34,6 +34,50 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return elemIndexSrc.Type == OperandType.Constant && elemIndexSrc.Value == elemIndex;
}
+ private static bool IsSameOperand(Operand x, Operand y)
+ {
+ if (x.Type != y.Type || x.Value != y.Value)
+ {
+ return false;
+ }
+
+ // TODO: Handle Load operations with the same storage and the same constant parameters.
+ return x == y || x.Type == OperandType.Constant || x.Type == OperandType.ConstantBuffer;
+ }
+
+ private static bool AreAllSourcesEqual(INode node, INode otherNode)
+ {
+ if (node.SourcesCount != otherNode.SourcesCount)
+ {
+ return false;
+ }
+
+ for (int index = 0; index < node.SourcesCount; index++)
+ {
+ if (!IsSameOperand(node.GetSource(index), otherNode.GetSource(index)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool AreAllSourcesTheSameOperand(INode node)
+ {
+ Operand firstSrc = node.GetSource(0);
+
+ for (int index = 1; index < node.SourcesCount; index++)
+ {
+ if (!IsSameOperand(firstSrc, node.GetSource(index)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private static Operation FindBranchSource(BasicBlock block)
{
foreach (BasicBlock sourceBlock in block.Predecessors)
@@ -55,6 +99,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return inst == Instruction.BranchIfFalse || inst == Instruction.BranchIfTrue;
}
+ private static bool IsSameCondition(Operand currentCondition, Operand queryCondition)
+ {
+ if (currentCondition == queryCondition)
+ {
+ return true;
+ }
+
+ return currentCondition.AsgOp is Operation currentOperation &&
+ queryCondition.AsgOp is Operation queryOperation &&
+ currentOperation.Inst == queryOperation.Inst &&
+ AreAllSourcesEqual(currentOperation, queryOperation);
+ }
+
private static bool BlockConditionsMatch(BasicBlock currentBlock, BasicBlock queryBlock)
{
// Check if all the conditions for the query block are satisfied by the current block.
@@ -70,10 +127,10 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
return currentBranch != null && queryBranch != null &&
currentBranch.Inst == queryBranch.Inst &&
- currentCondition == queryCondition;
+ IsSameCondition(currentCondition, queryCondition);
}
- public static Operand FindLastOperation(Operand source, BasicBlock block)
+ public static Operand FindLastOperation(Operand source, BasicBlock block, bool recurse = true)
{
if (source.AsgOp is PhiNode phiNode)
{
@@ -84,10 +141,23 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
{
BasicBlock phiBlock = phiNode.GetBlock(i);
+ Operand phiSource = phiNode.GetSource(i);
if (BlockConditionsMatch(block, phiBlock))
{
- return phiNode.GetSource(i);
+ return phiSource;
+ }
+ else if (recurse && phiSource.AsgOp is PhiNode)
+ {
+ // Phi source is another phi.
+ // Let's check if that phi has a block that matches our condition.
+
+ Operand match = FindLastOperation(phiSource, block, false);
+
+ if (match != phiSource)
+ {
+ return match;
+ }
}
}
}
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
index 890501c91..94691a5b4 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs
@@ -20,8 +20,8 @@ namespace Ryujinx.Graphics.Shader.Translation
private readonly ShaderStage _stage;
private readonly string _stagePrefix;
- private readonly int[] _cbSlotToBindingMap;
- private readonly int[] _sbSlotToBindingMap;
+ private readonly SetBindingPair[] _cbSlotToBindingMap;
+ private readonly SetBindingPair[] _sbSlotToBindingMap;
private uint _sbSlotWritten;
private readonly Dictionary _sbSlots;
@@ -33,6 +33,7 @@ namespace Ryujinx.Graphics.Shader.Translation
private struct TextureMeta
{
+ public int Set;
public int Binding;
public bool AccurateType;
public SamplerType Type;
@@ -64,10 +65,10 @@ namespace Ryujinx.Graphics.Shader.Translation
_stage = stage;
_stagePrefix = GetShaderStagePrefix(stage);
- _cbSlotToBindingMap = new int[18];
- _sbSlotToBindingMap = new int[16];
- _cbSlotToBindingMap.AsSpan().Fill(-1);
- _sbSlotToBindingMap.AsSpan().Fill(-1);
+ _cbSlotToBindingMap = new SetBindingPair[18];
+ _sbSlotToBindingMap = new SetBindingPair[16];
+ _cbSlotToBindingMap.AsSpan().Fill(new(-1, -1));
+ _sbSlotToBindingMap.AsSpan().Fill(new(-1, -1));
_sbSlots = new();
_sbSlotsReverse = new();
@@ -146,16 +147,16 @@ namespace Ryujinx.Graphics.Shader.Translation
public int GetConstantBufferBinding(int slot)
{
- int binding = _cbSlotToBindingMap[slot];
- if (binding < 0)
+ SetBindingPair setAndBinding = _cbSlotToBindingMap[slot];
+ if (setAndBinding.Binding < 0)
{
- binding = _gpuAccessor.CreateConstantBufferBinding(slot);
- _cbSlotToBindingMap[slot] = binding;
+ setAndBinding = _gpuAccessor.CreateConstantBufferBinding(slot);
+ _cbSlotToBindingMap[slot] = setAndBinding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
- AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}");
+ AddNewConstantBuffer(setAndBinding.SetIndex, setAndBinding.Binding, $"{_stagePrefix}_c{slotNumber}");
}
- return binding;
+ return setAndBinding.Binding;
}
public bool TryGetStorageBufferBinding(int sbCbSlot, int sbCbOffset, bool write, out int binding)
@@ -166,14 +167,14 @@ namespace Ryujinx.Graphics.Shader.Translation
return false;
}
- binding = _sbSlotToBindingMap[slot];
+ SetBindingPair setAndBinding = _sbSlotToBindingMap[slot];
- if (binding < 0)
+ if (setAndBinding.Binding < 0)
{
- binding = _gpuAccessor.CreateStorageBufferBinding(slot);
- _sbSlotToBindingMap[slot] = binding;
+ setAndBinding = _gpuAccessor.CreateStorageBufferBinding(slot);
+ _sbSlotToBindingMap[slot] = setAndBinding;
string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
- AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}");
+ AddNewStorageBuffer(setAndBinding.SetIndex, setAndBinding.Binding, $"{_stagePrefix}_s{slotNumber}");
}
if (write)
@@ -181,6 +182,7 @@ namespace Ryujinx.Graphics.Shader.Translation
_sbSlotWritten |= 1u << slot;
}
+ binding = setAndBinding.Binding;
return true;
}
@@ -208,7 +210,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
{
- if (_cbSlotToBindingMap[slot] == binding)
+ if (_cbSlotToBindingMap[slot].Binding == binding)
{
return true;
}
@@ -218,7 +220,7 @@ namespace Ryujinx.Graphics.Shader.Translation
return false;
}
- public int GetTextureOrImageBinding(
+ public SetBindingPair GetTextureOrImageBinding(
Instruction inst,
SamplerType type,
TextureFormat format,
@@ -240,7 +242,7 @@ namespace Ryujinx.Graphics.Shader.Translation
format = TextureFormat.Unknown;
}
- int binding = GetTextureOrImageBinding(
+ SetBindingPair setAndBinding = GetTextureOrImageBinding(
cbufSlot,
handle,
arrayLength,
@@ -255,10 +257,10 @@ namespace Ryujinx.Graphics.Shader.Translation
_gpuAccessor.RegisterTexture(handle, cbufSlot);
- return binding;
+ return setAndBinding;
}
- private int GetTextureOrImageBinding(
+ private SetBindingPair GetTextureOrImageBinding(
int cbufSlot,
int handle,
int arrayLength,
@@ -311,21 +313,38 @@ namespace Ryujinx.Graphics.Shader.Translation
UsageFlags = usageFlags,
};
+ int setIndex;
int binding;
if (dict.TryGetValue(info, out var existingMeta))
{
dict[info] = MergeTextureMeta(meta, existingMeta);
+ setIndex = existingMeta.Set;
binding = existingMeta.Binding;
}
else
{
- bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
+ if (arrayLength > 1 && (setIndex = _gpuAccessor.CreateExtraSet()) >= 0)
+ {
+ // We reserved an "extra set" for the array.
+ // In this case the binding is always the first one (0).
+ // Using separate sets for array is better as we need to do less descriptor set updates.
- binding = isImage
- ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer)
- : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer);
+ binding = 0;
+ }
+ else
+ {
+ bool isBuffer = (type & SamplerType.Mask) == SamplerType.TextureBuffer;
+ SetBindingPair setAndBinding = isImage
+ ? _gpuAccessor.CreateImageBinding(arrayLength, isBuffer)
+ : _gpuAccessor.CreateTextureBinding(arrayLength, isBuffer);
+
+ setIndex = setAndBinding.SetIndex;
+ binding = setAndBinding.Binding;
+ }
+
+ meta.Set = setIndex;
meta.Binding = binding;
dict.Add(info, meta);
@@ -355,7 +374,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
var definition = new TextureDefinition(
- isImage ? 3 : 2,
+ setIndex,
binding,
arrayLength,
separate,
@@ -373,11 +392,12 @@ namespace Ryujinx.Graphics.Shader.Translation
Properties.AddOrUpdateTexture(definition);
}
- return binding;
+ return new SetBindingPair(setIndex, binding);
}
private static TextureMeta MergeTextureMeta(TextureMeta meta, TextureMeta existingMeta)
{
+ meta.Set = existingMeta.Set;
meta.Binding = existingMeta.Binding;
meta.UsageFlags |= existingMeta.UsageFlags;
@@ -440,11 +460,11 @@ namespace Ryujinx.Graphics.Shader.Translation
for (int slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
{
- int binding = _cbSlotToBindingMap[slot];
+ SetBindingPair setAndBinding = _cbSlotToBindingMap[slot];
- if (binding >= 0 && _usedConstantBufferBindings.Contains(binding))
+ if (setAndBinding.Binding >= 0 && _usedConstantBufferBindings.Contains(setAndBinding.Binding))
{
- descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot);
+ descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot);
}
}
@@ -464,13 +484,13 @@ namespace Ryujinx.Graphics.Shader.Translation
foreach ((int key, int slot) in _sbSlots)
{
- int binding = _sbSlotToBindingMap[slot];
+ SetBindingPair setAndBinding = _sbSlotToBindingMap[slot];
- if (binding >= 0)
+ if (setAndBinding.Binding >= 0)
{
(int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key);
BufferUsageFlags flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None;
- descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot, sbCbSlot, sbCbOffset, flags);
+ descriptors[descriptorIndex++] = new BufferDescriptor(setAndBinding.SetIndex, setAndBinding.Binding, slot, sbCbSlot, sbCbOffset, flags);
}
}
@@ -507,6 +527,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
descriptors.Add(new TextureDescriptor(
+ meta.Set,
meta.Binding,
meta.Type,
info.Format,
@@ -527,6 +548,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
descriptors.Add(new TextureDescriptor(
+ meta.Set,
meta.Binding,
meta.Type,
info.Format,
@@ -587,24 +609,24 @@ namespace Ryujinx.Graphics.Shader.Translation
return false;
}
- private void AddNewConstantBuffer(int binding, string name)
+ private void AddNewConstantBuffer(int setIndex, int binding, string name)
{
StructureType type = new(new[]
{
new StructureField(AggregateType.Array | AggregateType.Vector4 | AggregateType.FP32, "data", Constants.ConstantBufferSize / 16),
});
- Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, 0, binding, name, type));
+ Properties.AddOrUpdateConstantBuffer(new(BufferLayout.Std140, setIndex, binding, name, type));
}
- private void AddNewStorageBuffer(int binding, string name)
+ private void AddNewStorageBuffer(int setIndex, int binding, string name)
{
StructureType type = new(new[]
{
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0),
});
- Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, 1, binding, name, type));
+ Properties.AddOrUpdateStorageBuffer(new(BufferLayout.Std430, setIndex, binding, name, type));
}
public static string GetShaderStagePrefix(ShaderStage stage)
diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs
index d559f6699..c89c4d0b6 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceReservations.cs
@@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.Shader.Translation
public const int MaxVertexBufferTextures = 32;
+ private const int TextureSetIndex = 2; // TODO: Get from GPU accessor.
+
public int VertexInfoConstantBufferBinding { get; }
public int VertexOutputStorageBufferBinding { get; }
public int GeometryVertexOutputStorageBufferBinding { get; }
@@ -163,6 +165,21 @@ namespace Ryujinx.Graphics.Shader.Translation
return _vertexBufferTextureBaseBinding + vaLocation;
}
+ public SetBindingPair GetVertexBufferTextureSetAndBinding(int vaLocation)
+ {
+ return new SetBindingPair(TextureSetIndex, GetVertexBufferTextureBinding(vaLocation));
+ }
+
+ public SetBindingPair GetIndexBufferTextureSetAndBinding()
+ {
+ return new SetBindingPair(TextureSetIndex, IndexBufferTextureBinding);
+ }
+
+ public SetBindingPair GetTopologyRemapBufferTextureSetAndBinding()
+ {
+ return new SetBindingPair(TextureSetIndex, TopologyRemapBufferTextureBinding);
+ }
+
internal bool TryGetOffset(StorageKind storageKind, int location, int component, out int offset)
{
return _offsets.TryGetValue(new IoDefinition(storageKind, IoVariable.UserDefined, location, component), out offset);
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
index 072b45695..6ba8cb44a 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs
@@ -182,6 +182,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
index,
new[] { coordSize },
@@ -251,6 +252,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
index,
new[] { coordSize },
@@ -471,6 +473,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
+ texOp.Set,
texOp.Binding,
1 << 3, // W component: i=0, j=0
new[] { dests[destIndex++] },
@@ -527,6 +530,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
+ texOp.Set,
texOp.Binding,
componentIndex,
dests,
@@ -573,6 +577,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
index,
new[] { texSizes[index] },
@@ -603,6 +608,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
0,
new[] { lod },
@@ -633,6 +639,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Type,
texOp.Format,
texOp.Flags,
+ texOp.Set,
texOp.Binding,
index,
new[] { texSizes[index] },
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs
index d71ada865..ddd2134d2 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/VertexToCompute.cs
@@ -54,6 +54,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{
bool needsSextNorm = context.Definitions.IsAttributePackedRgb10A2Signed(location);
+ SetBindingPair setAndBinding = context.ResourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location);
Operand temp = needsSextNorm ? Local() : dest;
Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, 0);
@@ -62,7 +63,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
SamplerType.TextureBuffer,
TextureFormat.Unknown,
TextureFlags.IntCoords,
- context.ResourceManager.Reservations.GetVertexBufferTextureBinding(location),
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
1 << component,
new[] { temp },
new[] { vertexElemOffset }));
@@ -75,6 +77,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
}
else
{
+ SetBindingPair setAndBinding = context.ResourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location);
Operand temp = component > 0 ? Local() : dest;
Operand vertexElemOffset = GenerateVertexOffset(context.ResourceManager, node, location, component);
@@ -83,7 +86,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
SamplerType.TextureBuffer,
TextureFormat.Unknown,
TextureFlags.IntCoords,
- context.ResourceManager.Reservations.GetVertexBufferTextureBinding(location),
+ setAndBinding.SetIndex,
+ setAndBinding.Binding,
1,
new[] { temp },
new[] { vertexElemOffset }));
diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
index 106535588..a579433f9 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
@@ -363,6 +363,7 @@ namespace Ryujinx.Graphics.Shader.Translation
GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(),
GpuAccessor.QueryHostSupportsShaderBallot(),
GpuAccessor.QueryHostSupportsShaderBarrierDivergence(),
+ GpuAccessor.QueryHostSupportsShaderFloat64(),
GpuAccessor.QueryHostSupportsTextureShadowLod(),
GpuAccessor.QueryHostSupportsViewportMask());
@@ -412,8 +413,8 @@ namespace Ryujinx.Graphics.Shader.Translation
if (Stage == ShaderStage.Vertex)
{
- int ibBinding = resourceManager.Reservations.IndexBufferTextureBinding;
- TextureDefinition indexBuffer = new(2, ibBinding, "ib_data", SamplerType.TextureBuffer);
+ SetBindingPair ibSetAndBinding = resourceManager.Reservations.GetIndexBufferTextureSetAndBinding();
+ TextureDefinition indexBuffer = new(ibSetAndBinding.SetIndex, ibSetAndBinding.Binding, "ib_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(indexBuffer);
int inputMap = _program.AttributeUsage.UsedInputAttributes;
@@ -421,8 +422,8 @@ namespace Ryujinx.Graphics.Shader.Translation
while (inputMap != 0)
{
int location = BitOperations.TrailingZeroCount(inputMap);
- int binding = resourceManager.Reservations.GetVertexBufferTextureBinding(location);
- TextureDefinition vaBuffer = new(2, binding, $"vb_data{location}", SamplerType.TextureBuffer);
+ SetBindingPair setAndBinding = resourceManager.Reservations.GetVertexBufferTextureSetAndBinding(location);
+ TextureDefinition vaBuffer = new(setAndBinding.SetIndex, setAndBinding.Binding, $"vb_data{location}", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(vaBuffer);
inputMap &= ~(1 << location);
@@ -430,8 +431,8 @@ namespace Ryujinx.Graphics.Shader.Translation
}
else if (Stage == ShaderStage.Geometry)
{
- int trbBinding = resourceManager.Reservations.TopologyRemapBufferTextureBinding;
- TextureDefinition remapBuffer = new(2, trbBinding, "trb_data", SamplerType.TextureBuffer);
+ SetBindingPair trbSetAndBinding = resourceManager.Reservations.GetTopologyRemapBufferTextureSetAndBinding();
+ TextureDefinition remapBuffer = new(trbSetAndBinding.SetIndex, trbSetAndBinding.Binding, "trb_data", SamplerType.TextureBuffer);
resourceManager.Properties.AddOrUpdateTexture(remapBuffer);
int geometryVbOutputSbBinding = resourceManager.Reservations.GeometryVertexOutputStorageBufferBinding;
diff --git a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs
index 3673ee5a1..3dcbc3130 100644
--- a/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/BufferHolder.cs
@@ -1,4 +1,3 @@
-using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
@@ -31,40 +30,29 @@ namespace Ryujinx.Graphics.Vulkan
private readonly VulkanRenderer _gd;
private readonly Device _device;
- private MemoryAllocation _allocation;
- private Auto _buffer;
- private Auto _allocationAuto;
+ private readonly MemoryAllocation _allocation;
+ private readonly Auto _buffer;
+ private readonly Auto _allocationAuto;
private readonly bool _allocationImported;
- private ulong _bufferHandle;
+ private readonly ulong _bufferHandle;
private CacheByRange _cachedConvertedBuffers;
public int Size { get; }
- private IntPtr _map;
+ private readonly IntPtr _map;
- private MultiFenceHolder _waitable;
+ private readonly MultiFenceHolder _waitable;
private bool _lastAccessIsWrite;
- private BufferAllocationType _baseType;
- private BufferAllocationType _currentType;
- private bool _swapQueued;
-
- public BufferAllocationType DesiredType { get; private set; }
-
- private int _setCount;
- private int _writeCount;
- private int _flushCount;
- private int _flushTemp;
- private int _lastFlushWrite = -1;
+ private readonly BufferAllocationType _baseType;
+ private readonly BufferAllocationType _activeType;
private readonly ReaderWriterLockSlim _flushLock;
private FenceHolder _flushFence;
private int _flushWaiting;
- private List _swapActions;
-
private byte[] _pendingData;
private BufferMirrorRangeList _pendingDataRanges;
private Dictionary _mirrors;
@@ -83,8 +71,7 @@ namespace Ryujinx.Graphics.Vulkan
_map = allocation.HostPointer;
_baseType = type;
- _currentType = currentType;
- DesiredType = currentType;
+ _activeType = currentType;
_flushLock = new ReaderWriterLockSlim();
_useMirrors = gd.IsTBDR;
@@ -104,8 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
_map = _allocation.HostPointer + offset;
_baseType = type;
- _currentType = currentType;
- DesiredType = currentType;
+ _activeType = currentType;
_flushLock = new ReaderWriterLockSlim();
}
@@ -120,164 +106,11 @@ namespace Ryujinx.Graphics.Vulkan
Size = size;
_baseType = BufferAllocationType.Sparse;
- _currentType = BufferAllocationType.Sparse;
- DesiredType = BufferAllocationType.Sparse;
+ _activeType = BufferAllocationType.Sparse;
_flushLock = new ReaderWriterLockSlim();
}
- public bool TryBackingSwap(ref CommandBufferScoped? cbs)
- {
- if (_swapQueued && DesiredType != _currentType)
- {
- // Only swap if the buffer is not used in any queued command buffer.
- bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
-
- if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReadLockHeld && (_pendingData == null || cbs != null))
- {
- var currentAllocation = _allocationAuto;
- var currentBuffer = _buffer;
- IntPtr currentMap = _map;
-
- (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, false, _currentType);
-
- if (buffer.Handle != 0)
- {
- if (cbs != null)
- {
- ClearMirrors(cbs.Value, 0, Size);
- }
-
- _flushLock.EnterWriteLock();
-
- ClearFlushFence();
-
- _waitable = new MultiFenceHolder(Size);
-
- _allocation = allocation;
- _allocationAuto = new Auto(allocation);
- _buffer = new Auto(new DisposableBuffer(_gd.Api, _device, buffer), this, _waitable, _allocationAuto);
- _bufferHandle = buffer.Handle;
- _map = allocation.HostPointer;
-
- if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
- {
- // Copy data directly. Readbacks don't have to wait if this is done.
-
- unsafe
- {
- new Span((void*)currentMap, Size).CopyTo(new Span((void*)_map, Size));
- }
- }
- else
- {
- cbs ??= _gd.CommandBufferPool.Rent();
-
- CommandBufferScoped cbsV = cbs.Value;
-
- Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
-
- // Need to wait for the data to reach the new buffer before data can be flushed.
-
- _flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
- _flushFence.Get();
- }
-
- Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
-
- _currentType = resultType;
-
- if (_swapActions != null)
- {
- foreach (var action in _swapActions)
- {
- action();
- }
-
- _swapActions.Clear();
- }
-
- currentBuffer.Dispose();
- currentAllocation.Dispose();
-
- _gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
-
- _flushLock.ExitWriteLock();
- }
-
- _swapQueued = false;
-
- return true;
- }
-
- return false;
- }
-
- _swapQueued = false;
-
- return true;
- }
-
- private void ConsiderBackingSwap()
- {
- if (_baseType == BufferAllocationType.Auto)
- {
- // When flushed, wait for a bit more info to make a decision.
- bool wasFlushed = _flushTemp > 0;
- int multiplier = wasFlushed ? 2 : 0;
- if (_writeCount >= (WriteCountThreshold << multiplier) || _setCount >= (SetCountThreshold << multiplier) || _flushCount >= (FlushCountThreshold << multiplier))
- {
- if (_flushCount > 0 || _flushTemp-- > 0)
- {
- // Buffers that flush should ideally be mapped in host address space for easy copies.
- // If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
- // If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
-
- bool hostMappingSensitive = _gd.Vendor == Vendor.Nvidia;
- bool deviceLocalMapped = Size > DeviceLocalSizeThreshold || (wasFlushed && _writeCount > _flushCount * 10 && hostMappingSensitive) || _currentType == BufferAllocationType.DeviceLocalMapped;
-
- DesiredType = deviceLocalMapped ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
-
- // It's harder for a buffer that is flushed to revert to another type of mapping.
- if (_flushCount > 0)
- {
- _flushTemp = 1000;
- }
- }
- else if (_writeCount >= (WriteCountThreshold << multiplier))
- {
- // Buffers that are written often should ideally be in the device local heap. (Storage buffers)
- DesiredType = BufferAllocationType.DeviceLocal;
- }
- else if (_setCount > (SetCountThreshold << multiplier))
- {
- // Buffers that have their data set often should ideally be host mapped. (Constant buffers)
- DesiredType = BufferAllocationType.HostMapped;
- }
-
- _lastFlushWrite = -1;
- _flushCount = 0;
- _writeCount = 0;
- _setCount = 0;
- }
-
- if (!_swapQueued && DesiredType != _currentType)
- {
- _swapQueued = true;
-
- _gd.PipelineInternal.AddBackingSwap(this);
- }
- }
- }
-
- public void Pin()
- {
- if (_baseType == BufferAllocationType.Auto)
- {
- _baseType = _currentType;
- }
- }
-
public unsafe Auto CreateView(VkFormat format, int offset, int size, Action invalidateView)
{
var bufferViewCreateInfo = new BufferViewCreateInfo
@@ -291,19 +124,9 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
- (_swapActions ??= new List()).Add(invalidateView);
-
return new Auto(new DisposableBufferView(_gd.Api, _device, bufferView), this, _waitable, _buffer);
}
- public void InheritMetrics(BufferHolder other)
- {
- _setCount = other._setCount;
- _writeCount = other._writeCount;
- _flushCount = other._flushCount;
- _flushTemp = other._flushTemp;
- }
-
public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
{
// If the last access is write, we always need a barrier to be sure we will read or modify
@@ -423,18 +246,8 @@ namespace Ryujinx.Graphics.Vulkan
{
if (isWrite)
{
- _writeCount++;
-
SignalWrite(0, Size);
}
- else if (isSSBO)
- {
- // Always consider SSBO access for swapping to device local memory.
-
- _writeCount++;
-
- ConsiderBackingSwap();
- }
return _buffer;
}
@@ -443,8 +256,6 @@ namespace Ryujinx.Graphics.Vulkan
{
if (isWrite)
{
- _writeCount++;
-
SignalWrite(offset, size);
}
@@ -543,8 +354,6 @@ namespace Ryujinx.Graphics.Vulkan
public void SignalWrite(int offset, int size)
{
- ConsiderBackingSwap();
-
if (offset == 0 && size == Size)
{
_cachedConvertedBuffers.Clear();
@@ -624,13 +433,6 @@ namespace Ryujinx.Graphics.Vulkan
WaitForFlushFence();
- if (_lastFlushWrite != _writeCount)
- {
- // If it's on the same page as the last flush, ignore it.
- _lastFlushWrite = _writeCount;
- _flushCount++;
- }
-
Span result;
if (_map != IntPtr.Zero)
@@ -711,8 +513,7 @@ namespace Ryujinx.Graphics.Vulkan
return;
}
- _setCount++;
- bool allowMirror = _useMirrors && allowCbsWait && cbs != null && _currentType <= BufferAllocationType.HostMapped;
+ bool allowMirror = _useMirrors && allowCbsWait && cbs != null && _activeType <= BufferAllocationType.HostMapped;
if (_map != IntPtr.Zero)
{
@@ -863,8 +664,6 @@ namespace Ryujinx.Graphics.Vulkan
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length, true).Value;
- _writeCount--;
-
InsertBufferBarrier(
_gd,
cbs.CommandBuffer,
@@ -1100,8 +899,6 @@ namespace Ryujinx.Graphics.Vulkan
public void Dispose()
{
- _swapQueued = false;
-
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
_buffer.Dispose();
diff --git a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs
index 33289a0e0..1b6ac9988 100644
--- a/src/Ryujinx.Graphics.Vulkan/BufferManager.cs
+++ b/src/Ryujinx.Graphics.Vulkan/BufferManager.cs
@@ -103,12 +103,19 @@ namespace Ryujinx.Graphics.Vulkan
usage |= BufferUsageFlags.IndirectBufferBit;
}
+ var externalMemoryBuffer = new ExternalMemoryBufferCreateInfo
+ {
+ SType = StructureType.ExternalMemoryBufferCreateInfo,
+ HandleTypes = ExternalMemoryHandleTypeFlags.HostAllocationBitExt,
+ };
+
var bufferCreateInfo = new BufferCreateInfo
{
SType = StructureType.BufferCreateInfo,
Size = (ulong)size,
Usage = usage,
SharingMode = SharingMode.Exclusive,
+ PNext = &externalMemoryBuffer,
};
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
@@ -165,10 +172,6 @@ namespace Ryujinx.Graphics.Vulkan
if (TryGetBuffer(range.Handle, out var existingHolder))
{
- // Since this buffer now also owns the memory from the referenced buffer,
- // we pin it to ensure the memory location will not change.
- existingHolder.Pin();
-
(var memory, var offset) = existingHolder.GetDeviceMemoryAndOffset();
memoryBinds[index] = new SparseMemoryBind()
@@ -235,10 +238,9 @@ namespace Ryujinx.Graphics.Vulkan
int size,
bool sparseCompatible = false,
BufferAllocationType baseType = BufferAllocationType.HostMapped,
- BufferHandle storageHint = default,
bool forceMirrors = false)
{
- return CreateWithHandle(gd, size, out _, sparseCompatible, baseType, storageHint, forceMirrors);
+ return CreateWithHandle(gd, size, out _, sparseCompatible, baseType, forceMirrors);
}
public BufferHandle CreateWithHandle(
@@ -247,10 +249,9 @@ namespace Ryujinx.Graphics.Vulkan
out BufferHolder holder,
bool sparseCompatible = false,
BufferAllocationType baseType = BufferAllocationType.HostMapped,
- BufferHandle storageHint = default,
bool forceMirrors = false)
{
- holder = Create(gd, size, forConditionalRendering: false, sparseCompatible, baseType, storageHint);
+ holder = Create(gd, size, forConditionalRendering: false, sparseCompatible, baseType);
if (holder == null)
{
return BufferHandle.Null;
@@ -387,31 +388,13 @@ namespace Ryujinx.Graphics.Vulkan
int size,
bool forConditionalRendering = false,
bool sparseCompatible = false,
- BufferAllocationType baseType = BufferAllocationType.HostMapped,
- BufferHandle storageHint = default)
+ BufferAllocationType baseType = BufferAllocationType.HostMapped)
{
BufferAllocationType type = baseType;
- BufferHolder storageHintHolder = null;
if (baseType == BufferAllocationType.Auto)
{
- if (gd.IsSharedMemory)
- {
- baseType = BufferAllocationType.HostMapped;
- type = baseType;
- }
- else
- {
- type = size >= BufferHolder.DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocal : BufferAllocationType.HostMapped;
- }
-
- if (storageHint != BufferHandle.Null)
- {
- if (TryGetBuffer(storageHint, out storageHintHolder))
- {
- type = storageHintHolder.DesiredType;
- }
- }
+ type = BufferAllocationType.HostMapped;
}
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) =
@@ -421,11 +404,6 @@ namespace Ryujinx.Graphics.Vulkan
{
var holder = new BufferHolder(gd, _device, buffer, allocation, size, baseType, resultType);
- if (storageHintHolder != null)
- {
- holder.InheritMetrics(storageHintHolder);
- }
-
return holder;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
index a0010e660..3590d5d05 100644
--- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
+++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs
@@ -69,17 +69,7 @@ namespace Ryujinx.Graphics.Vulkan
}
}
- private record struct ArrayRef
- {
- public ShaderStage Stage;
- public T Array;
-
- public ArrayRef(ShaderStage stage, T array)
- {
- Stage = stage;
- Array = array;
- }
- }
+ private readonly record struct ArrayRef(ShaderStage Stage, T Array);
private readonly VulkanRenderer _gd;
private readonly Device _device;
@@ -97,6 +87,9 @@ namespace Ryujinx.Graphics.Vulkan
private ArrayRef[] _textureArrayRefs;
private ArrayRef[] _imageArrayRefs;
+ private ArrayRef[] _textureArrayExtraRefs;
+ private ArrayRef[] _imageArrayExtraRefs;
+
private readonly DescriptorBufferInfo[] _uniformBuffers;
private readonly DescriptorBufferInfo[] _storageBuffers;
private readonly DescriptorImageInfo[] _textures;
@@ -152,6 +145,9 @@ namespace Ryujinx.Graphics.Vulkan
_textureArrayRefs = Array.Empty>();
_imageArrayRefs = Array.Empty>();
+ _textureArrayExtraRefs = Array.Empty>();
+ _imageArrayExtraRefs = Array.Empty>();
+
_uniformBuffers = new DescriptorBufferInfo[Constants.MaxUniformBufferBindings];
_storageBuffers = new DescriptorBufferInfo[Constants.MaxStorageBufferBindings];
_textures = new DescriptorImageInfo[Constants.MaxTexturesPerStage];
@@ -295,8 +291,9 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
- PipelineStageFlags stageFlags = _textureArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags();
- _textureArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags);
+ ref var arrayRef = ref _textureArrayRefs[segment.Binding];
+ PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
+ arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
}
}
}
@@ -315,8 +312,40 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
- PipelineStageFlags stageFlags = _imageArrayRefs[segment.Binding].Stage.ConvertToPipelineStageFlags();
- _imageArrayRefs[segment.Binding].Array?.QueueWriteToReadBarriers(cbs, stageFlags);
+ ref var arrayRef = ref _imageArrayRefs[segment.Binding];
+ PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
+ arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
+ }
+ }
+ }
+
+ for (int setIndex = PipelineBase.DescriptorSetLayouts; setIndex < _program.BindingSegments.Length; setIndex++)
+ {
+ var bindingSegments = _program.BindingSegments[setIndex];
+
+ if (bindingSegments.Length == 0)
+ {
+ continue;
+ }
+
+ ResourceBindingSegment segment = bindingSegments[0];
+
+ if (segment.IsArray)
+ {
+ if (segment.Type == ResourceType.Texture ||
+ segment.Type == ResourceType.Sampler ||
+ segment.Type == ResourceType.TextureAndSampler ||
+ segment.Type == ResourceType.BufferTexture)
+ {
+ ref var arrayRef = ref _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
+ PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
+ arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
+ }
+ else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage)
+ {
+ ref var arrayRef = ref _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts];
+ PipelineStageFlags stageFlags = arrayRef.Stage.ConvertToPipelineStageFlags();
+ arrayRef.Array?.QueueWriteToReadBarriers(cbs, stageFlags);
}
}
}
@@ -495,25 +524,39 @@ namespace Ryujinx.Graphics.Vulkan
public void SetTextureArray(CommandBufferScoped cbs, ShaderStage stage, int binding, ITextureArray array)
{
- if (_textureArrayRefs.Length <= binding)
- {
- Array.Resize(ref _textureArrayRefs, binding + ArrayGrowthSize);
- }
+ ref ArrayRef arrayRef = ref GetArrayRef(ref _textureArrayRefs, binding, ArrayGrowthSize);
- if (_textureArrayRefs[binding].Stage != stage || _textureArrayRefs[binding].Array != array)
+ if (arrayRef.Stage != stage || arrayRef.Array != array)
{
- if (_textureArrayRefs[binding].Array != null)
- {
- _textureArrayRefs[binding].Array.Bound = false;
- }
+ arrayRef.Array?.DecrementBindCount();
if (array is TextureArray textureArray)
{
- textureArray.Bound = true;
+ textureArray.IncrementBindCount();
textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags());
}
- _textureArrayRefs[binding] = new ArrayRef(stage, array as TextureArray);
+ arrayRef = new ArrayRef(stage, array as TextureArray);
+
+ SignalDirty(DirtyFlags.Texture);
+ }
+ }
+
+ public void SetTextureArraySeparate(CommandBufferScoped cbs, ShaderStage stage, int setIndex, ITextureArray array)
+ {
+ ref ArrayRef arrayRef = ref GetArrayRef(ref _textureArrayExtraRefs, setIndex - PipelineBase.DescriptorSetLayouts);
+
+ if (arrayRef.Stage != stage || arrayRef.Array != array)
+ {
+ arrayRef.Array?.DecrementBindCount();
+
+ if (array is TextureArray textureArray)
+ {
+ textureArray.IncrementBindCount();
+ textureArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags());
+ }
+
+ arrayRef = new ArrayRef(stage, array as TextureArray);
SignalDirty(DirtyFlags.Texture);
}
@@ -521,30 +564,56 @@ namespace Ryujinx.Graphics.Vulkan
public void SetImageArray(CommandBufferScoped cbs, ShaderStage stage, int binding, IImageArray array)
{
- if (_imageArrayRefs.Length <= binding)
- {
- Array.Resize(ref _imageArrayRefs, binding + ArrayGrowthSize);
- }
+ ref ArrayRef arrayRef = ref GetArrayRef(ref _imageArrayRefs, binding, ArrayGrowthSize);
- if (_imageArrayRefs[binding].Stage != stage || _imageArrayRefs[binding].Array != array)
+ if (arrayRef.Stage != stage || arrayRef.Array != array)
{
- if (_imageArrayRefs[binding].Array != null)
- {
- _imageArrayRefs[binding].Array.Bound = false;
- }
+ arrayRef.Array?.DecrementBindCount();
if (array is ImageArray imageArray)
{
- imageArray.Bound = true;
+ imageArray.IncrementBindCount();
imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags());
}
- _imageArrayRefs[binding] = new ArrayRef(stage, array as ImageArray);
+ arrayRef = new ArrayRef(stage, array as ImageArray);
SignalDirty(DirtyFlags.Image);
}
}
+ public void SetImageArraySeparate(CommandBufferScoped cbs, ShaderStage stage, int setIndex, IImageArray array)
+ {
+ ref ArrayRef arrayRef = ref GetArrayRef(ref _imageArrayExtraRefs, setIndex - PipelineBase.DescriptorSetLayouts);
+
+ if (arrayRef.Stage != stage || arrayRef.Array != array)
+ {
+ arrayRef.Array?.DecrementBindCount();
+
+ if (array is ImageArray imageArray)
+ {
+ imageArray.IncrementBindCount();
+ imageArray.QueueWriteToReadBarriers(cbs, stage.ConvertToPipelineStageFlags());
+ }
+
+ arrayRef = new ArrayRef(stage, array as ImageArray);
+
+ SignalDirty(DirtyFlags.Image);
+ }
+ }
+
+ private static ref ArrayRef GetArrayRef(ref ArrayRef[] array, int index, int growthSize = 1)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(index);
+
+ if (array.Length <= index)
+ {
+ Array.Resize(ref array, index + growthSize);
+ }
+
+ return ref array[index];
+ }
+
public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan buffers)
{
for (int i = 0; i < buffers.Length; i++)
@@ -594,31 +663,40 @@ namespace Ryujinx.Graphics.Vulkan
return;
}
+ var program = _program;
+
if (_dirty.HasFlag(DirtyFlags.Uniform))
{
- if (_program.UsePushDescriptors)
+ if (program.UsePushDescriptors)
{
- UpdateAndBindUniformBufferPd(cbs, pbp);
+ UpdateAndBindUniformBufferPd(cbs);
}
else
{
- UpdateAndBind(cbs, PipelineBase.UniformSetIndex, pbp);
+ UpdateAndBind(cbs, program, PipelineBase.UniformSetIndex, pbp);
}
}
if (_dirty.HasFlag(DirtyFlags.Storage))
{
- UpdateAndBind(cbs, PipelineBase.StorageSetIndex, pbp);
+ UpdateAndBind(cbs, program, PipelineBase.StorageSetIndex, pbp);
}
if (_dirty.HasFlag(DirtyFlags.Texture))
{
- UpdateAndBind(cbs, PipelineBase.TextureSetIndex, pbp);
+ UpdateAndBind(cbs, program, PipelineBase.TextureSetIndex, pbp);
}
if (_dirty.HasFlag(DirtyFlags.Image))
{
- UpdateAndBind(cbs, PipelineBase.ImageSetIndex, pbp);
+ UpdateAndBind(cbs, program, PipelineBase.ImageSetIndex, pbp);
+ }
+
+ if (program.BindingSegments.Length > PipelineBase.DescriptorSetLayouts)
+ {
+ // Program is using extra sets, we need to bind those too.
+
+ BindExtraSets(cbs, program, pbp);
}
_dirty = DirtyFlags.None;
@@ -658,9 +736,8 @@ namespace Ryujinx.Graphics.Vulkan
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPoint pbp)
+ private void UpdateAndBind(CommandBufferScoped cbs, ShaderCollection program, int setIndex, PipelineBindPoint pbp)
{
- var program = _program;
var bindingSegments = program.BindingSegments[setIndex];
if (bindingSegments.Length == 0)
@@ -869,7 +946,7 @@ namespace Ryujinx.Graphics.Vulkan
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
+ private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs)
{
int sequence = _pdSequence;
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
@@ -933,6 +1010,56 @@ namespace Ryujinx.Graphics.Vulkan
}
}
+ private void BindExtraSets(CommandBufferScoped cbs, ShaderCollection program, PipelineBindPoint pbp)
+ {
+ 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)
+ {
+ DescriptorSet[] sets = null;
+
+ if (segment.Type == ResourceType.Texture ||
+ segment.Type == ResourceType.Sampler ||
+ segment.Type == ResourceType.TextureAndSampler ||
+ segment.Type == ResourceType.BufferTexture)
+ {
+ sets = _textureArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets(
+ _device,
+ cbs,
+ _templateUpdater,
+ program,
+ setIndex,
+ _dummyTexture,
+ _dummySampler);
+ }
+ else if (segment.Type == ResourceType.Image || segment.Type == ResourceType.BufferImage)
+ {
+ sets = _imageArrayExtraRefs[setIndex - PipelineBase.DescriptorSetLayouts].Array.GetDescriptorSets(
+ _device,
+ cbs,
+ _templateUpdater,
+ program,
+ setIndex,
+ _dummyTexture);
+ }
+
+ if (sets != null)
+ {
+ _gd.Api.CmdBindDescriptorSets(cbs.CommandBuffer, pbp, _program.PipelineLayout, (uint)setIndex, 1, sets, 0, ReadOnlySpan.Empty);
+ }
+ }
+ }
+ }
+
public void SignalCommandBufferChange()
{
_updateDescriptorCacheCbIndex = true;
diff --git a/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs b/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs
index f9243bf83..9d1fd9ffd 100644
--- a/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs
+++ b/src/Ryujinx.Graphics.Vulkan/EnumConversion.cs
@@ -424,10 +424,20 @@ namespace Ryujinx.Graphics.Vulkan
public static BufferAllocationType Convert(this BufferAccess access)
{
- if (access.HasFlag(BufferAccess.FlushPersistent) || access.HasFlag(BufferAccess.Stream))
+ BufferAccess memType = access & BufferAccess.MemoryTypeMask;
+
+ if (memType == BufferAccess.HostMemory || access.HasFlag(BufferAccess.Stream))
{
return BufferAllocationType.HostMapped;
}
+ else if (memType == BufferAccess.DeviceMemory)
+ {
+ return BufferAllocationType.DeviceLocal;
+ }
+ else if (memType == BufferAccess.DeviceMemoryMapped)
+ {
+ return BufferAllocationType.DeviceLocalMapped;
+ }
return BufferAllocationType.Auto;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
index 8079e5ff9..ea0fd42e5 100644
--- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
+++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs
@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Vulkan
public VkFormat[] AttachmentFormats { get; }
public int[] AttachmentIndices { get; }
public uint AttachmentIntegerFormatMask { get; }
+ public bool LogicOpsAllowed { get; }
public int AttachmentsCount { get; }
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[^1] : -1;
@@ -32,7 +33,9 @@ namespace Ryujinx.Graphics.Vulkan
public FramebufferParams(Device device, TextureView view, uint width, uint height)
{
- bool isDepthStencil = view.Info.Format.IsDepthOrStencil();
+ var format = view.Info.Format;
+
+ bool isDepthStencil = format.IsDepthOrStencil();
_device = device;
_attachments = new[] { view.GetImageViewForAttachment() };
@@ -56,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
AttachmentSamples = new[] { (uint)view.Info.Samples };
AttachmentFormats = new[] { view.VkFormat };
AttachmentIndices = isDepthStencil ? Array.Empty() : new[] { 0 };
+ AttachmentIntegerFormatMask = format.IsInteger() ? 1u : 0u;
+ LogicOpsAllowed = !format.IsFloatOrSrgb();
AttachmentsCount = 1;
@@ -85,6 +90,7 @@ namespace Ryujinx.Graphics.Vulkan
int index = 0;
int bindIndex = 0;
uint attachmentIntegerFormatMask = 0;
+ bool allFormatsFloatOrSrgb = colorsCount != 0;
foreach (ITexture color in colors)
{
@@ -101,11 +107,15 @@ namespace Ryujinx.Graphics.Vulkan
AttachmentFormats[index] = texture.VkFormat;
AttachmentIndices[index] = bindIndex;
- if (texture.Info.Format.IsInteger())
+ var format = texture.Info.Format;
+
+ if (format.IsInteger())
{
attachmentIntegerFormatMask |= 1u << bindIndex;
}
+ allFormatsFloatOrSrgb &= format.IsFloatOrSrgb();
+
width = Math.Min(width, (uint)texture.Width);
height = Math.Min(height, (uint)texture.Height);
layers = Math.Min(layers, (uint)texture.Layers);
@@ -120,6 +130,7 @@ namespace Ryujinx.Graphics.Vulkan
}
AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
+ LogicOpsAllowed = !allFormatsFloatOrSrgb;
if (depthStencil is TextureView dsTexture && dsTexture.Valid)
{
diff --git a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs
index 38a5b6b48..e42750d3c 100644
--- a/src/Ryujinx.Graphics.Vulkan/ImageArray.cs
+++ b/src/Ryujinx.Graphics.Vulkan/ImageArray.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
- class ImageArray : IImageArray
+ class ImageArray : ResourceArray, IImageArray
{
private readonly VulkanRenderer _gd;
@@ -29,8 +29,6 @@ namespace Ryujinx.Graphics.Vulkan
private readonly bool _isBuffer;
- public bool Bound;
-
public ImageArray(VulkanRenderer gd, int size, bool isBuffer)
{
_gd = gd;
@@ -97,8 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_cachedCommandBufferIndex = -1;
_storages = null;
-
- _gd.PipelineInternal.ForceImageDirty();
+ SetDirty(_gd);
}
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
@@ -175,5 +172,47 @@ namespace Ryujinx.Graphics.Vulkan
return bufferTextures;
}
+
+ public DescriptorSet[] GetDescriptorSets(
+ Device device,
+ CommandBufferScoped cbs,
+ DescriptorSetTemplateUpdater templateUpdater,
+ ShaderCollection program,
+ int setIndex,
+ TextureView dummyTexture)
+ {
+ if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets))
+ {
+ // We still need to ensure the current command buffer holds a reference to all used textures.
+
+ if (!_isBuffer)
+ {
+ GetImageInfos(_gd, cbs, dummyTexture);
+ }
+ else
+ {
+ GetBufferViews(cbs);
+ }
+
+ return sets;
+ }
+
+ DescriptorSetTemplate template = program.Templates[setIndex];
+
+ DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
+
+ if (!_isBuffer)
+ {
+ tu.Push(GetImageInfos(_gd, cbs, dummyTexture));
+ }
+ else
+ {
+ tu.Push(GetBufferViews(cbs));
+ }
+
+ templateUpdater.Commit(_gd, device, sets[0]);
+
+ return sets;
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
index 0bce3b72d..806b872bc 100644
--- a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using System;
@@ -165,14 +166,15 @@ namespace Ryujinx.Graphics.Vulkan
/// True if all fences were signaled before the timeout expired, false otherwise
private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
{
- Span fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
+ using SpanOwner fenceHoldersOwner = SpanOwner.Rent(CommandBufferPool.MaxCommandBuffers);
+ Span fenceHolders = fenceHoldersOwner.Span;
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
Span fences = stackalloc Fence[count];
int fenceCount = 0;
- for (int i = 0; i < count; i++)
+ for (int i = 0; i < fences.Length; i++)
{
if (fenceHolders[i].TryGet(out Fence fence))
{
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 41ab84d94..918de59b7 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -751,14 +751,12 @@ namespace Ryujinx.Graphics.Vulkan
_vertexBufferUpdater.Commit(Cbs);
}
-#pragma warning disable CA1822 // Mark member as static
public void SetAlphaTest(bool enable, float reference, CompareOp op)
{
// This is currently handled using shader specialization, as Vulkan does not support alpha test.
// In the future, we may want to use this to write the reference value into the support buffer,
// to avoid creating one version of the shader per reference value used.
}
-#pragma warning restore CA1822
public void SetBlendState(AdvancedBlendDescriptor blend)
{
@@ -903,6 +901,11 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSetUpdater.SetImageArray(Cbs, stage, binding, array);
}
+ public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
+ {
+ _descriptorSetUpdater.SetImageArraySeparate(Cbs, stage, setIndex, array);
+ }
+
public void SetIndexBuffer(BufferRange buffer, IndexType type)
{
if (buffer.Handle != BufferHandle.Null)
@@ -945,7 +948,6 @@ namespace Ryujinx.Graphics.Vulkan
// TODO: Default levels (likely needs emulation on shaders?)
}
-#pragma warning disable CA1822 // Mark member as static
public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
{
// TODO.
@@ -955,7 +957,6 @@ namespace Ryujinx.Graphics.Vulkan
{
// TODO.
}
-#pragma warning restore CA1822
public void SetPrimitiveRestart(bool enable, int index)
{
@@ -1156,6 +1157,11 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSetUpdater.SetTextureArray(Cbs, stage, binding, array);
}
+ public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
+ {
+ _descriptorSetUpdater.SetTextureArraySeparate(Cbs, stage, setIndex, array);
+ }
+
public void SetTransformFeedbackBuffers(ReadOnlySpan buffers)
{
PauseTransformFeedbackInternal();
@@ -1186,12 +1192,10 @@ namespace Ryujinx.Graphics.Vulkan
_descriptorSetUpdater.SetUniformBuffers(CommandBuffer, buffers);
}
-#pragma warning disable CA1822 // Mark member as static
public void SetUserClipDistance(int index, bool enableClip)
{
// TODO.
}
-#pragma warning restore CA1822
public void SetVertexAttribs(ReadOnlySpan vertexAttribs)
{
@@ -1498,6 +1502,7 @@ namespace Ryujinx.Graphics.Vulkan
var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
_newState.Internal.AttachmentIntegerFormatMask = FramebufferParams.AttachmentIntegerFormatMask;
+ _newState.Internal.LogicOpsAllowed = FramebufferParams.LogicOpsAllowed;
for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++)
{
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
index 95b480a5e..7d124c830 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
@@ -180,9 +180,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 +205,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;
@@ -302,6 +293,7 @@ namespace Ryujinx.Graphics.Vulkan
int attachmentCount = 0;
int maxColorAttachmentIndex = -1;
uint attachmentIntegerFormatMask = 0;
+ bool allFormatsFloatOrSrgb = true;
for (int i = 0; i < Constants.MaxRenderTargets; i++)
{
@@ -314,6 +306,8 @@ namespace Ryujinx.Graphics.Vulkan
{
attachmentIntegerFormatMask |= 1u << i;
}
+
+ allFormatsFloatOrSrgb &= state.AttachmentFormats[i].IsFloatOrSrgb();
}
}
@@ -325,6 +319,7 @@ namespace Ryujinx.Graphics.Vulkan
pipeline.ColorBlendAttachmentStateCount = (uint)(maxColorAttachmentIndex + 1);
pipeline.VertexAttributeDescriptionsCount = (uint)Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
pipeline.Internal.AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
+ pipeline.Internal.LogicOpsAllowed = attachmentCount == 0 || !allFormatsFloatOrSrgb;
return pipeline;
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
index 4987548cd..357d517eb 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineFull.cs
@@ -222,20 +222,6 @@ namespace Ryujinx.Graphics.Vulkan
}
}
- private void TryBackingSwaps()
- {
- CommandBufferScoped? cbs = null;
-
- _backingSwaps.RemoveAll(holder => holder.TryBackingSwap(ref cbs));
-
- cbs?.Dispose();
- }
-
- public void AddBackingSwap(BufferHolder holder)
- {
- _backingSwaps.Add(holder);
- }
-
public void Restore()
{
if (Pipeline != null)
@@ -291,8 +277,6 @@ namespace Ryujinx.Graphics.Vulkan
Gd.ResetCounterPool();
- TryBackingSwaps();
-
Restore();
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
index fb1f0a5ff..ae296b033 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs
@@ -3,6 +3,8 @@ 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
{
@@ -14,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;
@@ -27,6 +30,41 @@ namespace Ryujinx.Graphics.Vulkan
private int _dsLastCbIndex;
private int _dsLastSubmissionCount;
+ private struct ManualDescriptorSetEntry
+ {
+ public Auto DescriptorSet;
+ public uint CbRefMask;
+ public bool InUse;
+
+ public ManualDescriptorSetEntry(Auto descriptorSet, int cbIndex)
+ {
+ DescriptorSet = descriptorSet;
+ CbRefMask = 1u << cbIndex;
+ InUse = true;
+ }
+ }
+
+ private readonly struct PendingManualDsConsumption
+ {
+ public FenceHolder Fence { get; }
+ public int CommandBufferIndex { get; }
+ public int SetIndex { get; }
+ public int CacheIndex { get; }
+
+ public PendingManualDsConsumption(FenceHolder fence, int commandBufferIndex, int setIndex, int cacheIndex)
+ {
+ Fence = fence;
+ CommandBufferIndex = commandBufferIndex;
+ SetIndex = setIndex;
+ CacheIndex = cacheIndex;
+ fence.Get();
+ }
+ }
+
+ private readonly List[] _manualDsCache;
+ private readonly Queue _pendingManualDsConsumptions;
+ private readonly Queue[] _freeManualDsCacheEntries;
+
private readonly Dictionary _pdTemplates;
private readonly ResourceDescriptorCollection _pdDescriptors;
private long _lastPdUsage;
@@ -50,6 +88,9 @@ namespace Ryujinx.Graphics.Vulkan
}
_dsCacheCursor = new int[setsCount];
+ _manualDsCache = new List[setsCount];
+ _pendingManualDsConsumptions = new Queue();
+ _freeManualDsCacheEntries = new Queue[setsCount];
}
public PipelineLayoutCacheEntry(
@@ -58,7 +99,11 @@ namespace Ryujinx.Graphics.Vulkan
ReadOnlyCollection setDescriptors,
bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
{
- (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
+ ResourceLayouts layouts = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
+
+ DescriptorSetLayouts = layouts.DescriptorSetLayouts;
+ DescriptorSetLayoutsUpdateAfterBind = layouts.DescriptorSetLayoutsUpdateAfterBind;
+ PipelineLayout = layouts.PipelineLayout;
_consumedDescriptorsPerSet = new int[setDescriptors.Count];
_poolSizes = new DescriptorPoolSize[setDescriptors.Count][];
@@ -113,7 +158,7 @@ namespace Ryujinx.Graphics.Vulkan
_poolSizes[setIndex],
setIndex,
_consumedDescriptorsPerSet[setIndex],
- false);
+ DescriptorSetLayoutsUpdateAfterBind[setIndex]);
list.Add(dsc);
isNew = true;
@@ -124,6 +169,101 @@ namespace Ryujinx.Graphics.Vulkan
return list[index];
}
+ public Auto GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex)
+ {
+ FreeCompletedManualDescriptorSets();
+
+ var list = _manualDsCache[setIndex] ??= new();
+ var span = CollectionsMarshal.AsSpan(list);
+
+ Queue freeQueue = _freeManualDsCacheEntries[setIndex];
+
+ // Do we have at least one freed descriptor set? If so, just use that.
+ if (freeQueue != null && freeQueue.TryDequeue(out int freeIndex))
+ {
+ ref ManualDescriptorSetEntry entry = ref span[freeIndex];
+
+ Debug.Assert(!entry.InUse && entry.CbRefMask == 0);
+
+ entry.InUse = true;
+ entry.CbRefMask = 1u << cbs.CommandBufferIndex;
+ cacheIndex = freeIndex;
+
+ _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],
+ DescriptorSetLayoutsUpdateAfterBind[setIndex]);
+
+ cacheIndex = list.Count;
+ list.Add(new ManualDescriptorSetEntry(dsc, cbs.CommandBufferIndex));
+ _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex));
+
+ return dsc;
+ }
+
+ public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex)
+ {
+ FreeCompletedManualDescriptorSets();
+
+ var list = _manualDsCache[setIndex];
+ var span = CollectionsMarshal.AsSpan(list);
+ ref var entry = ref span[cacheIndex];
+
+ uint cbMask = 1u << cbs.CommandBufferIndex;
+
+ if ((entry.CbRefMask & cbMask) == 0)
+ {
+ entry.CbRefMask |= cbMask;
+
+ _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex));
+ }
+ }
+
+ private void FreeCompletedManualDescriptorSets()
+ {
+ FenceHolder signalledFence = null;
+ while (_pendingManualDsConsumptions.TryPeek(out var pds) && (pds.Fence == signalledFence || pds.Fence.IsSignaled()))
+ {
+ signalledFence = pds.Fence; // Already checked - don't need to do it again.
+ var dequeued = _pendingManualDsConsumptions.Dequeue();
+ Debug.Assert(dequeued.Fence == pds.Fence);
+ pds.Fence.Put();
+
+ var span = CollectionsMarshal.AsSpan(_manualDsCache[dequeued.SetIndex]);
+ ref var entry = ref span[dequeued.CacheIndex];
+ entry.CbRefMask &= ~(1u << dequeued.CommandBufferIndex);
+
+ if (!entry.InUse && entry.CbRefMask == 0)
+ {
+ // If not in use by any array, and not bound to any command buffer, the descriptor set can be re-used immediately.
+ (_freeManualDsCacheEntries[dequeued.SetIndex] ??= new()).Enqueue(dequeued.CacheIndex);
+ }
+ }
+ }
+
+ public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex)
+ {
+ var list = _manualDsCache[setIndex];
+ var span = CollectionsMarshal.AsSpan(list);
+
+ span[cacheIndex].InUse = false;
+
+ if (span[cacheIndex].CbRefMask == 0)
+ {
+ // This is no longer in use by any array, so if not bound to any command buffer, the descriptor set can be re-used immediately.
+ (_freeManualDsCacheEntries[setIndex] ??= new()).Enqueue(cacheIndex);
+ }
+ }
+
private static Span GetDescriptorPoolSizes(Span output, ResourceDescriptorCollection setDescriptor, uint multiplier)
{
int count = 0;
@@ -204,6 +344,21 @@ namespace Ryujinx.Graphics.Vulkan
}
}
+ for (int i = 0; i < _manualDsCache.Length; i++)
+ {
+ if (_manualDsCache[i] == null)
+ {
+ continue;
+ }
+
+ for (int j = 0; j < _manualDsCache[i].Count; j++)
+ {
+ _manualDsCache[i][j].DescriptorSet.Dispose();
+ }
+
+ _manualDsCache[i].Clear();
+ }
+
_gd.Api.DestroyPipelineLayout(_device, PipelineLayout, null);
for (int i = 0; i < DescriptorSetLayouts.Length; i++)
@@ -211,6 +366,11 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null);
}
+ while (_pendingManualDsConsumptions.TryDequeue(out var pds))
+ {
+ pds.Fence.Put();
+ }
+
_descriptorSetManager.Dispose();
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
index 8bf286c65..bca119f6a 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs
@@ -1,18 +1,23 @@
+using Ryujinx.Common.Memory;
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
+using System;
using System.Collections.ObjectModel;
namespace Ryujinx.Graphics.Vulkan
{
+ record struct ResourceLayouts(DescriptorSetLayout[] DescriptorSetLayouts, bool[] DescriptorSetLayoutsUpdateAfterBind, PipelineLayout PipelineLayout);
+
static class PipelineLayoutFactory
{
- public static unsafe (DescriptorSetLayout[], PipelineLayout) Create(
+ public static unsafe ResourceLayouts Create(
VulkanRenderer gd,
Device device,
ReadOnlyCollection setDescriptors,
bool usePushDescriptors)
{
DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count];
+ bool[] updateAfterBindFlags = new bool[setDescriptors.Count];
bool isMoltenVk = gd.IsMoltenVk;
@@ -32,10 +37,11 @@ namespace Ryujinx.Graphics.Vulkan
DescriptorSetLayoutBinding[] layoutBindings = new DescriptorSetLayoutBinding[rdc.Descriptors.Count];
+ bool hasArray = false;
+
for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++)
{
ResourceDescriptor descriptor = rdc.Descriptors[descIndex];
-
ResourceStages stages = descriptor.Stages;
if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk)
@@ -52,16 +58,37 @@ namespace Ryujinx.Graphics.Vulkan
DescriptorCount = (uint)descriptor.Count,
StageFlags = stages.Convert(),
};
+
+ if (descriptor.Count > 1)
+ {
+ hasArray = true;
+ }
}
fixed (DescriptorSetLayoutBinding* pLayoutBindings = layoutBindings)
{
+ DescriptorSetLayoutCreateFlags flags = DescriptorSetLayoutCreateFlags.None;
+
+ if (usePushDescriptors && setIndex == 0)
+ {
+ flags = DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr;
+ }
+
+ if (gd.Vendor == Vendor.Intel && hasArray)
+ {
+ // Some vendors (like Intel) have low per-stage limits.
+ // We must set the flag if we exceed those limits.
+ flags |= DescriptorSetLayoutCreateFlags.UpdateAfterBindPoolBit;
+
+ updateAfterBindFlags[setIndex] = true;
+ }
+
var descriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo
{
SType = StructureType.DescriptorSetLayoutCreateInfo,
PBindings = pLayoutBindings,
BindingCount = (uint)layoutBindings.Length,
- Flags = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None,
+ Flags = flags,
};
gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError();
@@ -82,7 +109,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError();
}
- return (layouts, layout);
+ return new ResourceLayouts(layouts, updateAfterBindFlags, layout);
}
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
index 49c12b376..2a8f93081 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineState.cs
@@ -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;
@@ -560,10 +533,14 @@ namespace Ryujinx.Graphics.Vulkan
}
}
+ // Vendors other than NVIDIA have a bug where it enables logical operations even for float formats,
+ // so we need to force disable them here.
+ bool logicOpEnable = LogicOpEnable && (gd.Vendor == Vendor.Nvidia || Internal.LogicOpsAllowed);
+
var colorBlendState = new PipelineColorBlendStateCreateInfo
{
SType = StructureType.PipelineColorBlendStateCreateInfo,
- LogicOpEnable = LogicOpEnable,
+ LogicOpEnable = logicOpEnable,
LogicOp = LogicOp,
AttachmentCount = ColorBlendAttachmentStateCount,
PAttachments = pColorBlendAttachmentState,
@@ -587,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
@@ -628,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);
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
index 3448d9743..c56224216 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineUid.cs
@@ -17,23 +17,21 @@ namespace Ryujinx.Graphics.Vulkan
public ulong Id4;
public ulong Id5;
public ulong Id6;
+
public ulong Id7;
-
public ulong Id8;
- public ulong Id9;
- private readonly uint VertexAttributeDescriptionsCount => (byte)((Id6 >> 38) & 0xFF);
- private readonly uint VertexBindingDescriptionsCount => (byte)((Id6 >> 46) & 0xFF);
- private readonly uint ColorBlendAttachmentStateCount => (byte)((Id7 >> 8) & 0xFF);
- private readonly bool HasDepthStencil => ((Id7 >> 63) & 0x1) != 0UL;
+ private readonly uint VertexAttributeDescriptionsCount => (byte)((Id5 >> 38) & 0xFF);
+ private readonly uint VertexBindingDescriptionsCount => (byte)((Id5 >> 46) & 0xFF);
+ private readonly uint ColorBlendAttachmentStateCount => (byte)((Id6 >> 8) & 0xFF);
+ private readonly bool HasDepthStencil => ((Id6 >> 63) & 0x1) != 0UL;
public Array32 VertexAttributeDescriptions;
public Array33 VertexBindingDescriptions;
- public Array16 Viewports;
- public Array16 Scissors;
public Array8 ColorBlendAttachmentState;
public Array9 AttachmentFormats;
public uint AttachmentIntegerFormatMask;
+ public bool LogicOpsAllowed;
public readonly override bool Equals(object obj)
{
@@ -44,7 +42,7 @@ namespace Ryujinx.Graphics.Vulkan
{
if (!Unsafe.As>(ref Id0).Equals(Unsafe.As>(ref other.Id0)) ||
!Unsafe.As>(ref Id4).Equals(Unsafe.As>(ref other.Id4)) ||
- !Unsafe.As>(ref Id8).Equals(Unsafe.As>(ref other.Id8)))
+ !Unsafe.As>(ref Id7).Equals(Unsafe.As>(ref other.Id7)))
{
return false;
}
@@ -87,8 +85,7 @@ namespace Ryujinx.Graphics.Vulkan
Id5 * 23 ^
Id6 * 23 ^
Id7 * 23 ^
- Id8 * 23 ^
- Id9 * 23;
+ Id8 * 23;
for (int i = 0; i < (int)VertexAttributeDescriptionsCount; i++)
{
diff --git a/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs b/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs
new file mode 100644
index 000000000..0880a10f0
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/ResourceArray.cs
@@ -0,0 +1,74 @@
+using Silk.NET.Vulkan;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class ResourceArray : IDisposable
+ {
+ private DescriptorSet[] _cachedDescriptorSets;
+
+ private ShaderCollection _cachedDscProgram;
+ private int _cachedDscSetIndex;
+ private int _cachedDscIndex;
+
+ private int _bindCount;
+
+ protected void SetDirty(VulkanRenderer gd)
+ {
+ ReleaseDescriptorSet();
+
+ if (_bindCount != 0)
+ {
+ gd.PipelineInternal.ForceTextureDirty();
+ }
+ }
+
+ public bool TryGetCachedDescriptorSets(CommandBufferScoped cbs, ShaderCollection program, int setIndex, out DescriptorSet[] sets)
+ {
+ if (_cachedDescriptorSets != null)
+ {
+ _cachedDscProgram.UpdateManualDescriptorSetCollectionOwnership(cbs, _cachedDscSetIndex, _cachedDscIndex);
+
+ sets = _cachedDescriptorSets;
+
+ return true;
+ }
+
+ var dsc = program.GetNewManualDescriptorSetCollection(cbs, setIndex, out _cachedDscIndex).Get(cbs);
+
+ sets = dsc.GetSets();
+
+ _cachedDescriptorSets = sets;
+ _cachedDscProgram = program;
+ _cachedDscSetIndex = setIndex;
+
+ return false;
+ }
+
+ public void IncrementBindCount()
+ {
+ _bindCount++;
+ }
+
+ public void DecrementBindCount()
+ {
+ int newBindCount = --_bindCount;
+ Debug.Assert(newBindCount >= 0);
+ }
+
+ private void ReleaseDescriptorSet()
+ {
+ if (_cachedDescriptorSets != null)
+ {
+ _cachedDscProgram.ReleaseManualDescriptorSetCollection(_cachedDscSetIndex, _cachedDscIndex);
+ _cachedDescriptorSets = null;
+ }
+ }
+
+ public void Dispose()
+ {
+ ReleaseDescriptorSet();
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
index 178546983..f9637789e 100644
--- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
+++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs
@@ -604,6 +604,21 @@ namespace Ryujinx.Graphics.Vulkan
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
}
+ public Auto GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int 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)
+ {
+ _plce.ReleaseManualDescriptorSetCollection(setIndex, cacheIndex);
+ }
+
public bool HasSameLayout(ShaderCollection other)
{
return other != null && _plce == other._plce;
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs
index 6ef9087bc..31c408d64 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureArray.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureArray.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
- class TextureArray : ITextureArray
+ class TextureArray : ResourceArray, ITextureArray
{
private readonly VulkanRenderer _gd;
@@ -29,8 +29,6 @@ namespace Ryujinx.Graphics.Vulkan
private readonly bool _isBuffer;
- public bool Bound;
-
public TextureArray(VulkanRenderer gd, int size, bool isBuffer)
{
_gd = gd;
@@ -106,8 +104,7 @@ namespace Ryujinx.Graphics.Vulkan
{
_cachedCommandBufferIndex = -1;
_storages = null;
-
- _gd.PipelineInternal.ForceTextureDirty();
+ SetDirty(_gd);
}
public void QueueWriteToReadBarriers(CommandBufferScoped cbs, PipelineStageFlags stageFlags)
@@ -190,5 +187,48 @@ namespace Ryujinx.Graphics.Vulkan
return bufferTextures;
}
+
+ public DescriptorSet[] GetDescriptorSets(
+ Device device,
+ CommandBufferScoped cbs,
+ DescriptorSetTemplateUpdater templateUpdater,
+ ShaderCollection program,
+ int setIndex,
+ TextureView dummyTexture,
+ SamplerHolder dummySampler)
+ {
+ if (TryGetCachedDescriptorSets(cbs, program, setIndex, out DescriptorSet[] sets))
+ {
+ // We still need to ensure the current command buffer holds a reference to all used textures.
+
+ if (!_isBuffer)
+ {
+ GetImageInfos(_gd, cbs, dummyTexture, dummySampler);
+ }
+ else
+ {
+ GetBufferViews(cbs);
+ }
+
+ return sets;
+ }
+
+ DescriptorSetTemplate template = program.Templates[setIndex];
+
+ DescriptorSetTemplateWriter tu = templateUpdater.Begin(template);
+
+ if (!_isBuffer)
+ {
+ tu.Push(GetImageInfos(_gd, cbs, dummyTexture, dummySampler));
+ }
+ else
+ {
+ tu.Push(GetBufferViews(cbs));
+ }
+
+ templateUpdater.Commit(_gd, device, sets[0]);
+
+ return sets;
+ }
}
}
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
index 230dbd4e8..1aaf2fbbe 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs
@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities.SupportsShaderStorageImageMultisample);
- var flags = ImageCreateFlags.CreateMutableFormatBit;
+ var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
// This flag causes mipmapped texture arrays to break on AMD GCN, so for that copy dependencies are forced for aliasing as cube.
bool isCube = info.Target == Target.Cubemap || info.Target == Target.CubemapArray;
diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
index f2aaf4693..520668028 100644
--- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -100,7 +100,7 @@ namespace Ryujinx.Graphics.Vulkan
unsafe Auto CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags)
{
- var usage = new ImageViewUsageCreateInfo
+ var imageViewUsage = new ImageViewUsageCreateInfo
{
SType = StructureType.ImageViewUsageCreateInfo,
Usage = usageFlags,
@@ -114,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
Format = format,
Components = cm,
SubresourceRange = sr,
- PNext = &usage,
+ PNext = &imageViewUsage,
};
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
@@ -123,7 +123,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageUsageFlags shaderUsage = ImageUsageFlags.SampledBit;
- if (info.Format.IsImageCompatible())
+ if (info.Format.IsImageCompatible() && (_gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample()))
{
shaderUsage |= ImageUsageFlags.StorageBit;
}
@@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan
}
else
{
- subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
+ subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, 1, (uint)firstLayer, (uint)info.Depth);
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage);
}
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
index d59ca7e0e..5a9844cb9 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
@@ -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;
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index 8ef05de36..86a347e01 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -486,12 +486,7 @@ namespace Ryujinx.Graphics.Vulkan
public BufferHandle CreateBuffer(int size, BufferAccess access)
{
- return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), default, access == BufferAccess.Stream);
- }
-
- public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint)
- {
- return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), storageHint);
+ return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), access.HasFlag(BufferAccess.Stream));
}
public BufferHandle CreateBuffer(nint pointer, int size)
@@ -675,9 +670,23 @@ namespace Ryujinx.Graphics.Vulkan
var limits = _physicalDevice.PhysicalDeviceProperties.Limits;
var mainQueueProperties = _physicalDevice.QueueFamilyProperties[QueueFamilyIndex];
+ SystemMemoryType memoryType;
+
+ if (IsSharedMemory)
+ {
+ memoryType = SystemMemoryType.UnifiedMemory;
+ }
+ else
+ {
+ memoryType = Vendor == Vendor.Nvidia ?
+ SystemMemoryType.DedicatedMemorySlowStorage :
+ SystemMemoryType.DedicatedMemory;
+ }
+
return new Capabilities(
api: TargetApi.Vulkan,
GpuVendor,
+ memoryType: memoryType,
hasFrontFacingBug: IsIntelWindows,
hasVectorIndexingBug: Vendor == Vendor.Qualcomm,
needsFragmentOutputSpecialization: IsMoltenVk,
@@ -719,6 +728,12 @@ namespace Ryujinx.Graphics.Vulkan
supportsViewportSwizzle: false,
supportsIndirectParameters: true,
supportsDepthClipControl: Capabilities.SupportsDepthClipControl,
+ uniformBufferSetIndex: PipelineBase.UniformSetIndex,
+ storageBufferSetIndex: PipelineBase.StorageSetIndex,
+ textureSetIndex: PipelineBase.TextureSetIndex,
+ imageSetIndex: PipelineBase.ImageSetIndex,
+ extraSetBaseIndex: PipelineBase.DescriptorSetLayouts,
+ maximumExtraSets: Math.Max(0, (int)limits.MaxBoundDescriptorSets - PipelineBase.DescriptorSetLayouts),
maximumUniformBuffersPerStage: Constants.MaxUniformBuffersPerStage,
maximumStorageBuffersPerStage: Constants.MaxStorageBuffersPerStage,
maximumTexturesPerStage: Constants.MaxTexturesPerStage,
diff --git a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs
index e502254be..bd71c7933 100644
--- a/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs
+++ b/src/Ryujinx.Gtk3/Input/GTK3/GTK3KeyboardDriver.cs
@@ -81,6 +81,11 @@ namespace Ryujinx.Input.GTK3
return _pressedKeys.Contains(nativeKey);
}
+ public void Clear()
+ {
+ _pressedKeys.Clear();
+ }
+
public IGamepad GetGamepad(string id)
{
if (!_keyboardIdentifers[0].Equals(id))
diff --git a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs
index 1d918d21b..b3f509a09 100644
--- a/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs
+++ b/src/Ryujinx.Gtk3/UI/Applet/GtkHostUIHandler.cs
@@ -107,6 +107,8 @@ namespace Ryujinx.UI.Applet
swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
swkbdDialog.SetInputValidation(args.KeyboardMode);
+ ((MainWindow)_parent).RendererWidget.NpadManager.BlockInputUpdates();
+
if (swkbdDialog.Run() == (int)ResponseType.Ok)
{
inputText = swkbdDialog.InputEntry.Text;
@@ -128,6 +130,7 @@ namespace Ryujinx.UI.Applet
});
dialogCloseEvent.WaitOne();
+ ((MainWindow)_parent).RendererWidget.NpadManager.UnblockInputUpdates();
userText = error ? null : inputText;
diff --git a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
index 7cddc362b..d9ecd47b7 100644
--- a/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
+++ b/src/Ryujinx.Gtk3/UI/Windows/AvatarWindow.cs
@@ -233,7 +233,7 @@ namespace Ryujinx.UI.Windows
reader.ReadInt64(); // Padding
byte[] input = new byte[stream.Length - stream.Position];
- stream.Read(input, 0, input.Length);
+ stream.ReadExactly(input, 0, input.Length);
long inputOffset = 0;
diff --git a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
index 6595ecef2..8f104b0b7 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
@@ -616,7 +616,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
}
- ArrayPool