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.Shared.Return(syncObjsArray); + ArrayPool.Shared.Return(syncObjsArray, true); return result; } @@ -1546,8 +1546,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall #pragma warning disable CA1822 // Mark member as static public Result SetProcessMemoryPermission( int handle, - [PointerSized] ulong src, - [PointerSized] ulong size, + ulong src, + ulong size, KMemoryPermission permission) { if (!PageAligned(src)) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs index 905c61d66..8ef77902c 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -28,42 +28,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private SchedulingState _state; - private AutoResetEvent _idleInterruptEvent; - private readonly object _idleInterruptEventLock; - private KThread _previousThread; private KThread _currentThread; - private readonly KThread _idleThread; + + private int _coreIdleLock; + private bool _idleSignalled = true; + private bool _idleActive = true; + private long _idleTimeRunning; public KThread PreviousThread => _previousThread; public KThread CurrentThread => _currentThread; public long LastContextSwitchTime { get; private set; } - public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning; + public long TotalIdleTimeTicks => _idleTimeRunning; public KScheduler(KernelContext context, int coreId) { _context = context; _coreId = coreId; - _idleInterruptEvent = new AutoResetEvent(false); - _idleInterruptEventLock = new object(); - - KThread idleThread = CreateIdleThread(context, coreId); - - _currentThread = idleThread; - _idleThread = idleThread; - - idleThread.StartHostThread(); - idleThread.SchedulerWaitEvent.Set(); - } - - private KThread CreateIdleThread(KernelContext context, int cpuCore) - { - KThread idleThread = new(context); - - idleThread.Initialize(0UL, 0UL, 0UL, PrioritiesCount, cpuCore, null, ThreadType.Dummy, IdleThreadLoop); - - return idleThread; + _currentThread = null; } public static ulong SelectThreads(KernelContext context) @@ -237,39 +220,64 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread; // Request the thread running on that core to stop and reschedule, if we have one. - if (threadToSignal != context.Schedulers[coreToSignal]._idleThread) - { - threadToSignal.Context.RequestInterrupt(); - } + threadToSignal?.Context.RequestInterrupt(); // If the core is idle, ensure that the idle thread is awaken. - context.Schedulers[coreToSignal]._idleInterruptEvent.Set(); + context.Schedulers[coreToSignal].NotifyIdleThread(); scheduledCoresMask &= ~(1UL << coreToSignal); } } - private void IdleThreadLoop() + private void ActivateIdleThread() { - while (_context.Running) + while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0) + { + Thread.SpinWait(1); + } + + Thread.MemoryBarrier(); + + // Signals that idle thread is now active on this core. + _idleActive = true; + + TryLeaveIdle(); + + Interlocked.Exchange(ref _coreIdleLock, 0); + } + + private void NotifyIdleThread() + { + while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0) + { + Thread.SpinWait(1); + } + + Thread.MemoryBarrier(); + + // Signals that the idle core may be able to exit idle. + _idleSignalled = true; + + TryLeaveIdle(); + + Interlocked.Exchange(ref _coreIdleLock, 0); + } + + public void TryLeaveIdle() + { + if (_idleSignalled && _idleActive) { _state.NeedsScheduling = false; Thread.MemoryBarrier(); - KThread nextThread = PickNextThread(_state.SelectedThread); + KThread nextThread = PickNextThread(null, _state.SelectedThread); - if (_idleThread != nextThread) + if (nextThread != null) { - _idleThread.SchedulerWaitEvent.Reset(); - WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, _idleThread.SchedulerWaitEvent); + _idleActive = false; + nextThread.SchedulerWaitEvent.Set(); } - _idleInterruptEvent.WaitOne(); - } - - lock (_idleInterruptEventLock) - { - _idleInterruptEvent.Dispose(); - _idleInterruptEvent = null; + _idleSignalled = false; } } @@ -292,20 +300,37 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // Wake all the threads that might be waiting until this thread context is unlocked. for (int core = 0; core < CpuCoresCount; core++) { - _context.Schedulers[core]._idleInterruptEvent.Set(); + _context.Schedulers[core].NotifyIdleThread(); } - KThread nextThread = PickNextThread(selectedThread); + KThread nextThread = PickNextThread(KernelStatic.GetCurrentThread(), selectedThread); if (currentThread.Context.Running) { // Wait until this thread is scheduled again, and allow the next thread to run. - WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent); + + if (nextThread == null) + { + ActivateIdleThread(); + currentThread.SchedulerWaitEvent.WaitOne(); + } + else + { + WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent); + } } else { // Allow the next thread to run. - nextThread.SchedulerWaitEvent.Set(); + + if (nextThread == null) + { + ActivateIdleThread(); + } + else + { + nextThread.SchedulerWaitEvent.Set(); + } // We don't need to wait since the thread is exiting, however we need to // make sure this thread will never call the scheduler again, since it is @@ -319,7 +344,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } } - private KThread PickNextThread(KThread selectedThread) + private KThread PickNextThread(KThread currentThread, KThread selectedThread) { while (true) { @@ -335,7 +360,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // on the core, as the scheduled thread will handle the next switch. if (selectedThread.ThreadContext.Lock()) { - SwitchTo(selectedThread); + SwitchTo(currentThread, selectedThread); if (!_state.NeedsScheduling) { @@ -346,15 +371,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } else { - return _idleThread; + return null; } } else { // The core is idle now, make sure that the idle thread can run // and switch the core when a thread is available. - SwitchTo(null); - return _idleThread; + SwitchTo(currentThread, null); + return null; } _state.NeedsScheduling = false; @@ -363,12 +388,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } } - private void SwitchTo(KThread nextThread) + private void SwitchTo(KThread currentThread, KThread nextThread) { - KProcess currentProcess = KernelStatic.GetCurrentProcess(); - KThread currentThread = KernelStatic.GetCurrentThread(); - - nextThread ??= _idleThread; + KProcess currentProcess = currentThread?.Owner; if (currentThread != nextThread) { @@ -376,7 +398,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading long currentTicks = PerformanceCounter.ElapsedTicks; long ticksDelta = currentTicks - previousTicks; - currentThread.AddCpuTime(ticksDelta); + if (currentThread == null) + { + Interlocked.Add(ref _idleTimeRunning, ticksDelta); + } + else + { + currentThread.AddCpuTime(ticksDelta); + } currentProcess?.AddCpuTime(ticksDelta); @@ -386,13 +415,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { _previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null; } - else if (currentThread == _idleThread) + else if (currentThread == null) { _previousThread = null; } } - if (nextThread.CurrentCore != _coreId) + if (nextThread != null && nextThread.CurrentCore != _coreId) { nextThread.CurrentCore = _coreId; } @@ -645,11 +674,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public void Dispose() { - // Ensure that the idle thread is not blocked and can exit. - lock (_idleInterruptEventLock) - { - _idleInterruptEvent?.Set(); - } + // No resources to dispose for now. } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs index b1af06b0d..21c2730bf 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs @@ -104,7 +104,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } } - ArrayPool>.Shared.Return(syncNodesArray); + ArrayPool>.Shared.Return(syncNodesArray, true); } _context.CriticalSection.Leave(); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 12383fb8a..835bf5d40 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -143,9 +143,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading PreferredCore = cpuCore; AffinityMask |= 1UL << cpuCore; - SchedFlags = type == ThreadType.Dummy - ? ThreadSchedState.Running - : ThreadSchedState.None; + SchedFlags = ThreadSchedState.None; ActiveCore = cpuCore; ObjSyncResult = KernelResult.ThreadNotStarted; @@ -1055,6 +1053,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // If the thread is not schedulable, we want to just run or pause // it directly as we don't care about priority or the core it is // running on in this case. + if (SchedFlags == ThreadSchedState.Running) { _schedulerWaitEvent.Set(); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs index 83093570b..e2dfd2ffb 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs @@ -2,7 +2,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { enum ThreadType { - Dummy, Kernel, Kernel2, User, diff --git a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj index d1f572d5c..bf34ddd17 100644 --- a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj +++ b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -16,10 +16,4 @@ - - - - NU1605 - - diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs index 5d2798582..2146362df 100644 --- a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs @@ -14,6 +14,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail { partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable { + static HardwareOpusDecoder() + { + OpusCodecFactory.AttemptToUseNativeLibrary = false; + } + [StructLayout(LayoutKind.Sequential)] private struct OpusPacketHeader { @@ -30,60 +35,87 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail } } - private interface IDecoder + private interface IDecoder : IDisposable { int SampleRate { get; } int ChannelsCount { get; } - int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize); + int Decode(ReadOnlySpan inData, Span outPcm, int frameSize); void ResetState(); } private class Decoder : IDecoder { - private readonly OpusDecoder _decoder; + private readonly IOpusDecoder _decoder; public int SampleRate => _decoder.SampleRate; public int ChannelsCount => _decoder.NumChannels; public Decoder(int sampleRate, int channelsCount) { - _decoder = new OpusDecoder(sampleRate, channelsCount); + _decoder = OpusCodecFactory.CreateDecoder(sampleRate, channelsCount); } - public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) + public int Decode(ReadOnlySpan inData, Span outPcm, int frameSize) { - return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize); + return _decoder.Decode(inData, outPcm, frameSize); } public void ResetState() { _decoder.ResetState(); } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _decoder?.Dispose(); + } + } } private class MultiSampleDecoder : IDecoder { - private readonly OpusMSDecoder _decoder; + private readonly IOpusMultiStreamDecoder _decoder; public int SampleRate => _decoder.SampleRate; - public int ChannelsCount { get; } + public int ChannelsCount => _decoder.NumChannels; public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping) { - ChannelsCount = channelsCount; - _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + _decoder = OpusCodecFactory.CreateMultiStreamDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); } - public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize) + public int Decode(ReadOnlySpan inData, Span outPcm, int frameSize) { - return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0); + return _decoder.DecodeMultistream(inData, outPcm, frameSize, false); } public void ResetState() { _decoder.ResetState(); } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _decoder?.Dispose(); + } + } } private readonly IDecoder _decoder; @@ -221,7 +253,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail { timeTaken = 0; - Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples); + Span outPcmSpace = MemoryMarshal.Cast(output); + Result result = DecodeInterleaved(_decoder, reset, input, outPcmSpace, output.Length, out outConsumed, out outSamples); if (withPerf) { @@ -229,14 +262,12 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail timeTaken = 0; } - MemoryMarshal.Cast(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]); - return result; } - private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet) + private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, ReadOnlySpan packet) { - int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate); + int result = OpusPacketInfo.GetNumSamples(packet, decoder.SampleRate); numSamples = result; @@ -256,12 +287,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail IDecoder decoder, bool reset, ReadOnlySpan input, - out short[] outPcmData, + Span outPcmData, int outputSize, out int outConsumed, out int outSamples) { - outPcmData = null; outConsumed = 0; outSamples = 0; @@ -281,7 +311,7 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail return CodecResult.InvalidLength; } - byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray(); + ReadOnlySpan opusData = input.Slice(headerSize, (int)header.Length); Result result = GetPacketNumSamples(decoder, out int numSamples, opusData); @@ -292,8 +322,6 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail return CodecResult.InvalidLength; } - outPcmData = new short[numSamples * decoder.ChannelsCount]; - if (reset) { decoder.ResetState(); @@ -301,13 +329,22 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail try { - outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount); + outSamples = decoder.Decode(opusData, outPcmData, numSamples); outConsumed = (int)totalSize; } - catch (OpusException) + catch (OpusException e) { - // TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases... - return CodecResult.InvalidLength; + switch (e.OpusErrorCode) + { + case OpusError.OPUS_BUFFER_TOO_SMALL: + return CodecResult.InvalidLength; + case OpusError.OPUS_BAD_ARG: + return CodecResult.OpusBadArg; + case OpusError.OPUS_INVALID_PACKET: + return CodecResult.OpusInvalidPacket; + default: + return CodecResult.InvalidLength; + } } } @@ -324,6 +361,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail _workBufferHandle = 0; } + + _decoder?.Dispose(); } } diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 4c7bb8b7a..1dc87358d 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -174,6 +174,11 @@ namespace Ryujinx.Input.HLE { lock (_lock) { + foreach (InputConfig inputConfig in _inputConfig) + { + _controllers[(int)inputConfig.PlayerIndex]?.GamepadDriver?.Clear(); + } + _blockInputUpdates = false; } } diff --git a/src/Ryujinx.Input/IGamepadDriver.cs b/src/Ryujinx.Input/IGamepadDriver.cs index 67b01c26c..625c3e694 100644 --- a/src/Ryujinx.Input/IGamepadDriver.cs +++ b/src/Ryujinx.Input/IGamepadDriver.cs @@ -33,5 +33,11 @@ namespace Ryujinx.Input /// The unique id of the gamepad /// An instance of associated to the gamepad id given or null if not found IGamepad GetGamepad(string id); + + /// + /// Clear the internal state of the driver. + /// + /// Does nothing by default. + void Clear() { } } } diff --git a/src/Ryujinx.ShaderTools/Program.cs b/src/Ryujinx.ShaderTools/Program.cs index d2c6bd59e..a84d7b466 100644 --- a/src/Ryujinx.ShaderTools/Program.cs +++ b/src/Ryujinx.ShaderTools/Program.cs @@ -25,32 +25,32 @@ namespace Ryujinx.ShaderTools _imagesCount = 0; } - public int CreateConstantBufferBinding(int index) + public SetBindingPair CreateConstantBufferBinding(int index) { - return index + 1; + return new SetBindingPair(0, index + 1); } - public int CreateImageBinding(int count, bool isBuffer) + public SetBindingPair CreateImageBinding(int count, bool isBuffer) { int binding = _imagesCount; _imagesCount += count; - return binding; + return new SetBindingPair(3, binding); } - public int CreateStorageBufferBinding(int index) + public SetBindingPair CreateStorageBufferBinding(int index) { - return index; + return new SetBindingPair(1, index); } - public int CreateTextureBinding(int count, bool isBuffer) + public SetBindingPair CreateTextureBinding(int count, bool isBuffer) { int binding = _texturesCount; _texturesCount += count; - return binding; + return new SetBindingPair(2, binding); } public ReadOnlySpan GetCode(ulong address, int minimumSize) diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs index 557581881..3e48a5b4e 100644 --- a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs @@ -52,7 +52,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -78,7 +80,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -104,7 +108,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -130,7 +136,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -156,7 +164,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -182,7 +192,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -208,7 +220,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -234,7 +248,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -260,7 +276,9 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -286,11 +304,69 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); - Assert.IsTrue(behaviourContext.IsBiquadFilterGroupedOptimizationSupported()); + Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); } + + [Test] + public void TestRevision11() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision11); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } + + [Test] + public void TestRevision12() + { + BehaviourContext behaviourContext = new(); + + behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision12); + + Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed()); + Assert.IsTrue(behaviourContext.IsSplitterSupported()); + Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported()); + Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported()); + Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported()); + Assert.IsTrue(behaviourContext.IsSplitterBugFixed()); + Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported()); + Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()); + Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported()); + Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported()); + Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); + Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); + Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + + Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); + Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); + Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat()); + } } } diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs index ad974aab1..80b801336 100644 --- a/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs @@ -9,7 +9,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server [Test] public void EnsureTypeSize() { - Assert.AreEqual(0xE0, Unsafe.SizeOf()); + Assert.AreEqual(0xE0, Unsafe.SizeOf()); + Assert.AreEqual(0x110, Unsafe.SizeOf()); } } } diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 82783e638..176011dde 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -65,7 +65,7 @@ namespace Ryujinx.UI.App.Common Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName); byte[] resourceByteArray = new byte[resourceStream.Length]; - resourceStream.Read(resourceByteArray); + resourceStream.ReadExactly(resourceByteArray); return resourceByteArray; } diff --git a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs index fb07195d0..6966038b6 100644 --- a/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs +++ b/src/Ryujinx.UI.Common/DiscordIntegrationModule.cs @@ -104,8 +104,13 @@ namespace Ryujinx.UI.Common // Find the length to trim the string to guarantee we have space for the trailing ellipsis. int trimLimit = byteLimit - Encoding.UTF8.GetByteCount(Ellipsis); - // Basic trim to best case scenario of 1 byte characters. - input = input[..trimLimit]; + // Make sure the string is long enough to perform the basic trim. + // Amount of bytes != Length of the string + if (input.Length > trimLimit) + { + // Basic trim to best case scenario of 1 byte characters. + input = input[..trimLimit]; + } while (Encoding.UTF8.GetByteCount(input) > trimLimit) { diff --git a/src/Ryujinx/Input/AvaloniaKeyboard.cs b/src/Ryujinx/Input/AvaloniaKeyboard.cs index fbaaaabab..ff88de79e 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboard.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboard.cs @@ -195,7 +195,7 @@ namespace Ryujinx.Ava.Input public void Clear() { - _driver?.ResetKeys(); + _driver?.Clear(); } public void Dispose() { } diff --git a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs index e9e71b99b..9f87e821a 100644 --- a/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx/Input/AvaloniaKeyboardDriver.cs @@ -94,7 +94,7 @@ namespace Ryujinx.Ava.Input return _pressedKeys.Contains(nativeKey); } - public void ResetKeys() + public void Clear() { _pressedKeys.Clear(); } diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 4bcc35a7a..4bcf8eb94 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -122,6 +122,7 @@ namespace Ryujinx.Ava.UI.Applet { try { + _parent.ViewModel.AppHost.NpadManager.BlockInputUpdates(); var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args); if (response.Result == UserResult.Ok) @@ -143,6 +144,7 @@ namespace Ryujinx.Ava.UI.Applet }); dialogCloseEvent.WaitOne(); + _parent.ViewModel.AppHost.NpadManager.UnblockInputUpdates(); userText = error ? null : inputText; diff --git a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs index 89b591229..12adfe94b 100644 --- a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs @@ -151,7 +151,7 @@ namespace Ryujinx.Ava.UI.ViewModels reader.ReadInt64(); // Padding byte[] input = new byte[stream.Length - stream.Position]; - stream.Read(input, 0, input.Length); + stream.ReadExactly(input, 0, input.Length); uint inputOffset = 0;