mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2024-12-28 03:23:05 +00:00
Merge remote-tracking branch 'upstream/master' into fluent-system-icons
This commit is contained in:
commit
212faed4d1
162 changed files with 5898 additions and 1527 deletions
|
@ -11,7 +11,7 @@
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
|
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
<PackageVersion Include="Concentus" Version="2.2.0" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
<PackageVersion Include="DynamicData" Version="8.4.1" />
|
<PackageVersion Include="DynamicData" Version="8.4.1" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||||
|
@ -20,9 +20,9 @@
|
||||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.5.1" />
|
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.6.2" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
|
@ -49,4 +49,4 @@
|
||||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||||
long originalPosition = _stream.Position;
|
long originalPosition = _stream.Position;
|
||||||
|
|
||||||
_stream.Seek(0, SeekOrigin.Begin);
|
_stream.Seek(0, SeekOrigin.Begin);
|
||||||
_stream.Read(code, 0, code.Length);
|
_stream.ReadExactly(code, 0, code.Length);
|
||||||
_stream.Seek(originalPosition, SeekOrigin.Begin);
|
_stream.Seek(originalPosition, SeekOrigin.Begin);
|
||||||
|
|
||||||
RelocInfo relocInfo;
|
RelocInfo relocInfo;
|
||||||
|
|
|
@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int selectedReg = GetHighestValueIndex(freePositions);
|
// If this is a copy destination variable, we prefer the register used for the copy source.
|
||||||
|
// If the register is available, then the copy can be eliminated later as both source
|
||||||
|
// and destination will use the same register.
|
||||||
|
int selectedReg;
|
||||||
|
|
||||||
|
if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
|
||||||
|
{
|
||||||
|
selectedReg = preferredReg;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selectedReg = GetHighestValueIndex(freePositions);
|
||||||
|
}
|
||||||
|
|
||||||
int selectedNextUse = freePositions[selectedReg];
|
int selectedNextUse = freePositions[selectedReg];
|
||||||
|
|
||||||
// Intervals starts and ends at odd positions, unless they span an entire
|
// Intervals starts and ends at odd positions, unless they span an entire
|
||||||
|
@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetHighestValueIndex(Span<int> span)
|
private static int GetHighestValueIndex(ReadOnlySpan<int> span)
|
||||||
{
|
{
|
||||||
int highest = int.MinValue;
|
int highest = int.MinValue;
|
||||||
|
|
||||||
|
@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||||
// The "visited" state is stored in the MSB of the local's value.
|
// The "visited" state is stored in the MSB of the local's value.
|
||||||
const ulong VisitedMask = 1ul << 63;
|
const ulong VisitedMask = 1ul << 63;
|
||||||
|
|
||||||
bool IsVisited(Operand local)
|
static bool IsVisited(Operand local)
|
||||||
{
|
{
|
||||||
return (local.GetValueUnsafe() & VisitedMask) != 0;
|
return (local.GetValueUnsafe() & VisitedMask) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetVisited(Operand local)
|
static void SetVisited(Operand local)
|
||||||
{
|
{
|
||||||
local.GetValueUnsafe() |= VisitedMask;
|
local.GetValueUnsafe() |= VisitedMask;
|
||||||
}
|
}
|
||||||
|
@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||||
{
|
{
|
||||||
dest.NumberLocal(_intervals.Count);
|
dest.NumberLocal(_intervals.Count);
|
||||||
|
|
||||||
_intervals.Add(new LiveInterval(dest));
|
LiveInterval interval = new LiveInterval(dest);
|
||||||
|
_intervals.Add(interval);
|
||||||
|
|
||||||
SetVisited(dest);
|
SetVisited(dest);
|
||||||
|
|
||||||
|
// If this is a copy (or copy-like operation), set the copy source interval as well.
|
||||||
|
// This is used for register preferencing later on, which allows the copy to be eliminated
|
||||||
|
// in some cases.
|
||||||
|
if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
|
||||||
|
{
|
||||||
|
Operand source = node.GetSource(0);
|
||||||
|
|
||||||
|
if (source.Kind == OperandKind.LocalVariable &&
|
||||||
|
source.GetLocalNumber() > 0 &&
|
||||||
|
(node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
|
||||||
|
{
|
||||||
|
interval.SetCopySource(_intervals[source.GetLocalNumber()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||||
public LiveRange CurrRange;
|
public LiveRange CurrRange;
|
||||||
|
|
||||||
public LiveInterval Parent;
|
public LiveInterval Parent;
|
||||||
|
public LiveInterval CopySource;
|
||||||
|
|
||||||
public UseList Uses;
|
public UseList Uses;
|
||||||
public LiveIntervalList Children;
|
public LiveIntervalList Children;
|
||||||
|
@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||||
private ref LiveRange CurrRange => ref _data->CurrRange;
|
private ref LiveRange CurrRange => ref _data->CurrRange;
|
||||||
private ref LiveRange PrevRange => ref _data->PrevRange;
|
private ref LiveRange PrevRange => ref _data->PrevRange;
|
||||||
private ref LiveInterval Parent => ref _data->Parent;
|
private ref LiveInterval Parent => ref _data->Parent;
|
||||||
|
private ref LiveInterval CopySource => ref _data->CopySource;
|
||||||
private ref UseList Uses => ref _data->Uses;
|
private ref UseList Uses => ref _data->Uses;
|
||||||
private ref LiveIntervalList Children => ref _data->Children;
|
private ref LiveIntervalList Children => ref _data->Children;
|
||||||
|
|
||||||
|
@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||||
Register = register;
|
Register = register;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetCopySource(LiveInterval copySource)
|
||||||
|
{
|
||||||
|
CopySource = copySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetCopySourceRegister(out int copySourceRegIndex)
|
||||||
|
{
|
||||||
|
if (CopySource._data != null)
|
||||||
|
{
|
||||||
|
copySourceRegIndex = CopySource.Register.Index;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
copySourceRegIndex = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
PrevRange = default;
|
PrevRange = default;
|
||||||
|
|
|
@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||||
|
|
||||||
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
|
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
|
||||||
|
|
||||||
_stream.Read(buffer);
|
_stream.ReadExactly(buffer);
|
||||||
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
|
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
|
||||||
|
|
||||||
codeStream.Write(buffer);
|
codeStream.Write(buffer);
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
|
||||||
private int[] _postOrderMap;
|
private int[] _postOrderMap;
|
||||||
|
|
||||||
public int LocalsCount { get; private set; }
|
public int LocalsCount { get; private set; }
|
||||||
public BasicBlock Entry { get; }
|
public BasicBlock Entry { get; private set; }
|
||||||
public IntrusiveList<BasicBlock> Blocks { get; }
|
public IntrusiveList<BasicBlock> Blocks { get; }
|
||||||
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
|
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
|
||||||
public int[] PostOrderMap => _postOrderMap;
|
public int[] PostOrderMap => _postOrderMap;
|
||||||
|
@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateEntry(BasicBlock newEntry)
|
||||||
|
{
|
||||||
|
newEntry.AddSuccessor(Entry);
|
||||||
|
|
||||||
|
Entry = newEntry;
|
||||||
|
Blocks.AddFirst(newEntry);
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
RemoveUnreachableBlocks(Blocks);
|
RemoveUnreachableBlocks(Blocks);
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
|
||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 6634; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
|
|
|
@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
|
||||||
|
|
||||||
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
|
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
|
||||||
{
|
{
|
||||||
|
if (cfg.Entry.Predecessors.Count != 0)
|
||||||
|
{
|
||||||
|
// We expect the entry block to have no predecessors.
|
||||||
|
// This is required because we have a implicit context load at the start of the function,
|
||||||
|
// but if there is a jump to the start of the function, the context load would trash the modified values.
|
||||||
|
// Here we insert a new entry block that will jump to the existing entry block.
|
||||||
|
BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
|
||||||
|
|
||||||
|
cfg.UpdateEntry(newEntry);
|
||||||
|
}
|
||||||
|
|
||||||
// Compute local register inputs and outputs used inside blocks.
|
// Compute local register inputs and outputs used inside blocks.
|
||||||
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
|
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
|
||||||
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
|
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
|
||||||
|
@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
|
||||||
|
|
||||||
// The only block without any predecessor should be the entry block.
|
// The only block without any predecessor should be the entry block.
|
||||||
// It always needs a context load as it is the first block to run.
|
// It always needs a context load as it is the first block to run.
|
||||||
if (block.Predecessors.Count == 0 || hasContextLoad)
|
if (block == cfg.Entry || hasContextLoad)
|
||||||
{
|
{
|
||||||
long vecMask = globalInputs[block.Index].VecMask;
|
long vecMask = globalInputs[block.Index].VecMask;
|
||||||
long intMask = globalInputs[block.Index].IntMask;
|
long intMask = globalInputs[block.Index].IntMask;
|
||||||
|
|
|
@ -89,9 +89,9 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * _bytesPerFrame);
|
using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame);
|
||||||
|
|
||||||
Span<byte> samples = samplesOwner.Memory.Span;
|
Span<byte> samples = samplesOwner.Span;
|
||||||
|
|
||||||
_ringBuffer.Read(samples, 0, samples.Length);
|
_ringBuffer.Read(samples, 0, samples.Length);
|
||||||
|
|
||||||
|
|
|
@ -122,9 +122,9 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
|
|
||||||
int channelCount = areas.Length;
|
int channelCount = areas.Length;
|
||||||
|
|
||||||
using IMemoryOwner<byte> samplesOwner = ByteMemoryPool.Rent(frameCount * bytesPerFrame);
|
using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * bytesPerFrame);
|
||||||
|
|
||||||
Span<byte> samples = samplesOwner.Memory.Span;
|
Span<byte> samples = samplesOwner.Span;
|
||||||
|
|
||||||
_ringBuffer.Read(samples, 0, samples.Length);
|
_ringBuffer.Read(samples, 0, samples.Length);
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Backends.Common
|
||||||
|
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
private IMemoryOwner<byte> _bufferOwner;
|
private MemoryOwner<byte> _bufferOwner;
|
||||||
private Memory<byte> _buffer;
|
private Memory<byte> _buffer;
|
||||||
private int _size;
|
private int _size;
|
||||||
private int _headOffset;
|
private int _headOffset;
|
||||||
|
@ -24,7 +24,7 @@ namespace Ryujinx.Audio.Backends.Common
|
||||||
|
|
||||||
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
|
public DynamicRingBuffer(int initialCapacity = RingBufferAlignment)
|
||||||
{
|
{
|
||||||
_bufferOwner = ByteMemoryPool.RentCleared(initialCapacity);
|
_bufferOwner = MemoryOwner<byte>.RentCleared(initialCapacity);
|
||||||
_buffer = _bufferOwner.Memory;
|
_buffer = _bufferOwner.Memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Backends.Common
|
||||||
|
|
||||||
private void SetCapacityLocked(int capacity)
|
private void SetCapacityLocked(int capacity)
|
||||||
{
|
{
|
||||||
IMemoryOwner<byte> newBufferOwner = ByteMemoryPool.RentCleared(capacity);
|
MemoryOwner<byte> newBufferOwner = MemoryOwner<byte>.RentCleared(capacity);
|
||||||
Memory<byte> newBuffer = newBufferOwner.Memory;
|
Memory<byte> newBuffer = newBufferOwner.Memory;
|
||||||
|
|
||||||
if (_size > 0)
|
if (_size > 0)
|
||||||
|
|
|
@ -15,7 +15,6 @@ namespace Ryujinx.Audio.Renderer.Common
|
||||||
{
|
{
|
||||||
public const int Align = 0x10;
|
public const int Align = 0x10;
|
||||||
public const int BiquadStateOffset = 0x0;
|
public const int BiquadStateOffset = 0x0;
|
||||||
public const int BiquadStateSize = 0x10;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The state of the biquad filters of this voice.
|
/// The state of the biquad filters of this voice.
|
||||||
|
|
|
@ -16,10 +16,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
/// <param name="parameter">The biquad filter parameter</param>
|
/// <param name="parameter">The biquad filter parameter</param>
|
||||||
/// <param name="state">The biquad filter state</param>
|
/// <param name="state">The biquad filter state</param>
|
||||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
/// <param name="inputBuffer">The input buffer to write the result</param>
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
/// <param name="sampleCount">The count of samples to process</param>
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
public static void ProcessBiquadFilter(
|
||||||
|
ref BiquadFilterParameter parameter,
|
||||||
|
ref BiquadFilterState state,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
uint sampleCount)
|
||||||
{
|
{
|
||||||
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
|
float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
|
||||||
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
|
float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
|
||||||
|
@ -40,6 +45,96 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply a single biquad filter and mix the result into the output buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||||
|
/// <param name="parameter">The biquad filter parameter</param>
|
||||||
|
/// <param name="state">The biquad filter state</param>
|
||||||
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
|
/// <param name="volume">Mix volume</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ProcessBiquadFilterAndMix(
|
||||||
|
ref BiquadFilterParameter parameter,
|
||||||
|
ref BiquadFilterState state,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply a single biquad filter and mix the result into the output buffer with volume ramp.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||||
|
/// <param name="parameter">The biquad filter parameter</param>
|
||||||
|
/// <param name="state">The biquad filter state</param>
|
||||||
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
|
/// <param name="volume">Initial mix volume</param>
|
||||||
|
/// <param name="ramp">Volume increment step</param>
|
||||||
|
/// <returns>Last filtered sample value</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static float ProcessBiquadFilterAndMixRamp(
|
||||||
|
ref BiquadFilterParameter parameter,
|
||||||
|
ref BiquadFilterState state,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> 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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply multiple biquad filter.
|
/// Apply multiple biquad filter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -47,10 +142,15 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
/// <param name="parameters">The biquad filter parameter</param>
|
/// <param name="parameters">The biquad filter parameter</param>
|
||||||
/// <param name="states">The biquad filter state</param>
|
/// <param name="states">The biquad filter state</param>
|
||||||
/// <param name="outputBuffer">The output buffer to write the result</param>
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
/// <param name="inputBuffer">The input buffer to write the result</param>
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
/// <param name="sampleCount">The count of samples to process</param>
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void ProcessBiquadFilter(ReadOnlySpan<BiquadFilterParameter> parameters, Span<BiquadFilterState> states, Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, uint sampleCount)
|
public static void ProcessBiquadFilter(
|
||||||
|
ReadOnlySpan<BiquadFilterParameter> parameters,
|
||||||
|
Span<BiquadFilterState> states,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
uint sampleCount)
|
||||||
{
|
{
|
||||||
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
|
for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
|
||||||
{
|
{
|
||||||
|
@ -67,7 +167,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
|
|
||||||
for (int i = 0; i < sampleCount; i++)
|
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;
|
float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
|
||||||
|
|
||||||
state.State1 = state.State0;
|
state.State1 = state.State0;
|
||||||
|
@ -79,5 +179,129 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply double biquad filter and mix the result into the output buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||||
|
/// <param name="parameters">The biquad filter parameter</param>
|
||||||
|
/// <param name="states">The biquad filter state</param>
|
||||||
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
|
/// <param name="volume">Mix volume</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void ProcessDoubleBiquadFilterAndMix(
|
||||||
|
ref BiquadFilterParameter parameter0,
|
||||||
|
ref BiquadFilterParameter parameter1,
|
||||||
|
ref BiquadFilterState state0,
|
||||||
|
ref BiquadFilterState state1,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Apply double biquad filter and mix the result into the output buffer with volume ramp.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is implemented with a direct form 1.</remarks>
|
||||||
|
/// <param name="parameters">The biquad filter parameter</param>
|
||||||
|
/// <param name="states">The biquad filter state</param>
|
||||||
|
/// <param name="outputBuffer">The output buffer to write the result</param>
|
||||||
|
/// <param name="inputBuffer">The input buffer to read the samples from</param>
|
||||||
|
/// <param name="sampleCount">The count of samples to process</param>
|
||||||
|
/// <param name="volume">Initial mix volume</param>
|
||||||
|
/// <param name="ramp">Volume increment step</param>
|
||||||
|
/// <returns>Last filtered sample value</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static float ProcessDoubleBiquadFilterAndMixRamp(
|
||||||
|
ref BiquadFilterParameter parameter0,
|
||||||
|
ref BiquadFilterParameter parameter1,
|
||||||
|
ref BiquadFilterState state0,
|
||||||
|
ref BiquadFilterState state1,
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> BiquadFilterState { get; }
|
||||||
|
public Memory<BiquadFilterState> PreviousBiquadFilterState { get; }
|
||||||
|
|
||||||
|
public Memory<VoiceUpdateState> 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<VoiceUpdateState> state,
|
||||||
|
ref BiquadFilterParameter filter,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState,
|
||||||
|
Memory<BiquadFilterState> 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<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||||
|
Span<float> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,8 +30,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
CopyMixBuffer,
|
CopyMixBuffer,
|
||||||
LimiterVersion1,
|
LimiterVersion1,
|
||||||
LimiterVersion2,
|
LimiterVersion2,
|
||||||
GroupedBiquadFilter,
|
MultiTapBiquadFilter,
|
||||||
CaptureBuffer,
|
CaptureBuffer,
|
||||||
Compressor,
|
Compressor,
|
||||||
|
BiquadFilterAndMix,
|
||||||
|
MultiTapBiquadFilterAndMix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
public Memory<VoiceUpdateState> State { get; }
|
public Memory<VoiceUpdateState> State { get; }
|
||||||
|
|
||||||
public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> volume0, Span<float> volume1, Memory<VoiceUpdateState> state, int nodeId)
|
public MixRampGroupedCommand(
|
||||||
|
uint mixBufferCount,
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
ReadOnlySpan<float> volume0,
|
||||||
|
ReadOnlySpan<float> volume1,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
int nodeId)
|
||||||
{
|
{
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
MixBufferCount = mixBufferCount;
|
MixBufferCount = mixBufferCount;
|
||||||
|
@ -48,7 +55,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static float ProcessMixRampGrouped(Span<float> outputBuffer, ReadOnlySpan<float> inputBuffer, float volume0, float volume1, int sampleCount)
|
private static float ProcessMixRampGrouped(
|
||||||
|
Span<float> outputBuffer,
|
||||||
|
ReadOnlySpan<float> inputBuffer,
|
||||||
|
float volume0,
|
||||||
|
float volume1,
|
||||||
|
int sampleCount)
|
||||||
{
|
{
|
||||||
float ramp = (volume1 - volume0) / sampleCount;
|
float ramp = (volume1 - volume0) / sampleCount;
|
||||||
float volume = volume0;
|
float volume = volume0;
|
||||||
|
|
|
@ -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<BiquadFilterState> BiquadFilterState0 { get; }
|
||||||
|
public Memory<BiquadFilterState> BiquadFilterState1 { get; }
|
||||||
|
public Memory<BiquadFilterState> PreviousBiquadFilterState0 { get; }
|
||||||
|
public Memory<BiquadFilterState> PreviousBiquadFilterState1 { get; }
|
||||||
|
|
||||||
|
public Memory<VoiceUpdateState> 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<VoiceUpdateState> state,
|
||||||
|
ref BiquadFilterParameter filter0,
|
||||||
|
ref BiquadFilterParameter filter1,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState0,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState1,
|
||||||
|
Memory<BiquadFilterState> previousBiquadFilterState0,
|
||||||
|
Memory<BiquadFilterState> 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<BiquadFilterState> state, Memory<BiquadFilterState> 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<float> inputBuffer = context.GetBuffer(InputBufferIndex);
|
||||||
|
Span<float> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,13 +4,13 @@ using System;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
{
|
{
|
||||||
public class GroupedBiquadFilterCommand : ICommand
|
public class MultiTapBiquadFilterCommand : ICommand
|
||||||
{
|
{
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
public int NodeId { get; }
|
public int NodeId { get; }
|
||||||
|
|
||||||
public CommandType CommandType => CommandType.GroupedBiquadFilter;
|
public CommandType CommandType => CommandType.MultiTapBiquadFilter;
|
||||||
|
|
||||||
public uint EstimatedProcessingTime { get; set; }
|
public uint EstimatedProcessingTime { get; set; }
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
private readonly int _outputBufferIndex;
|
private readonly int _outputBufferIndex;
|
||||||
private readonly bool[] _isInitialized;
|
private readonly bool[] _isInitialized;
|
||||||
|
|
||||||
public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
public MultiTapBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
||||||
{
|
{
|
||||||
_parameters = filters.ToArray();
|
_parameters = filters.ToArray();
|
||||||
_biquadFilterStates = biquadFilterStateMemory;
|
_biquadFilterStates = biquadFilterStateMemory;
|
|
@ -2,12 +2,16 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Dsp.State
|
namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x20)]
|
||||||
public struct BiquadFilterState
|
public struct BiquadFilterState
|
||||||
{
|
{
|
||||||
public float State0;
|
public float State0;
|
||||||
public float State1;
|
public float State1;
|
||||||
public float State2;
|
public float State2;
|
||||||
public float State3;
|
public float State3;
|
||||||
|
public float State4;
|
||||||
|
public float State5;
|
||||||
|
public float State6;
|
||||||
|
public float State7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generic interface for the splitter destination parameters.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISplitterDestinationInParameter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Target splitter destination data id.
|
||||||
|
/// </summary>
|
||||||
|
int Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mix to output the result of the splitter.
|
||||||
|
/// </summary>
|
||||||
|
int DestinationId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Biquad filter parameters.
|
||||||
|
/// </summary>
|
||||||
|
Array2<BiquadFilterParameter> BiquadFilters { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if in use.
|
||||||
|
/// </summary>
|
||||||
|
bool IsUsed { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
Span<float> MixBufferVolume { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the magic is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns true if the magic is valid.</returns>
|
||||||
|
bool IsMagicValid();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -5,10 +6,10 @@ using System.Runtime.InteropServices;
|
||||||
namespace Ryujinx.Audio.Renderer.Parameter
|
namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Input header for a splitter destination update.
|
/// Input header for a splitter destination version 1 update.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public struct SplitterDestinationInParameter
|
public struct SplitterDestinationInParameterVersion1 : ISplitterDestinationInParameter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Magic of the input header.
|
/// Magic of the input header.
|
||||||
|
@ -41,7 +42,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private unsafe fixed byte _reserved[3];
|
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 { }
|
private struct MixArray { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -50,6 +51,14 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
|
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
|
||||||
|
|
||||||
|
readonly int ISplitterDestinationInParameter.Id => Id;
|
||||||
|
|
||||||
|
readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
|
||||||
|
|
||||||
|
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
|
||||||
|
|
||||||
|
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The expected constant of any input header.
|
/// The expected constant of any input header.
|
||||||
/// </summary>
|
/// </summary>
|
|
@ -0,0 +1,81 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Input header for a splitter destination version 2 update.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct SplitterDestinationInParameterVersion2 : ISplitterDestinationInParameter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Magic of the input header.
|
||||||
|
/// </summary>
|
||||||
|
public uint Magic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Target splitter destination data id.
|
||||||
|
/// </summary>
|
||||||
|
public int Id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes storage.
|
||||||
|
/// </summary>
|
||||||
|
private MixArray _mixBufferVolume;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mix to output the result of the splitter.
|
||||||
|
/// </summary>
|
||||||
|
public int DestinationId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Biquad filter parameters.
|
||||||
|
/// </summary>
|
||||||
|
public Array2<BiquadFilterParameter> BiquadFilters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if in use.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool IsUsed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved/padding.
|
||||||
|
/// </summary>
|
||||||
|
private unsafe fixed byte _reserved[11];
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
||||||
|
private struct MixArray { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mixBufferVolume);
|
||||||
|
|
||||||
|
readonly int ISplitterDestinationInParameter.Id => Id;
|
||||||
|
|
||||||
|
readonly int ISplitterDestinationInParameter.DestinationId => DestinationId;
|
||||||
|
|
||||||
|
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
|
||||||
|
|
||||||
|
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The expected constant of any input header.
|
||||||
|
/// </summary>
|
||||||
|
private const uint ValidMagic = 0x44444E53;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the magic is valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns true if the magic is valid.</returns>
|
||||||
|
public readonly bool IsMagicValid()
|
||||||
|
{
|
||||||
|
return Magic == ValidMagic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
using Ryujinx.Audio.Integration;
|
using Ryujinx.Audio.Integration;
|
||||||
using Ryujinx.Audio.Renderer.Common;
|
using Ryujinx.Audio.Renderer.Common;
|
||||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||||
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
using Ryujinx.Audio.Renderer.Parameter;
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||||
|
@ -173,6 +174,22 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
return ResultCode.WorkBufferTooSmall;
|
return ResultCode.WorkBufferTooSmall;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Memory<BiquadFilterState> splitterBqfStates = Memory<BiquadFilterState>.Empty;
|
||||||
|
|
||||||
|
if (_behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
|
||||||
|
parameter.SplitterCount > 0 &&
|
||||||
|
parameter.SplitterDestinationCount > 0)
|
||||||
|
{
|
||||||
|
splitterBqfStates = workBufferAllocator.Allocate<BiquadFilterState>(parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
|
||||||
|
|
||||||
|
if (splitterBqfStates.IsEmpty)
|
||||||
|
{
|
||||||
|
return ResultCode.WorkBufferTooSmall;
|
||||||
|
}
|
||||||
|
|
||||||
|
splitterBqfStates.Span.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Invalidate DSP cache on what was currently allocated with workBuffer.
|
// Invalidate DSP cache on what was currently allocated with workBuffer.
|
||||||
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
|
AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset);
|
||||||
|
|
||||||
|
@ -292,7 +309,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu);
|
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;
|
return ResultCode.WorkBufferTooSmall;
|
||||||
}
|
}
|
||||||
|
@ -775,6 +792,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
// Splitter
|
// Splitter
|
||||||
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
|
size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter);
|
||||||
|
|
||||||
|
if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled() &&
|
||||||
|
parameter.SplitterCount > 0 &&
|
||||||
|
parameter.SplitterDestinationCount > 0)
|
||||||
|
{
|
||||||
|
size = WorkBufferAllocator.GetTargetSize<BiquadFilterState>(size, parameter.SplitterDestinationCount * SplitterContext.BqfStatesPerDestination, 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
// DSP Voice
|
// DSP Voice
|
||||||
size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
|
size = WorkBufferAllocator.GetTargetSize<VoiceUpdateState>(size, parameter.VoiceCount, VoiceUpdateState.Align);
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <see cref="Parameter.RendererInfoOutStatus"/> was added to supply the count of update done sent to the DSP.
|
/// <see cref="Parameter.RendererInfoOutStatus"/> 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.
|
/// 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%.
|
/// Additionally, the rendering limit percent was incremented to 80%.
|
||||||
///
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This was added in system update 6.0.0</remarks>
|
/// <remarks>This was added in system update 6.0.0</remarks>
|
||||||
public const int Revision5 = 5 << 24;
|
public const int Revision5 = 5 << 24;
|
||||||
|
@ -101,10 +100,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
|
/// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
|
||||||
public const int Revision11 = 11 << 24;
|
public const int Revision11 = 11 << 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This was added in system update 17.0.0</remarks>
|
||||||
|
public const int Revision12 = 12 << 24;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last revision supported by the implementation.
|
/// Last revision supported by the implementation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int LastRevision = Revision11;
|
public const int LastRevision = Revision12;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Target revision magic supported by the implementation.
|
/// 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.
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if the audio renderer should use the optimization.</returns>
|
/// <returns>True if the audio renderer should use the optimization.</returns>
|
||||||
public bool IsBiquadFilterGroupedOptimizationSupported()
|
public bool UseMultiTapBiquadFilterProcessing()
|
||||||
{
|
{
|
||||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
|
||||||
}
|
}
|
||||||
|
@ -368,6 +375,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the audio renderer should support biquad filter on splitter.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the audio renderer support biquad filter on splitter</returns>
|
||||||
|
public bool IsBiquadFilterParameterForSplitterEnabled()
|
||||||
|
{
|
||||||
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -204,7 +204,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="GroupedBiquadFilterCommand"/>.
|
/// Create a new <see cref="MultiTapBiquadFilterCommand"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseIndex">The base index of the input and output buffer.</param>
|
/// <param name="baseIndex">The base index of the input and output buffer.</param>
|
||||||
/// <param name="filters">The biquad filter parameters.</param>
|
/// <param name="filters">The biquad filter parameters.</param>
|
||||||
|
@ -213,9 +213,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <param name="outputBufferOffset">The output buffer offset.</param>
|
/// <param name="outputBufferOffset">The output buffer offset.</param>
|
||||||
/// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
|
/// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
|
||||||
/// <param name="nodeId">The node id associated to this command.</param>
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
|
public void GenerateMultiTapBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> 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);
|
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <param name="volume">The new volume.</param>
|
/// <param name="volume">The new volume.</param>
|
||||||
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||||
/// <param name="nodeId">The node id associated to this command.</param>
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span<float> previousVolume, Span<float> volume, Memory<VoiceUpdateState> state, int nodeId)
|
public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, ReadOnlySpan<float> previousVolume, ReadOnlySpan<float> volume, Memory<VoiceUpdateState> state, int nodeId)
|
||||||
{
|
{
|
||||||
MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
|
MixRampGroupedCommand command = new(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId);
|
||||||
|
|
||||||
|
@ -260,6 +260,120 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
AddCommand(command);
|
AddCommand(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a new <see cref="BiquadFilterAndMixCommand"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="previousVolume">The previous volume.</param>
|
||||||
|
/// <param name="volume">The new volume.</param>
|
||||||
|
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||||
|
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||||
|
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
|
||||||
|
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||||
|
/// <param name="filter">The biquad filter parameter.</param>
|
||||||
|
/// <param name="biquadFilterState">The biquad state.</param>
|
||||||
|
/// <param name="previousBiquadFilterState">The previous biquad state.</param>
|
||||||
|
/// <param name="needInitialization">Set to true if the biquad filter state needs to be initialized.</param>
|
||||||
|
/// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
|
||||||
|
/// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
|
||||||
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
|
public void GenerateBiquadFilterAndMix(
|
||||||
|
float previousVolume,
|
||||||
|
float volume,
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
int lastSampleIndex,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
ref BiquadFilterParameter filter,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState,
|
||||||
|
Memory<BiquadFilterState> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a new <see cref="MultiTapBiquadFilterAndMixCommand"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="previousVolume">The previous volume.</param>
|
||||||
|
/// <param name="volume">The new volume.</param>
|
||||||
|
/// <param name="inputBufferIndex">The input buffer index.</param>
|
||||||
|
/// <param name="outputBufferIndex">The output buffer index.</param>
|
||||||
|
/// <param name="lastSampleIndex">The index in the <see cref="VoiceUpdateState.LastSamples"/> array to store the ramped sample.</param>
|
||||||
|
/// <param name="state">The <see cref="VoiceUpdateState"/> to generate the command from.</param>
|
||||||
|
/// <param name="filter0">First biquad filter parameter.</param>
|
||||||
|
/// <param name="filter1">Second biquad filter parameter.</param>
|
||||||
|
/// <param name="biquadFilterState0">First biquad state.</param>
|
||||||
|
/// <param name="biquadFilterState1">Second biquad state.</param>
|
||||||
|
/// <param name="previousBiquadFilterState0">First previous biquad state.</param>
|
||||||
|
/// <param name="previousBiquadFilterState1">Second previous biquad state.</param>
|
||||||
|
/// <param name="needInitialization0">Set to true if the first biquad filter state needs to be initialized.</param>
|
||||||
|
/// <param name="needInitialization1">Set to true if the second biquad filter state needs to be initialized.</param>
|
||||||
|
/// <param name="hasVolumeRamp">Set to true if the mix has volume ramp, and <paramref name="previousVolume"/> should be taken into account.</param>
|
||||||
|
/// <param name="isFirstMixBuffer">Set to true if the buffer is the first mix buffer.</param>
|
||||||
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
|
public void GenerateMultiTapBiquadFilterAndMix(
|
||||||
|
float previousVolume,
|
||||||
|
float volume,
|
||||||
|
uint inputBufferIndex,
|
||||||
|
uint outputBufferIndex,
|
||||||
|
int lastSampleIndex,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
ref BiquadFilterParameter filter0,
|
||||||
|
ref BiquadFilterParameter filter1,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState0,
|
||||||
|
Memory<BiquadFilterState> biquadFilterState1,
|
||||||
|
Memory<BiquadFilterState> previousBiquadFilterState0,
|
||||||
|
Memory<BiquadFilterState> 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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate a new <see cref="DepopForMixBuffersCommand"/>.
|
/// Generate a new <see cref="DepopForMixBuffersCommand"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -268,7 +382,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <param name="bufferCount">The buffer count.</param>
|
/// <param name="bufferCount">The buffer count.</param>
|
||||||
/// <param name="nodeId">The node id associated to this command.</param>
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
/// <param name="sampleRate">The target sample rate in use.</param>
|
/// <param name="sampleRate">The target sample rate in use.</param>
|
||||||
public void GenerateDepopForMixBuffersCommand(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
|
public void GenerateDepopForMixBuffers(Memory<float> depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate)
|
||||||
{
|
{
|
||||||
DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
|
DepopForMixBuffersCommand command = new(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate);
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ using Ryujinx.Audio.Renderer.Server.Voice;
|
||||||
using Ryujinx.Audio.Renderer.Utils;
|
using Ryujinx.Audio.Renderer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Server
|
namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
|
@ -46,12 +47,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
|
ref MixState mix = ref _mixContext.GetState(voiceState.MixId);
|
||||||
|
|
||||||
_commandBuffer.GenerateDepopPrepare(dspState,
|
_commandBuffer.GenerateDepopPrepare(
|
||||||
_rendererContext.DepopBuffer,
|
dspState,
|
||||||
mix.BufferCount,
|
_rendererContext.DepopBuffer,
|
||||||
mix.BufferOffset,
|
mix.BufferCount,
|
||||||
voiceState.NodeId,
|
mix.BufferOffset,
|
||||||
voiceState.WasPlaying);
|
voiceState.NodeId,
|
||||||
|
voiceState.WasPlaying);
|
||||||
}
|
}
|
||||||
else if (voiceState.SplitterId != Constants.UnusedSplitterId)
|
else if (voiceState.SplitterId != Constants.UnusedSplitterId)
|
||||||
{
|
{
|
||||||
|
@ -59,15 +61,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
|
SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++);
|
||||||
|
|
||||||
if (destinationSpan.IsEmpty)
|
if (destination.IsNull)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref SplitterDestination destination = ref destinationSpan[0];
|
|
||||||
|
|
||||||
if (destination.IsConfigured())
|
if (destination.IsConfigured())
|
||||||
{
|
{
|
||||||
int mixId = destination.DestinationId;
|
int mixId = destination.DestinationId;
|
||||||
|
@ -76,12 +76,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
ref MixState mix = ref _mixContext.GetState(mixId);
|
ref MixState mix = ref _mixContext.GetState(mixId);
|
||||||
|
|
||||||
_commandBuffer.GenerateDepopPrepare(dspState,
|
_commandBuffer.GenerateDepopPrepare(
|
||||||
_rendererContext.DepopBuffer,
|
dspState,
|
||||||
mix.BufferCount,
|
_rendererContext.DepopBuffer,
|
||||||
mix.BufferOffset,
|
mix.BufferCount,
|
||||||
voiceState.NodeId,
|
mix.BufferOffset,
|
||||||
voiceState.WasPlaying);
|
voiceState.NodeId,
|
||||||
|
voiceState.WasPlaying);
|
||||||
|
|
||||||
destination.MarkAsNeedToUpdateInternalState();
|
destination.MarkAsNeedToUpdateInternalState();
|
||||||
}
|
}
|
||||||
|
@ -95,35 +96,39 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
|
if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported())
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateDataSourceVersion2(ref voiceState,
|
_commandBuffer.GenerateDataSourceVersion2(
|
||||||
dspState,
|
ref voiceState,
|
||||||
(ushort)_rendererContext.MixBufferCount,
|
dspState,
|
||||||
(ushort)channelIndex,
|
(ushort)_rendererContext.MixBufferCount,
|
||||||
voiceState.NodeId);
|
(ushort)channelIndex,
|
||||||
|
voiceState.NodeId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (voiceState.SampleFormat)
|
switch (voiceState.SampleFormat)
|
||||||
{
|
{
|
||||||
case SampleFormat.PcmInt16:
|
case SampleFormat.PcmInt16:
|
||||||
_commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState,
|
_commandBuffer.GeneratePcmInt16DataSourceVersion1(
|
||||||
dspState,
|
ref voiceState,
|
||||||
(ushort)_rendererContext.MixBufferCount,
|
dspState,
|
||||||
(ushort)channelIndex,
|
(ushort)_rendererContext.MixBufferCount,
|
||||||
voiceState.NodeId);
|
(ushort)channelIndex,
|
||||||
|
voiceState.NodeId);
|
||||||
break;
|
break;
|
||||||
case SampleFormat.PcmFloat:
|
case SampleFormat.PcmFloat:
|
||||||
_commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState,
|
_commandBuffer.GeneratePcmFloatDataSourceVersion1(
|
||||||
dspState,
|
ref voiceState,
|
||||||
(ushort)_rendererContext.MixBufferCount,
|
dspState,
|
||||||
(ushort)channelIndex,
|
(ushort)_rendererContext.MixBufferCount,
|
||||||
voiceState.NodeId);
|
(ushort)channelIndex,
|
||||||
|
voiceState.NodeId);
|
||||||
break;
|
break;
|
||||||
case SampleFormat.Adpcm:
|
case SampleFormat.Adpcm:
|
||||||
_commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState,
|
_commandBuffer.GenerateAdpcmDataSourceVersion1(
|
||||||
dspState,
|
ref voiceState,
|
||||||
(ushort)_rendererContext.MixBufferCount,
|
dspState,
|
||||||
voiceState.NodeId);
|
(ushort)_rendererContext.MixBufferCount,
|
||||||
|
voiceState.NodeId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
|
throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}");
|
||||||
|
@ -134,14 +139,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> state, int baseIndex, int bufferOffset, int nodeId)
|
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> 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)
|
if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
|
||||||
{
|
{
|
||||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
|
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
|
||||||
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.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
|
else
|
||||||
{
|
{
|
||||||
|
@ -151,33 +156,134 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
if (filter.Enable)
|
if (filter.Enable)
|
||||||
{
|
{
|
||||||
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount)];
|
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state)[..(Unsafe.SizeOf<BiquadFilterState>() * Constants.VoiceBiquadFilterCount)];
|
||||||
|
|
||||||
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
|
||||||
|
|
||||||
_commandBuffer.GenerateBiquadFilter(baseIndex,
|
_commandBuffer.GenerateBiquadFilter(
|
||||||
ref filter,
|
baseIndex,
|
||||||
stateMemory.Slice(i, 1),
|
ref filter,
|
||||||
bufferOffset,
|
stateMemory.Slice(i, 1),
|
||||||
bufferOffset,
|
bufferOffset,
|
||||||
!voiceState.BiquadFilterNeedInitialization[i],
|
bufferOffset,
|
||||||
nodeId);
|
!voiceState.BiquadFilterNeedInitialization[i],
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateVoiceMix(Span<float> mixVolumes, Span<float> previousMixVolumes, Memory<VoiceUpdateState> state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId)
|
private void GenerateVoiceMixWithSplitter(
|
||||||
|
SplitterDestination destination,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
uint bufferOffset,
|
||||||
|
uint bufferCount,
|
||||||
|
uint bufferIndex,
|
||||||
|
int nodeId)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<float> mixVolumes = destination.MixBufferVolume;
|
||||||
|
ReadOnlySpan<float> previousMixVolumes = destination.PreviousMixBufferVolume;
|
||||||
|
|
||||||
|
ref BiquadFilterParameter bqf0 = ref destination.GetBiquadFilterParameter(0);
|
||||||
|
ref BiquadFilterParameter bqf1 = ref destination.GetBiquadFilterParameter(1);
|
||||||
|
|
||||||
|
Memory<BiquadFilterState> 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<float> mixVolumes,
|
||||||
|
ReadOnlySpan<float> previousMixVolumes,
|
||||||
|
Memory<VoiceUpdateState> state,
|
||||||
|
uint bufferOffset,
|
||||||
|
uint bufferCount,
|
||||||
|
uint bufferIndex,
|
||||||
|
int nodeId)
|
||||||
{
|
{
|
||||||
if (bufferCount > Constants.VoiceChannelCountMax)
|
if (bufferCount > Constants.VoiceChannelCountMax)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMixRampGrouped(bufferCount,
|
_commandBuffer.GenerateMixRampGrouped(
|
||||||
bufferIndex,
|
bufferCount,
|
||||||
bufferOffset,
|
bufferIndex,
|
||||||
previousMixVolumes,
|
bufferOffset,
|
||||||
mixVolumes,
|
previousMixVolumes,
|
||||||
state,
|
mixVolumes,
|
||||||
nodeId);
|
state,
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -188,13 +294,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
|
if (mixVolume != 0.0f || previousMixVolume != 0.0f)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMixRamp(previousMixVolume,
|
_commandBuffer.GenerateMixRamp(
|
||||||
mixVolume,
|
previousMixVolume,
|
||||||
bufferIndex,
|
mixVolume,
|
||||||
bufferOffset + (uint)i,
|
bufferIndex,
|
||||||
i,
|
bufferOffset + (uint)i,
|
||||||
state,
|
i,
|
||||||
nodeId);
|
state,
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,10 +378,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume,
|
_commandBuffer.GenerateVolumeRamp(
|
||||||
voiceState.Volume,
|
voiceState.PreviousVolume,
|
||||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
voiceState.Volume,
|
||||||
nodeId);
|
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
if (performanceInitialized)
|
if (performanceInitialized)
|
||||||
{
|
{
|
||||||
|
@ -291,15 +399,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
|
SplitterDestination destination = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId);
|
||||||
|
|
||||||
if (destinationSpan.IsEmpty)
|
if (destination.IsNull)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref SplitterDestination destination = ref destinationSpan[0];
|
|
||||||
|
|
||||||
destinationId += (int)channelsCount;
|
destinationId += (int)channelsCount;
|
||||||
|
|
||||||
if (destination.IsConfigured())
|
if (destination.IsConfigured())
|
||||||
|
@ -310,13 +416,27 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
ref MixState mix = ref _mixContext.GetState(mixId);
|
ref MixState mix = ref _mixContext.GetState(mixId);
|
||||||
|
|
||||||
GenerateVoiceMix(destination.MixBufferVolume,
|
if (destination.IsBiquadFilterEnabled())
|
||||||
destination.PreviousMixBufferVolume,
|
{
|
||||||
dspStateMemory,
|
GenerateVoiceMixWithSplitter(
|
||||||
mix.BufferOffset,
|
destination,
|
||||||
mix.BufferCount,
|
dspStateMemory,
|
||||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
mix.BufferOffset,
|
||||||
nodeId);
|
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();
|
destination.MarkAsNeedToUpdateInternalState();
|
||||||
}
|
}
|
||||||
|
@ -337,13 +457,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
GenerateVoiceMix(channelResource.Mix.AsSpan(),
|
GenerateVoiceMix(
|
||||||
channelResource.PreviousMix.AsSpan(),
|
channelResource.Mix.AsSpan(),
|
||||||
dspStateMemory,
|
channelResource.PreviousMix.AsSpan(),
|
||||||
mix.BufferOffset,
|
dspStateMemory,
|
||||||
mix.BufferCount,
|
mix.BufferOffset,
|
||||||
_rendererContext.MixBufferCount + (uint)channelIndex,
|
mix.BufferCount,
|
||||||
nodeId);
|
_rendererContext.MixBufferCount + (uint)channelIndex,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
if (performanceInitialized)
|
if (performanceInitialized)
|
||||||
{
|
{
|
||||||
|
@ -409,10 +530,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
if (effect.Parameter.Volumes[i] != 0.0f)
|
if (effect.Parameter.Volumes[i] != 0.0f)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i],
|
_commandBuffer.GenerateMix(
|
||||||
(uint)bufferOffset + effect.Parameter.Output[i],
|
(uint)bufferOffset + effect.Parameter.Input[i],
|
||||||
nodeId,
|
(uint)bufferOffset + effect.Parameter.Output[i],
|
||||||
effect.Parameter.Volumes[i]);
|
nodeId,
|
||||||
|
effect.Parameter.Volumes[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,17 +569,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
updateCount = newUpdateCount;
|
updateCount = newUpdateCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateAuxEffect(bufferOffset,
|
_commandBuffer.GenerateAuxEffect(
|
||||||
effect.Parameter.Input[i],
|
bufferOffset,
|
||||||
effect.Parameter.Output[i],
|
effect.Parameter.Input[i],
|
||||||
ref effect.State,
|
effect.Parameter.Output[i],
|
||||||
effect.IsEnabled,
|
ref effect.State,
|
||||||
effect.Parameter.BufferStorageSize,
|
effect.IsEnabled,
|
||||||
effect.State.SendBufferInfoBase,
|
effect.Parameter.BufferStorageSize,
|
||||||
effect.State.ReturnBufferInfoBase,
|
effect.State.SendBufferInfoBase,
|
||||||
updateCount,
|
effect.State.ReturnBufferInfoBase,
|
||||||
writeOffset,
|
updateCount,
|
||||||
nodeId);
|
writeOffset,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
writeOffset = newUpdateCount;
|
writeOffset = newUpdateCount;
|
||||||
|
|
||||||
|
@ -500,7 +623,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
if (effect.IsEnabled)
|
if (effect.IsEnabled)
|
||||||
{
|
{
|
||||||
bool needInitialization = effect.Parameter.Status == UsageState.Invalid ||
|
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()
|
BiquadFilterParameter parameter = new()
|
||||||
{
|
{
|
||||||
|
@ -512,11 +635,14 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
|
for (int i = 0; i < effect.Parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1),
|
_commandBuffer.GenerateBiquadFilter(
|
||||||
effect.Parameter.Input[i],
|
(int)bufferOffset,
|
||||||
effect.Parameter.Output[i],
|
ref parameter,
|
||||||
needInitialization,
|
effect.State.Slice(i, 1),
|
||||||
nodeId);
|
effect.Parameter.Input[i],
|
||||||
|
effect.Parameter.Output[i],
|
||||||
|
needInitialization,
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -591,15 +717,16 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
updateCount = newUpdateCount;
|
updateCount = newUpdateCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateCaptureEffect(bufferOffset,
|
_commandBuffer.GenerateCaptureEffect(
|
||||||
effect.Parameter.Input[i],
|
bufferOffset,
|
||||||
effect.State.SendBufferInfo,
|
effect.Parameter.Input[i],
|
||||||
effect.IsEnabled,
|
effect.State.SendBufferInfo,
|
||||||
effect.Parameter.BufferStorageSize,
|
effect.IsEnabled,
|
||||||
effect.State.SendBufferInfoBase,
|
effect.Parameter.BufferStorageSize,
|
||||||
updateCount,
|
effect.State.SendBufferInfoBase,
|
||||||
writeOffset,
|
updateCount,
|
||||||
nodeId);
|
writeOffset,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
writeOffset = newUpdateCount;
|
writeOffset = newUpdateCount;
|
||||||
|
|
||||||
|
@ -612,11 +739,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
Debug.Assert(effect.Type == EffectType.Compressor);
|
Debug.Assert(effect.Type == EffectType.Compressor);
|
||||||
|
|
||||||
_commandBuffer.GenerateCompressorEffect(bufferOffset,
|
_commandBuffer.GenerateCompressorEffect(
|
||||||
effect.Parameter,
|
bufferOffset,
|
||||||
effect.State,
|
effect.Parameter,
|
||||||
effect.IsEnabled,
|
effect.State,
|
||||||
nodeId);
|
effect.IsEnabled,
|
||||||
|
nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
|
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
|
||||||
|
@ -629,8 +757,11 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
bool performanceInitialized = false;
|
bool performanceInitialized = false;
|
||||||
|
|
||||||
if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(),
|
if (_performanceManager != null && _performanceManager.GetNextEntry(
|
||||||
isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId))
|
out performanceEntry,
|
||||||
|
effect.GetPerformanceDetailType(),
|
||||||
|
isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix,
|
||||||
|
nodeId))
|
||||||
{
|
{
|
||||||
performanceInitialized = true;
|
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<BiquadFilterState> bqfState = _splitterContext.GetBiquadFilterState(destination);
|
||||||
|
|
||||||
|
if (bqf0.Enable && bqf1.Enable)
|
||||||
|
{
|
||||||
|
_commandBuffer.GenerateMultiTapBiquadFilterAndMix(
|
||||||
|
0f,
|
||||||
|
volume,
|
||||||
|
inputBufferIndex,
|
||||||
|
outputBufferIndex,
|
||||||
|
0,
|
||||||
|
Memory<VoiceUpdateState>.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<VoiceUpdateState>.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<VoiceUpdateState>.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)
|
private void GenerateMix(ref MixState mix)
|
||||||
{
|
{
|
||||||
if (mix.HasAnyDestination())
|
if (mix.HasAnyDestination())
|
||||||
|
@ -722,15 +932,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
int destinationIndex = destinationId++;
|
int destinationIndex = destinationId++;
|
||||||
|
|
||||||
Span<SplitterDestination> destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
|
SplitterDestination destination = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex);
|
||||||
|
|
||||||
if (destinationSpan.IsEmpty)
|
if (destination.IsNull)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref SplitterDestination destination = ref destinationSpan[0];
|
|
||||||
|
|
||||||
if (destination.IsConfigured())
|
if (destination.IsConfigured())
|
||||||
{
|
{
|
||||||
int mixId = destination.DestinationId;
|
int mixId = destination.DestinationId;
|
||||||
|
@ -741,16 +949,32 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
|
uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount);
|
||||||
|
|
||||||
|
bool isFirstMixBuffer = true;
|
||||||
|
|
||||||
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
|
for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++)
|
||||||
{
|
{
|
||||||
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
|
float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex);
|
||||||
|
|
||||||
if (volume != 0.0f)
|
if (volume != 0.0f)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMix(inputBufferIndex,
|
if (destination.IsBiquadFilterEnabled())
|
||||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
{
|
||||||
mix.NodeId,
|
GenerateMixWithSplitter(
|
||||||
volume);
|
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)
|
if (volume != 0.0f)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex,
|
_commandBuffer.GenerateMix(
|
||||||
destinationMix.BufferOffset + bufferDestinationIndex,
|
mix.BufferOffset + bufferIndex,
|
||||||
mix.NodeId,
|
destinationMix.BufferOffset + bufferDestinationIndex,
|
||||||
volume);
|
mix.NodeId,
|
||||||
|
volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -783,11 +1008,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
private void GenerateSubMix(ref MixState subMix)
|
private void GenerateSubMix(ref MixState subMix)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
|
_commandBuffer.GenerateDepopForMixBuffers(
|
||||||
subMix.BufferOffset,
|
_rendererContext.DepopBuffer,
|
||||||
subMix.BufferCount,
|
subMix.BufferOffset,
|
||||||
subMix.NodeId,
|
subMix.BufferCount,
|
||||||
subMix.SampleRate);
|
subMix.NodeId,
|
||||||
|
subMix.SampleRate);
|
||||||
|
|
||||||
GenerateEffects(ref subMix);
|
GenerateEffects(ref subMix);
|
||||||
|
|
||||||
|
@ -847,11 +1073,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
ref MixState finalMix = ref _mixContext.GetFinalState();
|
ref MixState finalMix = ref _mixContext.GetFinalState();
|
||||||
|
|
||||||
_commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer,
|
_commandBuffer.GenerateDepopForMixBuffers(
|
||||||
finalMix.BufferOffset,
|
_rendererContext.DepopBuffer,
|
||||||
finalMix.BufferCount,
|
finalMix.BufferOffset,
|
||||||
finalMix.NodeId,
|
finalMix.BufferCount,
|
||||||
finalMix.SampleRate);
|
finalMix.NodeId,
|
||||||
|
finalMix.SampleRate);
|
||||||
|
|
||||||
GenerateEffects(ref finalMix);
|
GenerateEffects(ref finalMix);
|
||||||
|
|
||||||
|
@ -882,9 +1109,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateVolume(finalMix.Volume,
|
_commandBuffer.GenerateVolume(
|
||||||
finalMix.BufferOffset + bufferIndex,
|
finalMix.Volume,
|
||||||
nodeId);
|
finalMix.BufferOffset + bufferIndex,
|
||||||
|
nodeId);
|
||||||
|
|
||||||
if (performanceSubInitialized)
|
if (performanceSubInitialized)
|
||||||
{
|
{
|
||||||
|
@ -938,41 +1166,45 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
if (useCustomDownMixingCommand)
|
if (useCustomDownMixingCommand)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
|
_commandBuffer.GenerateDownMixSurroundToStereo(
|
||||||
sink.Parameter.Input.AsSpan(),
|
finalMix.BufferOffset,
|
||||||
sink.Parameter.Input.AsSpan(),
|
sink.Parameter.Input.AsSpan(),
|
||||||
sink.DownMixCoefficients,
|
sink.Parameter.Input.AsSpan(),
|
||||||
Constants.InvalidNodeId);
|
sink.DownMixCoefficients,
|
||||||
|
Constants.InvalidNodeId);
|
||||||
}
|
}
|
||||||
// NOTE: We do the downmixing at the DSP level as it's easier that way.
|
// NOTE: We do the downmixing at the DSP level as it's easier that way.
|
||||||
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
|
else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset,
|
_commandBuffer.GenerateDownMixSurroundToStereo(
|
||||||
sink.Parameter.Input.AsSpan(),
|
finalMix.BufferOffset,
|
||||||
sink.Parameter.Input.AsSpan(),
|
sink.Parameter.Input.AsSpan(),
|
||||||
Constants.DefaultSurroundToStereoCoefficients,
|
sink.Parameter.Input.AsSpan(),
|
||||||
Constants.InvalidNodeId);
|
Constants.DefaultSurroundToStereoCoefficients,
|
||||||
|
Constants.InvalidNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandList commandList = _commandBuffer.CommandList;
|
CommandList commandList = _commandBuffer.CommandList;
|
||||||
|
|
||||||
if (sink.UpsamplerState != null)
|
if (sink.UpsamplerState != null)
|
||||||
{
|
{
|
||||||
_commandBuffer.GenerateUpsample(finalMix.BufferOffset,
|
_commandBuffer.GenerateUpsample(
|
||||||
sink.UpsamplerState,
|
finalMix.BufferOffset,
|
||||||
sink.Parameter.InputCount,
|
sink.UpsamplerState,
|
||||||
sink.Parameter.Input.AsSpan(),
|
sink.Parameter.InputCount,
|
||||||
commandList.BufferCount,
|
sink.Parameter.Input.AsSpan(),
|
||||||
commandList.SampleCount,
|
commandList.BufferCount,
|
||||||
commandList.SampleRate,
|
commandList.SampleCount,
|
||||||
Constants.InvalidNodeId);
|
commandList.SampleRate,
|
||||||
|
Constants.InvalidNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateDeviceSink(finalMix.BufferOffset,
|
_commandBuffer.GenerateDeviceSink(
|
||||||
sink,
|
finalMix.BufferOffset,
|
||||||
_rendererContext.SessionId,
|
sink,
|
||||||
commandList.Buffers,
|
_rendererContext.SessionId,
|
||||||
Constants.InvalidNodeId);
|
commandList.Buffers,
|
||||||
|
Constants.InvalidNodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateSink(BaseSink sink, ref MixState finalMix)
|
private void GenerateSink(BaseSink sink, ref MixState finalMix)
|
||||||
|
|
|
@ -170,7 +170,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint Estimate(GroupedBiquadFilterCommand command)
|
public uint Estimate(MultiTapBiquadFilterCommand command)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -184,5 +184,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public uint Estimate(BiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -462,7 +462,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint Estimate(GroupedBiquadFilterCommand command)
|
public uint Estimate(MultiTapBiquadFilterCommand command)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -476,5 +476,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public uint Estimate(BiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -632,7 +632,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual uint Estimate(GroupedBiquadFilterCommand command)
|
public virtual uint Estimate(MultiTapBiquadFilterCommand command)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -646,5 +646,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual uint Estimate(BiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual uint Estimate(MultiTapBiquadFilterAndMixCommand command)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
|
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);
|
Debug.Assert(SampleCount == 160 || SampleCount == 240);
|
||||||
|
|
||||||
|
|
|
@ -210,5 +210,53 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
|
_ => 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,10 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
uint Estimate(UpsampleCommand command);
|
uint Estimate(UpsampleCommand command);
|
||||||
uint Estimate(LimiterCommandVersion1 command);
|
uint Estimate(LimiterCommandVersion1 command);
|
||||||
uint Estimate(LimiterCommandVersion2 command);
|
uint Estimate(LimiterCommandVersion2 command);
|
||||||
uint Estimate(GroupedBiquadFilterCommand command);
|
uint Estimate(MultiTapBiquadFilterCommand command);
|
||||||
uint Estimate(CaptureBufferCommand command);
|
uint Estimate(CaptureBufferCommand command);
|
||||||
uint Estimate(CompressorCommand command);
|
uint Estimate(CompressorCommand command);
|
||||||
|
uint Estimate(BiquadFilterAndMixCommand command);
|
||||||
|
uint Estimate(MultiTapBiquadFilterAndMixCommand command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,11 +225,11 @@ namespace Ryujinx.Audio.Renderer.Server.Mix
|
||||||
|
|
||||||
for (int i = 0; i < splitter.DestinationCount; i++)
|
for (int i = 0; i < splitter.DestinationCount; i++)
|
||||||
{
|
{
|
||||||
Span<SplitterDestination> 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)
|
if (destinationMixId != UnusedMixId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Audio.Renderer.Common;
|
using Ryujinx.Audio.Renderer.Common;
|
||||||
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
using Ryujinx.Audio.Renderer.Parameter;
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
using Ryujinx.Audio.Renderer.Utils;
|
using Ryujinx.Audio.Renderer.Utils;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
@ -15,15 +16,35 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SplitterContext
|
public class SplitterContext
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Amount of biquad filter states per splitter destination.
|
||||||
|
/// </summary>
|
||||||
|
public const int BqfStatesPerDestination = 4;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Storage for <see cref="SplitterState"/>.
|
/// Storage for <see cref="SplitterState"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Memory<SplitterState> _splitters;
|
private Memory<SplitterState> _splitters;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Storage for <see cref="SplitterDestination"/>.
|
/// Storage for <see cref="SplitterDestinationVersion1"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Memory<SplitterDestination> _splitterDestinations;
|
private Memory<SplitterDestinationVersion1> _splitterDestinationsV1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Storage for <see cref="SplitterDestinationVersion2"/>.
|
||||||
|
/// </summary>
|
||||||
|
private Memory<SplitterDestinationVersion2> _splitterDestinationsV2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splitter biquad filtering states.
|
||||||
|
/// </summary>
|
||||||
|
private Memory<BiquadFilterState> _splitterBqfStates;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Version of the splitter context that is being used, currently can be 1 or 2.
|
||||||
|
/// </summary>
|
||||||
|
public int Version { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.
|
/// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.
|
||||||
|
@ -36,12 +57,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// <param name="behaviourContext">The behaviour context.</param>
|
/// <param name="behaviourContext">The behaviour context.</param>
|
||||||
/// <param name="parameter">The audio renderer configuration.</param>
|
/// <param name="parameter">The audio renderer configuration.</param>
|
||||||
/// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param>
|
/// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param>
|
||||||
|
/// <param name="splitterBqfStates">Memory to store the biquad filtering state for splitters during processing.</param>
|
||||||
/// <returns>Return true if the initialization was successful.</returns>
|
/// <returns>Return true if the initialization was successful.</returns>
|
||||||
public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator)
|
public bool Initialize(
|
||||||
|
ref BehaviourContext behaviourContext,
|
||||||
|
ref AudioRendererConfiguration parameter,
|
||||||
|
WorkBufferAllocator workBufferAllocator,
|
||||||
|
Memory<BiquadFilterState> splitterBqfStates)
|
||||||
{
|
{
|
||||||
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
|
if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0)
|
||||||
{
|
{
|
||||||
Setup(Memory<SplitterState>.Empty, Memory<SplitterDestination>.Empty, false);
|
Setup(Memory<SplitterState>.Empty, Memory<SplitterDestinationVersion1>.Empty, Memory<SplitterDestinationVersion2>.Empty, false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -60,23 +86,62 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
splitter = new SplitterState(splitterId++);
|
splitter = new SplitterState(splitterId++);
|
||||||
}
|
}
|
||||||
|
|
||||||
Memory<SplitterDestination> splitterDestinations = workBufferAllocator.Allocate<SplitterDestination>(parameter.SplitterDestinationCount,
|
Memory<SplitterDestinationVersion1> splitterDestinationsV1 = Memory<SplitterDestinationVersion1>.Empty;
|
||||||
SplitterDestination.Alignment);
|
Memory<SplitterDestinationVersion2> splitterDestinationsV2 = Memory<SplitterDestinationVersion2>.Empty;
|
||||||
|
|
||||||
if (splitterDestinations.IsEmpty)
|
if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
|
||||||
{
|
{
|
||||||
return false;
|
Version = 1;
|
||||||
|
|
||||||
|
splitterDestinationsV1 = workBufferAllocator.Allocate<SplitterDestinationVersion1>(parameter.SplitterDestinationCount,
|
||||||
|
SplitterDestinationVersion1.Alignment);
|
||||||
|
|
||||||
|
if (splitterDestinationsV1.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int splitterDestinationId = 0;
|
||||||
|
foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span)
|
||||||
|
{
|
||||||
|
data = new SplitterDestinationVersion1(splitterDestinationId++);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
int splitterDestinationId = 0;
|
|
||||||
foreach (ref SplitterDestination data in splitterDestinations.Span)
|
|
||||||
{
|
{
|
||||||
data = new SplitterDestination(splitterDestinationId++);
|
Version = 2;
|
||||||
|
|
||||||
|
splitterDestinationsV2 = workBufferAllocator.Allocate<SplitterDestinationVersion2>(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<BiquadFilterState>.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SplitterState.InitializeSplitters(splitters.Span);
|
SplitterState.InitializeSplitters(splitters.Span);
|
||||||
|
|
||||||
Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed());
|
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +158,15 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
if (behaviourContext.IsSplitterSupported())
|
if (behaviourContext.IsSplitterSupported())
|
||||||
{
|
{
|
||||||
size = WorkBufferAllocator.GetTargetSize<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment);
|
size = WorkBufferAllocator.GetTargetSize<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment);
|
||||||
size = WorkBufferAllocator.GetTargetSize<SplitterDestination>(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment);
|
|
||||||
|
if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled())
|
||||||
|
{
|
||||||
|
size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion2>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion1>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment);
|
||||||
|
}
|
||||||
|
|
||||||
if (behaviourContext.IsSplitterBugFixed())
|
if (behaviourContext.IsSplitterBugFixed())
|
||||||
{
|
{
|
||||||
|
@ -110,12 +183,18 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// Setup the <see cref="SplitterContext"/> instance.
|
/// Setup the <see cref="SplitterContext"/> instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
|
/// <param name="splitters">The <see cref="SplitterState"/> storage.</param>
|
||||||
/// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param>
|
/// <param name="splitterDestinationsV1">The <see cref="SplitterDestinationVersion1"/> storage.</param>
|
||||||
|
/// <param name="splitterDestinationsV2">The <see cref="SplitterDestinationVersion2"/> storage.</param>
|
||||||
/// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param>
|
/// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param>
|
||||||
private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed)
|
private void Setup(
|
||||||
|
Memory<SplitterState> splitters,
|
||||||
|
Memory<SplitterDestinationVersion1> splitterDestinationsV1,
|
||||||
|
Memory<SplitterDestinationVersion2> splitterDestinationsV2,
|
||||||
|
bool isBugFixed)
|
||||||
{
|
{
|
||||||
_splitters = splitters;
|
_splitters = splitters;
|
||||||
_splitterDestinations = splitterDestinations;
|
_splitterDestinationsV1 = splitterDestinationsV1;
|
||||||
|
_splitterDestinationsV2 = splitterDestinationsV2;
|
||||||
IsBugFixed = isBugFixed;
|
IsBugFixed = isBugFixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +220,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _splitterDestinations.Length / _splitters.Length;
|
int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length;
|
||||||
|
|
||||||
|
return length / _splitters.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -178,7 +259,39 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update one or multiple <see cref="SplitterDestination"/> from user parameters.
|
/// Update one splitter destination data from user parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The raw data after the splitter header.</param>
|
||||||
|
/// <returns>True if the update was successful, false otherwise</returns>
|
||||||
|
private bool UpdateData<T>(ref SequenceReader<byte> input) where T : unmanaged, ISplitterDestinationInParameter
|
||||||
|
{
|
||||||
|
ref readonly T parameter = ref input.GetRefOrRefToCopy<T>(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<T>());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update one or multiple splitter destination data from user parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="inputHeader">The splitter header.</param>
|
/// <param name="inputHeader">The splitter header.</param>
|
||||||
/// <param name="input">The raw data after the splitter header.</param>
|
/// <param name="input">The raw data after the splitter header.</param>
|
||||||
|
@ -186,23 +299,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
{
|
{
|
||||||
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
|
for (int i = 0; i < inputHeader.SplitterDestinationCount; i++)
|
||||||
{
|
{
|
||||||
ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _);
|
if (Version == 1)
|
||||||
|
|
||||||
Debug.Assert(parameter.IsMagicValid());
|
|
||||||
|
|
||||||
if (parameter.IsMagicValid())
|
|
||||||
{
|
{
|
||||||
if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length)
|
if (!UpdateData<SplitterDestinationInParameterVersion1>(ref input))
|
||||||
{
|
{
|
||||||
ref SplitterDestination destination = ref GetDestination(parameter.Id);
|
break;
|
||||||
|
}
|
||||||
destination.Update(parameter);
|
}
|
||||||
|
else if (Version == 2)
|
||||||
|
{
|
||||||
|
if (!UpdateData<SplitterDestinationInParameterVersion2>(ref input))
|
||||||
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>());
|
Debug.Fail($"Invalid splitter context version {Version}.");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +327,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// <returns>Return true if the update was successful.</returns>
|
/// <returns>Return true if the update was successful.</returns>
|
||||||
public bool Update(ref SequenceReader<byte> input)
|
public bool Update(ref SequenceReader<byte> input)
|
||||||
{
|
{
|
||||||
if (_splitterDestinations.IsEmpty || _splitters.IsEmpty)
|
if (!UsingSplitter())
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -251,45 +364,52 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.
|
/// Get a reference to the splitter destination data at the given <paramref name="id"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The index to use.</param>
|
/// <param name="id">The index to use.</param>
|
||||||
/// <returns>A reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.</returns>
|
/// <returns>A reference to the splitter destination data at the given <paramref name="id"/>.</returns>
|
||||||
public ref SplitterDestination GetDestination(int id)
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.
|
/// Get a <see cref="SplitterDestination"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The index to use.</param>
|
|
||||||
/// <returns>A <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.</returns>
|
|
||||||
public Memory<SplitterDestination> GetDestinationMemory(int id)
|
|
||||||
{
|
|
||||||
return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a <see cref="Span{SplitterDestination}"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param>
|
/// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param>
|
||||||
/// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param>
|
/// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param>
|
||||||
/// <returns>A <see cref="Span{SplitterDestination}"/>.</returns>
|
/// <returns>A <see cref="SplitterDestination"/>.</returns>
|
||||||
public Span<SplitterDestination> GetDestination(int id, int destinationId)
|
public SplitterDestination GetDestination(int id, int destinationId)
|
||||||
{
|
{
|
||||||
ref SplitterState splitter = ref GetState(id);
|
ref SplitterState splitter = ref GetState(id);
|
||||||
|
|
||||||
return splitter.GetData(destinationId);
|
return splitter.GetData(destinationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the biquad filter state for a given splitter destination.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destination">The splitter destination.</param>
|
||||||
|
/// <returns>Biquad filter state for the specified destination.</returns>
|
||||||
|
public Memory<BiquadFilterState> GetBiquadFilterState(SplitterDestination destination)
|
||||||
|
{
|
||||||
|
return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return true if the audio renderer has any splitters.
|
/// Return true if the audio renderer has any splitters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if the audio renderer has any splitters.</returns>
|
/// <returns>True if the audio renderer has any splitters.</returns>
|
||||||
public bool UsingSplitter()
|
public bool UsingSplitter()
|
||||||
{
|
{
|
||||||
return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty;
|
return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,115 +1,198 @@
|
||||||
using Ryujinx.Audio.Renderer.Parameter;
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server state for a splitter destination.
|
/// Server state for a splitter destination.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
|
public ref struct SplitterDestination
|
||||||
public struct SplitterDestination
|
|
||||||
{
|
{
|
||||||
public const int Alignment = 0x10;
|
private ref SplitterDestinationVersion1 _v1;
|
||||||
|
private ref SplitterDestinationVersion2 _v2;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The unique id of this <see cref="SplitterDestination"/>.
|
/// Checks if the splitter destination data reference is null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id;
|
public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mix to output the result of the splitter.
|
/// The splitter unique id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DestinationId;
|
public int Id
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mix buffer volumes storage.
|
|
||||||
/// </summary>
|
|
||||||
private MixArray _mix;
|
|
||||||
private MixArray _previousMix;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pointer to the next linked element.
|
|
||||||
/// </summary>
|
|
||||||
private unsafe SplitterDestination* _next;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set to true if in use.
|
|
||||||
/// </summary>
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool IsUsed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set to true if the internal state need to be updated.
|
|
||||||
/// </summary>
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool NeedToUpdateInternalState;
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)]
|
|
||||||
private struct MixArray { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mix buffer volumes.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
|
||||||
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Previous mix buffer volumes.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
|
||||||
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the <see cref="Span{SplitterDestination}"/> of the next element or <see cref="Span{SplitterDestination}.Empty"/> if not present.
|
|
||||||
/// </summary>
|
|
||||||
public readonly Span<SplitterDestination> Next
|
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
unsafe
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
{
|
{
|
||||||
return _next != null ? new Span<SplitterDestination>(_next, 1) : Span<SplitterDestination>.Empty;
|
if (Unsafe.IsNullRef(ref _v1))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _v1.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _v2.Id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="SplitterDestination"/>.
|
/// The mix to output the result of the splitter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The unique id of this <see cref="SplitterDestination"/>.</param>
|
public int DestinationId
|
||||||
public SplitterDestination(int id) : this()
|
|
||||||
{
|
{
|
||||||
Id = id;
|
get
|
||||||
DestinationId = Constants.UnusedMixId;
|
{
|
||||||
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
ClearVolumes();
|
{
|
||||||
|
if (Unsafe.IsNullRef(ref _v1))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _v1.DestinationId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _v2.DestinationId;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update the <see cref="SplitterDestination"/> from user parameter.
|
/// Mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
public Span<float> MixBufferVolume
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
|
{
|
||||||
|
if (Unsafe.IsNullRef(ref _v1))
|
||||||
|
{
|
||||||
|
return Span<float>.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _v1.MixBufferVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _v2.MixBufferVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Previous mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
public Span<float> PreviousMixBufferVolume
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
|
{
|
||||||
|
if (Unsafe.IsNullRef(ref _v1))
|
||||||
|
{
|
||||||
|
return Span<float>.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _v1.PreviousMixBufferVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _v2.PreviousMixBufferVolume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the <see cref="SplitterDestination"/> of the next element or null if not present.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new splitter destination wrapper for the version 1 splitter destination data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="v1">Version 1 splitter destination data</param>
|
||||||
|
public SplitterDestination(ref SplitterDestinationVersion1 v1)
|
||||||
|
{
|
||||||
|
_v1 = ref v1;
|
||||||
|
_v2 = ref Unsafe.NullRef<SplitterDestinationVersion2>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new splitter destination wrapper for the version 2 splitter destination data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="v2">Version 2 splitter destination data</param>
|
||||||
|
public SplitterDestination(ref SplitterDestinationVersion2 v2)
|
||||||
|
{
|
||||||
|
|
||||||
|
_v1 = ref Unsafe.NullRef<SplitterDestinationVersion1>();
|
||||||
|
_v2 = ref v2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new splitter destination wrapper for the splitter destination data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="v1">Version 1 splitter destination data</param>
|
||||||
|
/// <param name="v2">Version 2 splitter destination data</param>
|
||||||
|
public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2)
|
||||||
|
{
|
||||||
|
_v1 = ref Unsafe.AsRef<SplitterDestinationVersion1>(v1);
|
||||||
|
_v2 = ref Unsafe.AsRef<SplitterDestinationVersion2>(v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the splitter destination data from user parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="parameter">The user parameter.</param>
|
/// <param name="parameter">The user parameter.</param>
|
||||||
public void Update(SplitterDestinationInParameter parameter)
|
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
|
||||||
{
|
{
|
||||||
Debug.Assert(Id == parameter.Id);
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
|
|
||||||
if (parameter.IsMagicValid() && Id == parameter.Id)
|
|
||||||
{
|
{
|
||||||
DestinationId = parameter.DestinationId;
|
_v1.Update(parameter);
|
||||||
|
}
|
||||||
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
|
else
|
||||||
|
{
|
||||||
if (!IsUsed && parameter.IsUsed)
|
_v2.Update(parameter);
|
||||||
{
|
|
||||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
|
||||||
|
|
||||||
NeedToUpdateInternalState = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsUsed = parameter.IsUsed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,12 +201,14 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateInternalState()
|
public void UpdateInternalState()
|
||||||
{
|
{
|
||||||
if (IsUsed && NeedToUpdateInternalState)
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
{
|
{
|
||||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
_v1.UpdateInternalState();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_v2.UpdateInternalState();
|
||||||
}
|
}
|
||||||
|
|
||||||
NeedToUpdateInternalState = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -131,16 +216,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void MarkAsNeedToUpdateInternalState()
|
public void MarkAsNeedToUpdateInternalState()
|
||||||
{
|
{
|
||||||
NeedToUpdateInternalState = true;
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
|
{
|
||||||
|
_v1.MarkAsNeedToUpdateInternalState();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_v2.MarkAsNeedToUpdateInternalState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return true if the <see cref="SplitterDestination"/> is used and has a destination.
|
/// Return true if the splitter destination is used and has a destination.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>True if the <see cref="SplitterDestination"/> is used and has a destination.</returns>
|
/// <returns>True if the splitter destination is used and has a destination.</returns>
|
||||||
public readonly bool IsConfigured()
|
public readonly bool IsConfigured()
|
||||||
{
|
{
|
||||||
return IsUsed && DestinationId != Constants.UnusedMixId;
|
return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -150,9 +242,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// <returns>The volume for the given destination.</returns>
|
/// <returns>The volume for the given destination.</returns>
|
||||||
public float GetMixVolume(int destinationIndex)
|
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];
|
/// <summary>
|
||||||
|
/// Get the previous volume for a given destination.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destinationIndex">The destination index to use.</param>
|
||||||
|
/// <returns>The volume for the given destination.</returns>
|
||||||
|
public float GetMixVolumePrev(int destinationIndex)
|
||||||
|
{
|
||||||
|
return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -160,22 +260,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ClearVolumes()
|
public void ClearVolumes()
|
||||||
{
|
{
|
||||||
MixBufferVolume.Clear();
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
PreviousMixBufferVolume.Clear();
|
{
|
||||||
|
_v1.ClearVolumes();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_v2.ClearVolumes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Link the next element to the given <see cref="SplitterDestination"/>.
|
/// Link the next element to the given splitter destination.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="next">The given <see cref="SplitterDestination"/> to link.</param>
|
/// <param name="next">The given splitter destination to link.</param>
|
||||||
public void Link(ref SplitterDestination next)
|
public void Link(SplitterDestination next)
|
||||||
{
|
{
|
||||||
unsafe
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
{
|
{
|
||||||
fixed (SplitterDestination* nextPtr = &next)
|
Debug.Assert(!Unsafe.IsNullRef(ref next._v1));
|
||||||
{
|
|
||||||
_next = nextPtr;
|
_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
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Unlink()
|
public void Unlink()
|
||||||
{
|
{
|
||||||
unsafe
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
{
|
{
|
||||||
_next = null;
|
_v1.Unlink();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_v2.Unlink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any biquad filter is enabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if any biquad filter is enabled.</returns>
|
||||||
|
public bool IsBiquadFilterEnabled()
|
||||||
|
{
|
||||||
|
return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any biquad filter was previously enabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if any biquad filter was previously enabled.</returns>
|
||||||
|
public bool IsBiquadFilterEnabledPrev()
|
||||||
|
{
|
||||||
|
return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the biquad filter parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Biquad filter index (0 or 1).</param>
|
||||||
|
/// <returns>Biquad filter parameters.</returns>
|
||||||
|
public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
|
||||||
|
{
|
||||||
|
Debug.Assert(!Unsafe.IsNullRef(ref _v2));
|
||||||
|
|
||||||
|
return ref _v2.GetBiquadFilterParameter(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any biquad filter was previously enabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Biquad filter index (0 or 1).</param>
|
||||||
|
public void UpdateBiquadFilterEnabledPrev(int index)
|
||||||
|
{
|
||||||
|
if (!Unsafe.IsNullRef(ref _v2))
|
||||||
|
{
|
||||||
|
_v2.UpdateBiquadFilterEnabledPrev(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Reference for the version 1 splitter destination data.</returns>
|
||||||
|
public ref SplitterDestinationVersion1 GetV1RefOrNull()
|
||||||
|
{
|
||||||
|
return ref _v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Reference for the version 2 splitter destination data.</returns>
|
||||||
|
public ref SplitterDestinationVersion2 GetV2RefOrNull()
|
||||||
|
{
|
||||||
|
return ref _v2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Server state for a splitter destination (version 1).
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)]
|
||||||
|
public struct SplitterDestinationVersion1
|
||||||
|
{
|
||||||
|
public const int Alignment = 0x10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The unique id of this <see cref="SplitterDestinationVersion1"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int Id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mix to output the result of the splitter.
|
||||||
|
/// </summary>
|
||||||
|
public int DestinationId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes storage.
|
||||||
|
/// </summary>
|
||||||
|
private MixArray _mix;
|
||||||
|
private MixArray _previousMix;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pointer to the next linked element.
|
||||||
|
/// </summary>
|
||||||
|
private unsafe SplitterDestinationVersion1* _next;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if in use.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool IsUsed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if the internal state need to be updated.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool NeedToUpdateInternalState;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
||||||
|
private struct MixArray { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Previous mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the reference of the next element or null if not present.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ref SplitterDestinationVersion1 Next
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
return ref Unsafe.AsRef<SplitterDestinationVersion1>(_next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="SplitterDestinationVersion1"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The unique id of this <see cref="SplitterDestinationVersion1"/>.</param>
|
||||||
|
public SplitterDestinationVersion1(int id) : this()
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
DestinationId = Constants.UnusedMixId;
|
||||||
|
|
||||||
|
ClearVolumes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The user parameter.</param>
|
||||||
|
public void Update<T>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the internal state of the instance.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateInternalState()
|
||||||
|
{
|
||||||
|
if (IsUsed && NeedToUpdateInternalState)
|
||||||
|
{
|
||||||
|
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
NeedToUpdateInternalState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the update internal state marker.
|
||||||
|
/// </summary>
|
||||||
|
public void MarkAsNeedToUpdateInternalState()
|
||||||
|
{
|
||||||
|
NeedToUpdateInternalState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return true if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.</returns>
|
||||||
|
public readonly bool IsConfigured()
|
||||||
|
{
|
||||||
|
return IsUsed && DestinationId != Constants.UnusedMixId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the volume for a given destination.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destinationIndex">The destination index to use.</param>
|
||||||
|
/// <returns>The volume for the given destination.</returns>
|
||||||
|
public float GetMixVolume(int destinationIndex)
|
||||||
|
{
|
||||||
|
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||||
|
|
||||||
|
return MixBufferVolume[destinationIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the previous volume for a given destination.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destinationIndex">The destination index to use.</param>
|
||||||
|
/// <returns>The volume for the given destination.</returns>
|
||||||
|
public float GetMixVolumePrev(int destinationIndex)
|
||||||
|
{
|
||||||
|
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||||
|
|
||||||
|
return PreviousMixBufferVolume[destinationIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear the volumes.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearVolumes()
|
||||||
|
{
|
||||||
|
MixBufferVolume.Clear();
|
||||||
|
PreviousMixBufferVolume.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Link the next element to the given <see cref="SplitterDestinationVersion1"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The given <see cref="SplitterDestinationVersion1"/> to link.</param>
|
||||||
|
public void Link(ref SplitterDestinationVersion1 next)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (SplitterDestinationVersion1* nextPtr = &next)
|
||||||
|
{
|
||||||
|
_next = nextPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove the link to the next element.
|
||||||
|
/// </summary>
|
||||||
|
public void Unlink()
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
_next = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Server state for a splitter destination (version 2).
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)]
|
||||||
|
public struct SplitterDestinationVersion2
|
||||||
|
{
|
||||||
|
public const int Alignment = 0x10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The unique id of this <see cref="SplitterDestinationVersion2"/>.
|
||||||
|
/// </summary>
|
||||||
|
public int Id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The mix to output the result of the splitter.
|
||||||
|
/// </summary>
|
||||||
|
public int DestinationId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes storage.
|
||||||
|
/// </summary>
|
||||||
|
private MixArray _mix;
|
||||||
|
private MixArray _previousMix;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pointer to the next linked element.
|
||||||
|
/// </summary>
|
||||||
|
private unsafe SplitterDestinationVersion2* _next;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if in use.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool IsUsed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true if the internal state need to be updated.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool NeedToUpdateInternalState;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
||||||
|
private struct MixArray { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Previous mix buffer volumes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Used when a splitter id is specified in the mix.</remarks>
|
||||||
|
public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the reference of the next element or null if not present.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ref SplitterDestinationVersion2 Next
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
return ref Unsafe.AsRef<SplitterDestinationVersion2>(_next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Array2<BiquadFilterParameter> _biquadFilters;
|
||||||
|
|
||||||
|
private Array2<bool> _isPreviousBiquadFilterEnabled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="SplitterDestinationVersion2"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The unique id of this <see cref="SplitterDestinationVersion2"/>.</param>
|
||||||
|
public SplitterDestinationVersion2(int id) : this()
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
DestinationId = Constants.UnusedMixId;
|
||||||
|
|
||||||
|
ClearVolumes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parameter">The user parameter.</param>
|
||||||
|
public void Update<T>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the internal state of the instance.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateInternalState()
|
||||||
|
{
|
||||||
|
if (IsUsed && NeedToUpdateInternalState)
|
||||||
|
{
|
||||||
|
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
NeedToUpdateInternalState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the update internal state marker.
|
||||||
|
/// </summary>
|
||||||
|
public void MarkAsNeedToUpdateInternalState()
|
||||||
|
{
|
||||||
|
NeedToUpdateInternalState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return true if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.</returns>
|
||||||
|
public readonly bool IsConfigured()
|
||||||
|
{
|
||||||
|
return IsUsed && DestinationId != Constants.UnusedMixId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the volume for a given destination.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destinationIndex">The destination index to use.</param>
|
||||||
|
/// <returns>The volume for the given destination.</returns>
|
||||||
|
public float GetMixVolume(int destinationIndex)
|
||||||
|
{
|
||||||
|
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||||
|
|
||||||
|
return MixBufferVolume[destinationIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the previous volume for a given destination.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="destinationIndex">The destination index to use.</param>
|
||||||
|
/// <returns>The volume for the given destination.</returns>
|
||||||
|
public float GetMixVolumePrev(int destinationIndex)
|
||||||
|
{
|
||||||
|
Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax);
|
||||||
|
|
||||||
|
return PreviousMixBufferVolume[destinationIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear the volumes.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearVolumes()
|
||||||
|
{
|
||||||
|
MixBufferVolume.Clear();
|
||||||
|
PreviousMixBufferVolume.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Link the next element to the given <see cref="SplitterDestinationVersion2"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The given <see cref="SplitterDestinationVersion2"/> to link.</param>
|
||||||
|
public void Link(ref SplitterDestinationVersion2 next)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (SplitterDestinationVersion2* nextPtr = &next)
|
||||||
|
{
|
||||||
|
_next = nextPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove the link to the next element.
|
||||||
|
/// </summary>
|
||||||
|
public void Unlink()
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
_next = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any biquad filter is enabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if any biquad filter is enabled.</returns>
|
||||||
|
public bool IsBiquadFilterEnabled()
|
||||||
|
{
|
||||||
|
return _biquadFilters[0].Enable || _biquadFilters[1].Enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any biquad filter was previously enabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if any biquad filter was previously enabled.</returns>
|
||||||
|
public bool IsBiquadFilterEnabledPrev()
|
||||||
|
{
|
||||||
|
return _isPreviousBiquadFilterEnabled[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the biquad filter parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Biquad filter index (0 or 1).</param>
|
||||||
|
/// <returns>Biquad filter parameters.</returns>
|
||||||
|
public ref BiquadFilterParameter GetBiquadFilterParameter(int index)
|
||||||
|
{
|
||||||
|
return ref _biquadFilters[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any biquad filter was previously enabled.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Biquad filter index (0 or 1).</param>
|
||||||
|
public void UpdateBiquadFilterEnabledPrev(int index)
|
||||||
|
{
|
||||||
|
_isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
{
|
{
|
||||||
public const int Alignment = 0x10;
|
public const int Alignment = 0x10;
|
||||||
|
|
||||||
|
private delegate void SplitterDestinationAction(SplitterDestination destination, int index);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The unique id of this <see cref="SplitterState"/>.
|
/// The unique id of this <see cref="SplitterState"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
public uint SampleRate;
|
public uint SampleRate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Count of splitter destinations (<see cref="SplitterDestination"/>).
|
/// Count of splitter destinations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DestinationCount;
|
public int DestinationCount;
|
||||||
|
|
||||||
|
@ -37,20 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
public bool HasNewConnection;
|
public bool HasNewConnection;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Linked list of <see cref="SplitterDestination"/>.
|
/// Linked list of <see cref="SplitterDestinationVersion1"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private unsafe SplitterDestination* _destinationsData;
|
private unsafe SplitterDestinationVersion1* _destinationDataV1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Span to the first element of the linked list of <see cref="SplitterDestination"/>.
|
/// Linked list of <see cref="SplitterDestinationVersion2"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Span<SplitterDestination> Destinations
|
private unsafe SplitterDestinationVersion2* _destinationDataV2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// First element of the linked list of splitter destinations data.
|
||||||
|
/// </summary>
|
||||||
|
public readonly SplitterDestination Destination
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
return (IntPtr)_destinationsData != IntPtr.Zero ? new Span<SplitterDestination>(_destinationsData, 1) : Span<SplitterDestination>.Empty;
|
return new SplitterDestination(_destinationDataV1, _destinationDataV2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,20 +71,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
Id = id;
|
Id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Span<SplitterDestination> GetData(int index)
|
public readonly SplitterDestination GetData(int index)
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
Span<SplitterDestination> result = Destinations;
|
SplitterDestination result = Destination;
|
||||||
|
|
||||||
while (i < index)
|
while (i < index)
|
||||||
{
|
{
|
||||||
if (result.IsEmpty)
|
if (result.IsNull)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = result[0].Next;
|
result = result.Next;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,25 +100,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utility function to apply a given <see cref="SpanAction{T, TArg}"/> to all <see cref="Destinations"/>.
|
/// Utility function to apply an action to all <see cref="Destination"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">The action to execute on each elements.</param>
|
/// <param name="action">The action to execute on each elements.</param>
|
||||||
private readonly void ForEachDestination(SpanAction<SplitterDestination, int> action)
|
private readonly void ForEachDestination(SplitterDestinationAction action)
|
||||||
{
|
{
|
||||||
Span<SplitterDestination> temp = Destinations;
|
SplitterDestination temp = Destination;
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (temp.IsEmpty)
|
if (temp.IsNull)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<SplitterDestination> next = temp[0].Next;
|
SplitterDestination next = temp.Next;
|
||||||
|
|
||||||
action.Invoke(temp, i++);
|
action(temp, i++);
|
||||||
|
|
||||||
temp = next;
|
temp = next;
|
||||||
}
|
}
|
||||||
|
@ -142,9 +149,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
{
|
{
|
||||||
input.ReadLittleEndian(out int destinationId);
|
input.ReadLittleEndian(out int destinationId);
|
||||||
|
|
||||||
Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationId);
|
SplitterDestination destination = context.GetDestination(destinationId);
|
||||||
|
|
||||||
SetDestination(ref destination.Span[0]);
|
SetDestination(destination);
|
||||||
|
|
||||||
DestinationCount = destinationCount;
|
DestinationCount = destinationCount;
|
||||||
|
|
||||||
|
@ -152,9 +159,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
{
|
{
|
||||||
input.ReadLittleEndian(out destinationId);
|
input.ReadLittleEndian(out destinationId);
|
||||||
|
|
||||||
Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationId);
|
SplitterDestination nextDestination = context.GetDestination(destinationId);
|
||||||
|
|
||||||
destination.Span[0].Link(ref nextDestination.Span[0]);
|
destination.Link(nextDestination);
|
||||||
destination = nextDestination;
|
destination = nextDestination;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,16 +181,21 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the head of the linked list of <see cref="Destinations"/>.
|
/// Set the head of the linked list of <see cref="Destination"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newValue">A reference to a <see cref="SplitterDestination"/>.</param>
|
/// <param name="newValue">New destination value.</param>
|
||||||
public void SetDestination(ref SplitterDestination newValue)
|
public void SetDestination(SplitterDestination newValue)
|
||||||
{
|
{
|
||||||
unsafe
|
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
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly void UpdateInternalState()
|
public readonly void UpdateInternalState()
|
||||||
{
|
{
|
||||||
ForEachDestination((destination, _) => destination[0].UpdateInternalState());
|
ForEachDestination((destination, _) => destination.UpdateInternalState());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clear all links from the <see cref="Destinations"/>.
|
/// Clear all links from the <see cref="Destination"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ClearLinks()
|
public void ClearLinks()
|
||||||
{
|
{
|
||||||
ForEachDestination((destination, _) => destination[0].Unlink());
|
ForEachDestination((destination, _) => destination.Unlink());
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
_destinationsData = (SplitterDestination*)IntPtr.Zero;
|
_destinationDataV1 = null;
|
||||||
|
_destinationDataV2 = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +232,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
splitter._destinationsData = (SplitterDestination*)IntPtr.Zero;
|
splitter._destinationDataV1 = null;
|
||||||
|
splitter._destinationDataV2 = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
splitter.DestinationCount = 0;
|
splitter.DestinationCount = 0;
|
||||||
|
|
140
src/Ryujinx.Common/Memory/MemoryOwner.cs
Normal file
140
src/Ryujinx.Common/Memory/MemoryOwner.cs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and fast <see cref="Span{T}"/>
|
||||||
|
/// accessor, with memory allocated from <seealso cref="ArrayPool{T}.Shared"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of item to store.</typeparam>
|
||||||
|
public sealed class MemoryOwner<T> : IMemoryOwner<T>
|
||||||
|
{
|
||||||
|
private readonly int _length;
|
||||||
|
private T[]? _array;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">The length of the new memory buffer to use</param>
|
||||||
|
private MemoryOwner(int length)
|
||||||
|
{
|
||||||
|
_length = length;
|
||||||
|
_array = ArrayPool<T>.Shared.Rent(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">The length of the new memory buffer to use</param>
|
||||||
|
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static MemoryOwner<T> Rent(int length) => new(length);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length and the content cleared.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">The length of the new memory buffer to use</param>
|
||||||
|
/// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length and the content cleared</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static MemoryOwner<T> RentCleared(int length)
|
||||||
|
{
|
||||||
|
MemoryOwner<T> result = new(length);
|
||||||
|
|
||||||
|
result._array.AsSpan(0, length).Clear();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="MemoryOwner{T}"/> instance with the content copied from the specified buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer to copy</param>
|
||||||
|
/// <returns>A <see cref="MemoryOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
|
||||||
|
public static MemoryOwner<T> RentCopy(ReadOnlySpan<T> buffer)
|
||||||
|
{
|
||||||
|
MemoryOwner<T> result = new(buffer.Length);
|
||||||
|
|
||||||
|
buffer.CopyTo(result._array);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of items in the current instance.
|
||||||
|
/// </summary>
|
||||||
|
public int Length
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Memory<T> Memory
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
T[]? array = _array;
|
||||||
|
|
||||||
|
if (array is null)
|
||||||
|
{
|
||||||
|
ThrowObjectDisposedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(array, 0, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Uses a trick made possible by the .NET 6+ runtime array layout.
|
||||||
|
/// </remarks>
|
||||||
|
public Span<T> Span
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
T[]? array = _array;
|
||||||
|
|
||||||
|
if (array is null)
|
||||||
|
{
|
||||||
|
ThrowObjectDisposedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array);
|
||||||
|
|
||||||
|
return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
T[]? array = Interlocked.Exchange(ref _array, null);
|
||||||
|
|
||||||
|
if (array is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<T>.Shared.Return(array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws an <see cref="ObjectDisposedException"/> when <see cref="_array"/> is <see langword="null"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DoesNotReturn]
|
||||||
|
private static void ThrowObjectDisposedException()
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The buffer has already been disposed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
114
src/Ryujinx.Common/Memory/SpanOwner.cs
Normal file
114
src/Ryujinx.Common/Memory/SpanOwner.cs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A stack-only type that rents a buffer of a specified length from <seealso cref="ArrayPool{T}.Shared"/>.
|
||||||
|
/// It does not implement <see cref="IDisposable"/> to avoid being boxed, but should still be disposed. This
|
||||||
|
/// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method.
|
||||||
|
/// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals.
|
||||||
|
/// For all these reasons, all usage should be with a `using` block or statement.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of item to store.</typeparam>
|
||||||
|
public readonly ref struct SpanOwner<T>
|
||||||
|
{
|
||||||
|
private readonly int _length;
|
||||||
|
private readonly T[] _array;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">The length of the new memory buffer to use</param>
|
||||||
|
private SpanOwner(int length)
|
||||||
|
{
|
||||||
|
_length = length;
|
||||||
|
_array = ArrayPool<T>.Shared.Rent(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an empty <see cref="SpanOwner{T}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public static SpanOwner<T> Empty
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => new(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SpanOwner{T}"/> instance with the specified length.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">The length of the new memory buffer to use</param>
|
||||||
|
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static SpanOwner<T> Rent(int length) => new(length);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SpanOwner{T}"/> instance with the length and the content cleared.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="length">The length of the new memory buffer to use</param>
|
||||||
|
/// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length and the content cleared</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static SpanOwner<T> RentCleared(int length)
|
||||||
|
{
|
||||||
|
SpanOwner<T> result = new(length);
|
||||||
|
|
||||||
|
result._array.AsSpan(0, length).Clear();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SpanOwner{T}"/> instance with the content copied from the specified buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer to copy</param>
|
||||||
|
/// <returns>A <see cref="SpanOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
|
||||||
|
public static SpanOwner<T> RentCopy(ReadOnlySpan<T> buffer)
|
||||||
|
{
|
||||||
|
SpanOwner<T> result = new(buffer.Length);
|
||||||
|
|
||||||
|
buffer.CopyTo(result._array);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of items in the current instance
|
||||||
|
/// </summary>
|
||||||
|
public int Length
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Uses a trick made possible by the .NET 6+ runtime array layout.
|
||||||
|
/// </remarks>
|
||||||
|
public Span<T> Span
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
|
||||||
|
|
||||||
|
return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
ArrayPool<T>.Shared.Return(_array, RuntimeHelpers.IsReferenceOrContainsReferences<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,13 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public enum BufferAccess
|
public enum BufferAccess
|
||||||
{
|
{
|
||||||
Default = 0,
|
Default = 0,
|
||||||
FlushPersistent = 1 << 0,
|
HostMemory = 1,
|
||||||
Stream = 1 << 1,
|
DeviceMemory = 2,
|
||||||
SparseCompatible = 1 << 2,
|
DeviceMemoryMapped = 3,
|
||||||
|
|
||||||
|
MemoryTypeMask = 0xf,
|
||||||
|
|
||||||
|
Stream = 1 << 4,
|
||||||
|
SparseCompatible = 1 << 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
public readonly TargetApi Api;
|
public readonly TargetApi Api;
|
||||||
public readonly string VendorName;
|
public readonly string VendorName;
|
||||||
|
public readonly SystemMemoryType MemoryType;
|
||||||
|
|
||||||
public readonly bool HasFrontFacingBug;
|
public readonly bool HasFrontFacingBug;
|
||||||
public readonly bool HasVectorIndexingBug;
|
public readonly bool HasVectorIndexingBug;
|
||||||
|
@ -50,6 +51,13 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public readonly bool SupportsIndirectParameters;
|
public readonly bool SupportsIndirectParameters;
|
||||||
public readonly bool SupportsDepthClipControl;
|
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 MaximumUniformBuffersPerStage;
|
||||||
public readonly uint MaximumStorageBuffersPerStage;
|
public readonly uint MaximumStorageBuffersPerStage;
|
||||||
public readonly uint MaximumTexturesPerStage;
|
public readonly uint MaximumTexturesPerStage;
|
||||||
|
@ -66,6 +74,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
public Capabilities(
|
public Capabilities(
|
||||||
TargetApi api,
|
TargetApi api,
|
||||||
string vendorName,
|
string vendorName,
|
||||||
|
SystemMemoryType memoryType,
|
||||||
bool hasFrontFacingBug,
|
bool hasFrontFacingBug,
|
||||||
bool hasVectorIndexingBug,
|
bool hasVectorIndexingBug,
|
||||||
bool needsFragmentOutputSpecialization,
|
bool needsFragmentOutputSpecialization,
|
||||||
|
@ -107,6 +116,12 @@ namespace Ryujinx.Graphics.GAL
|
||||||
bool supportsViewportSwizzle,
|
bool supportsViewportSwizzle,
|
||||||
bool supportsIndirectParameters,
|
bool supportsIndirectParameters,
|
||||||
bool supportsDepthClipControl,
|
bool supportsDepthClipControl,
|
||||||
|
int uniformBufferSetIndex,
|
||||||
|
int storageBufferSetIndex,
|
||||||
|
int textureSetIndex,
|
||||||
|
int imageSetIndex,
|
||||||
|
int extraSetBaseIndex,
|
||||||
|
int maximumExtraSets,
|
||||||
uint maximumUniformBuffersPerStage,
|
uint maximumUniformBuffersPerStage,
|
||||||
uint maximumStorageBuffersPerStage,
|
uint maximumStorageBuffersPerStage,
|
||||||
uint maximumTexturesPerStage,
|
uint maximumTexturesPerStage,
|
||||||
|
@ -120,6 +135,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
Api = api;
|
Api = api;
|
||||||
VendorName = vendorName;
|
VendorName = vendorName;
|
||||||
|
MemoryType = memoryType;
|
||||||
HasFrontFacingBug = hasFrontFacingBug;
|
HasFrontFacingBug = hasFrontFacingBug;
|
||||||
HasVectorIndexingBug = hasVectorIndexingBug;
|
HasVectorIndexingBug = hasVectorIndexingBug;
|
||||||
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
|
NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization;
|
||||||
|
@ -161,6 +177,12 @@ namespace Ryujinx.Graphics.GAL
|
||||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||||
SupportsIndirectParameters = supportsIndirectParameters;
|
SupportsIndirectParameters = supportsIndirectParameters;
|
||||||
SupportsDepthClipControl = supportsDepthClipControl;
|
SupportsDepthClipControl = supportsDepthClipControl;
|
||||||
|
UniformBufferSetIndex = uniformBufferSetIndex;
|
||||||
|
StorageBufferSetIndex = storageBufferSetIndex;
|
||||||
|
TextureSetIndex = textureSetIndex;
|
||||||
|
ImageSetIndex = imageSetIndex;
|
||||||
|
ExtraSetBaseIndex = extraSetBaseIndex;
|
||||||
|
MaximumExtraSets = maximumExtraSets;
|
||||||
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
|
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
|
||||||
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
|
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
|
||||||
MaximumTexturesPerStage = maximumTexturesPerStage;
|
MaximumTexturesPerStage = maximumTexturesPerStage;
|
||||||
|
|
|
@ -711,5 +711,36 @@ namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
return format.IsUint() || format.IsSint();
|
return format.IsUint() || format.IsSint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the texture format is a float or sRGB color format.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Does not include normalized, compressed or depth formats.
|
||||||
|
/// Float and sRGB formats do not participate in logical operations.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="format">Texture format</param>
|
||||||
|
/// <returns>True if the format is a float or sRGB color format, false otherwise</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL
|
namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
public interface IImageArray
|
public interface IImageArray : IDisposable
|
||||||
{
|
{
|
||||||
void SetFormats(int index, Format[] imageFormats);
|
void SetFormats(int index, Format[] imageFormats);
|
||||||
void SetImages(int index, ITexture[] images);
|
void SetImages(int index, ITexture[] images);
|
||||||
|
|
|
@ -60,6 +60,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
|
|
||||||
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
|
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
|
||||||
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
|
void SetImageArray(ShaderStage stage, int binding, IImageArray array);
|
||||||
|
void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array);
|
||||||
|
|
||||||
void SetLineParameters(float width, bool smooth);
|
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 SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
|
||||||
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
|
void SetTextureArray(ShaderStage stage, int binding, ITextureArray array);
|
||||||
|
void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array);
|
||||||
|
|
||||||
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
|
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
|
||||||
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
|
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
|
||||||
|
|
|
@ -17,7 +17,6 @@ namespace Ryujinx.Graphics.GAL
|
||||||
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
||||||
|
|
||||||
BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default);
|
BufferHandle CreateBuffer(int size, BufferAccess access = BufferAccess.Default);
|
||||||
BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint);
|
|
||||||
BufferHandle CreateBuffer(nint pointer, int size);
|
BufferHandle CreateBuffer(nint pointer, int size);
|
||||||
BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers);
|
BufferHandle CreateBufferSparse(ReadOnlySpan<BufferRange> storageBuffers);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL
|
namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
public interface ITextureArray
|
public interface ITextureArray : IDisposable
|
||||||
{
|
{
|
||||||
void SetSamplers(int index, ISampler[] samplers);
|
void SetSamplers(int index, ISampler[] samplers);
|
||||||
void SetTextures(int index, ITexture[] textures);
|
void SetTextures(int index, ITexture[] textures);
|
||||||
|
|
|
@ -44,7 +44,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
}
|
}
|
||||||
|
|
||||||
Register<ActionCommand>(CommandType.Action);
|
Register<ActionCommand>(CommandType.Action);
|
||||||
Register<CreateBufferCommand>(CommandType.CreateBuffer);
|
|
||||||
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
|
Register<CreateBufferAccessCommand>(CommandType.CreateBufferAccess);
|
||||||
Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse);
|
Register<CreateBufferSparseCommand>(CommandType.CreateBufferSparse);
|
||||||
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
|
Register<CreateHostBufferCommand>(CommandType.CreateHostBuffer);
|
||||||
|
@ -67,6 +66,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
|
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
|
||||||
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
|
Register<CounterEventFlushCommand>(CommandType.CounterEventFlush);
|
||||||
|
|
||||||
|
Register<ImageArrayDisposeCommand>(CommandType.ImageArrayDispose);
|
||||||
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
|
Register<ImageArraySetFormatsCommand>(CommandType.ImageArraySetFormats);
|
||||||
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
|
Register<ImageArraySetImagesCommand>(CommandType.ImageArraySetImages);
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
|
Register<TextureSetDataSliceRegionCommand>(CommandType.TextureSetDataSliceRegion);
|
||||||
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
|
Register<TextureSetStorageCommand>(CommandType.TextureSetStorage);
|
||||||
|
|
||||||
|
Register<TextureArrayDisposeCommand>(CommandType.TextureArrayDispose);
|
||||||
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
|
Register<TextureArraySetSamplersCommand>(CommandType.TextureArraySetSamplers);
|
||||||
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);
|
Register<TextureArraySetTexturesCommand>(CommandType.TextureArraySetTextures);
|
||||||
|
|
||||||
|
@ -125,6 +126,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
|
Register<SetUniformBuffersCommand>(CommandType.SetUniformBuffers);
|
||||||
Register<SetImageCommand>(CommandType.SetImage);
|
Register<SetImageCommand>(CommandType.SetImage);
|
||||||
Register<SetImageArrayCommand>(CommandType.SetImageArray);
|
Register<SetImageArrayCommand>(CommandType.SetImageArray);
|
||||||
|
Register<SetImageArraySeparateCommand>(CommandType.SetImageArraySeparate);
|
||||||
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
|
Register<SetIndexBufferCommand>(CommandType.SetIndexBuffer);
|
||||||
Register<SetLineParametersCommand>(CommandType.SetLineParameters);
|
Register<SetLineParametersCommand>(CommandType.SetLineParameters);
|
||||||
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
|
Register<SetLogicOpStateCommand>(CommandType.SetLogicOpState);
|
||||||
|
@ -142,6 +144,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
Register<SetStencilTestCommand>(CommandType.SetStencilTest);
|
Register<SetStencilTestCommand>(CommandType.SetStencilTest);
|
||||||
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
|
Register<SetTextureAndSamplerCommand>(CommandType.SetTextureAndSampler);
|
||||||
Register<SetTextureArrayCommand>(CommandType.SetTextureArray);
|
Register<SetTextureArrayCommand>(CommandType.SetTextureArray);
|
||||||
|
Register<SetTextureArraySeparateCommand>(CommandType.SetTextureArraySeparate);
|
||||||
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
|
Register<SetUserClipDistanceCommand>(CommandType.SetUserClipDistance);
|
||||||
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
|
Register<SetVertexAttribsCommand>(CommandType.SetVertexAttribs);
|
||||||
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);
|
Register<SetVertexBuffersCommand>(CommandType.SetVertexBuffers);
|
||||||
|
|
|
@ -3,7 +3,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
enum CommandType : byte
|
enum CommandType : byte
|
||||||
{
|
{
|
||||||
Action,
|
Action,
|
||||||
CreateBuffer,
|
|
||||||
CreateBufferAccess,
|
CreateBufferAccess,
|
||||||
CreateBufferSparse,
|
CreateBufferSparse,
|
||||||
CreateHostBuffer,
|
CreateHostBuffer,
|
||||||
|
@ -27,6 +26,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
CounterEventDispose,
|
CounterEventDispose,
|
||||||
CounterEventFlush,
|
CounterEventFlush,
|
||||||
|
|
||||||
|
ImageArrayDispose,
|
||||||
ImageArraySetFormats,
|
ImageArraySetFormats,
|
||||||
ImageArraySetImages,
|
ImageArraySetImages,
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
TextureSetDataSliceRegion,
|
TextureSetDataSliceRegion,
|
||||||
TextureSetStorage,
|
TextureSetStorage,
|
||||||
|
|
||||||
|
TextureArrayDispose,
|
||||||
TextureArraySetSamplers,
|
TextureArraySetSamplers,
|
||||||
TextureArraySetTextures,
|
TextureArraySetTextures,
|
||||||
|
|
||||||
|
@ -85,6 +86,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
SetUniformBuffers,
|
SetUniformBuffers,
|
||||||
SetImage,
|
SetImage,
|
||||||
SetImageArray,
|
SetImageArray,
|
||||||
|
SetImageArraySeparate,
|
||||||
SetIndexBuffer,
|
SetIndexBuffer,
|
||||||
SetLineParameters,
|
SetLineParameters,
|
||||||
SetLogicOpState,
|
SetLogicOpState,
|
||||||
|
@ -102,6 +104,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
SetStencilTest,
|
SetStencilTest,
|
||||||
SetTextureAndSampler,
|
SetTextureAndSampler,
|
||||||
SetTextureArray,
|
SetTextureArray,
|
||||||
|
SetTextureArraySeparate,
|
||||||
SetUserClipDistance,
|
SetUserClipDistance,
|
||||||
SetVertexAttribs,
|
SetVertexAttribs,
|
||||||
SetVertexBuffers,
|
SetVertexBuffers,
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.ImageArray
|
||||||
|
{
|
||||||
|
struct ImageArrayDisposeCommand : IGALCommand, IGALCommand<ImageArrayDisposeCommand>
|
||||||
|
{
|
||||||
|
public readonly CommandType CommandType => CommandType.ImageArrayDispose;
|
||||||
|
private TableRef<ThreadedImageArray> _imageArray;
|
||||||
|
|
||||||
|
public void Set(TableRef<ThreadedImageArray> imageArray)
|
||||||
|
{
|
||||||
|
_imageArray = imageArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(ref ImageArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
|
{
|
||||||
|
command._imageArray.Get(threaded).Base.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Renderer
|
|
||||||
{
|
|
||||||
struct CreateBufferCommand : IGALCommand, IGALCommand<CreateBufferCommand>
|
|
||||||
{
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<SetImageArraySeparateCommand>
|
||||||
|
{
|
||||||
|
public readonly CommandType CommandType => CommandType.SetImageArraySeparate;
|
||||||
|
private ShaderStage _stage;
|
||||||
|
private int _setIndex;
|
||||||
|
private TableRef<IImageArray> _array;
|
||||||
|
|
||||||
|
public void Set(ShaderStage stage, int setIndex, TableRef<IImageArray> 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<ThreadedImageArray>(threaded)?.Base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<SetTextureArraySeparateCommand>
|
||||||
|
{
|
||||||
|
public readonly CommandType CommandType => CommandType.SetTextureArraySeparate;
|
||||||
|
private ShaderStage _stage;
|
||||||
|
private int _setIndex;
|
||||||
|
private TableRef<ITextureArray> _array;
|
||||||
|
|
||||||
|
public void Set(ShaderStage stage, int setIndex, TableRef<ITextureArray> 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<ThreadedTextureArray>(threaded)?.Base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.TextureArray
|
||||||
|
{
|
||||||
|
struct TextureArrayDisposeCommand : IGALCommand, IGALCommand<TextureArrayDisposeCommand>
|
||||||
|
{
|
||||||
|
public readonly CommandType CommandType => CommandType.TextureArrayDispose;
|
||||||
|
private TableRef<ThreadedTextureArray> _textureArray;
|
||||||
|
|
||||||
|
public void Set(TableRef<ThreadedTextureArray> textureArray)
|
||||||
|
{
|
||||||
|
_textureArray = textureArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(ref TextureArrayDisposeCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
|
{
|
||||||
|
command._textureArray.Get(threaded).Base.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||||
return new TableRef<T>(_renderer, reference);
|
return new TableRef<T>(_renderer, reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_renderer.New<ImageArrayDisposeCommand>().Set(Ref(this));
|
||||||
|
_renderer.QueueCommand();
|
||||||
|
}
|
||||||
|
|
||||||
public void SetFormats(int index, Format[] imageFormats)
|
public void SetFormats(int index, Format[] imageFormats)
|
||||||
{
|
{
|
||||||
_renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats));
|
_renderer.New<ImageArraySetFormatsCommand>().Set(Ref(this), index, Ref(imageFormats));
|
||||||
|
|
|
@ -22,6 +22,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||||
return new TableRef<T>(_renderer, reference);
|
return new TableRef<T>(_renderer, reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_renderer.New<TextureArrayDisposeCommand>().Set(Ref(this));
|
||||||
|
_renderer.QueueCommand();
|
||||||
|
}
|
||||||
|
|
||||||
public void SetSamplers(int index, ISampler[] samplers)
|
public void SetSamplers(int index, ISampler[] samplers)
|
||||||
{
|
{
|
||||||
_renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray()));
|
_renderer.New<TextureArraySetSamplersCommand>().Set(Ref(this), index, Ref(samplers.ToArray()));
|
||||||
|
|
|
@ -189,6 +189,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
|
||||||
|
{
|
||||||
|
_renderer.New<SetImageArraySeparateCommand>().Set(stage, setIndex, Ref(array));
|
||||||
|
_renderer.QueueCommand();
|
||||||
|
}
|
||||||
|
|
||||||
public void SetIndexBuffer(BufferRange buffer, IndexType type)
|
public void SetIndexBuffer(BufferRange buffer, IndexType type)
|
||||||
{
|
{
|
||||||
_renderer.New<SetIndexBufferCommand>().Set(buffer, type);
|
_renderer.New<SetIndexBufferCommand>().Set(buffer, type);
|
||||||
|
@ -297,6 +303,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
|
||||||
|
{
|
||||||
|
_renderer.New<SetTextureArraySeparateCommand>().Set(stage, setIndex, Ref(array));
|
||||||
|
_renderer.QueueCommand();
|
||||||
|
}
|
||||||
|
|
||||||
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
||||||
{
|
{
|
||||||
_renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers));
|
_renderer.New<SetTransformFeedbackBuffersCommand>().Set(_renderer.CopySpan(buffers));
|
||||||
|
|
|
@ -272,15 +272,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferHandle CreateBuffer(int size, BufferAccess access, BufferHandle storageHint)
|
|
||||||
{
|
|
||||||
BufferHandle handle = Buffers.CreateBufferHandle();
|
|
||||||
New<CreateBufferCommand>().Set(handle, size, access, storageHint);
|
|
||||||
QueueCommand();
|
|
||||||
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BufferHandle CreateBuffer(nint pointer, int size)
|
public BufferHandle CreateBuffer(nint pointer, int size)
|
||||||
{
|
{
|
||||||
BufferHandle handle = Buffers.CreateBufferHandle();
|
BufferHandle handle = Buffers.CreateBufferHandle();
|
||||||
|
|
29
src/Ryujinx.Graphics.GAL/SystemMemoryType.cs
Normal file
29
src/Ryujinx.Graphics.GAL/SystemMemoryType.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public enum SystemMemoryType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The backend manages the ownership of memory. This mode never supports host imported memory.
|
||||||
|
/// </summary>
|
||||||
|
BackendManaged,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Device memory has similar performance to host memory, usually because it's shared between CPU/GPU.
|
||||||
|
/// Use host memory whenever possible.
|
||||||
|
/// </summary>
|
||||||
|
UnifiedMemory,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
DedicatedMemory,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
DedicatedMemorySlowStorage
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -495,8 +496,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||||
|
|
||||||
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
|
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
|
||||||
|
|
||||||
MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize);
|
MultiRange indirectBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize, BufferStage.Indirect);
|
||||||
MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4);
|
MultiRange parameterBufferRange = bufferCache.TranslateAndCreateMultiBuffers(_processor.MemoryManager, parameterBufferGpuVa, 4, BufferStage.Indirect);
|
||||||
|
|
||||||
_processor.ThreedClass.DrawIndirect(
|
_processor.ThreedClass.DrawIndirect(
|
||||||
topology,
|
topology,
|
||||||
|
|
|
@ -438,7 +438,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
|
|
||||||
ReadOnlySpan<byte> dataBytes = MemoryMarshal.Cast<int, byte>(data);
|
ReadOnlySpan<byte> dataBytes = MemoryMarshal.Cast<int, byte>(data);
|
||||||
|
|
||||||
BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length);
|
BufferHandle buffer = _context.Renderer.CreateBuffer(dataBytes.Length, BufferAccess.DeviceMemory);
|
||||||
_context.Renderer.SetBufferData(buffer, 0, dataBytes);
|
_context.Renderer.SetBufferData(buffer, 0, dataBytes);
|
||||||
|
|
||||||
return new IndexBuffer(buffer, count, dataBytes.Length);
|
return new IndexBuffer(buffer, count, dataBytes.Length);
|
||||||
|
@ -529,7 +529,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
{
|
{
|
||||||
if (_dummyBuffer == BufferHandle.Null)
|
if (_dummyBuffer == BufferHandle.Null)
|
||||||
{
|
{
|
||||||
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize);
|
_dummyBuffer = _context.Renderer.CreateBuffer(DummyBufferSize, BufferAccess.DeviceMemory);
|
||||||
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
|
_context.Renderer.Pipeline.ClearBuffer(_dummyBuffer, 0, DummyBufferSize, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,7 +550,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
_context.Renderer.DeleteBuffer(_sequentialIndexBuffer);
|
_context.Renderer.DeleteBuffer(_sequentialIndexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
_sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint));
|
_sequentialIndexBuffer = _context.Renderer.CreateBuffer(count * sizeof(uint), BufferAccess.DeviceMemory);
|
||||||
_sequentialIndexBufferCount = count;
|
_sequentialIndexBufferCount = count;
|
||||||
|
|
||||||
Span<int> data = new int[count];
|
Span<int> data = new int[count];
|
||||||
|
@ -583,7 +583,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
_context.Renderer.DeleteBuffer(buffer.Handle);
|
_context.Renderer.DeleteBuffer(buffer.Handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Handle = _context.Renderer.CreateBuffer(newSize);
|
buffer.Handle = _context.Renderer.CreateBuffer(newSize, BufferAccess.DeviceMemory);
|
||||||
buffer.Size = newSize;
|
buffer.Size = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||||
using Ryujinx.Graphics.Gpu.Image;
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Gpu.Shader;
|
using Ryujinx.Graphics.Gpu.Shader;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
|
@ -370,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
{
|
{
|
||||||
var memoryManager = _channel.MemoryManager;
|
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);
|
ITexture bufferTexture = _vacContext.EnsureBufferTexture(index + 2, format);
|
||||||
bufferTexture.SetStorage(range);
|
bufferTexture.SetStorage(range);
|
||||||
|
@ -412,7 +413,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.ComputeDraw
|
||||||
var memoryManager = _channel.MemoryManager;
|
var memoryManager = _channel.MemoryManager;
|
||||||
|
|
||||||
ulong misalign = address & ((ulong)_context.Capabilities.TextureBufferOffsetAlignment - 1);
|
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;
|
misalignedOffset = (int)misalign >> shift;
|
||||||
|
|
||||||
SetIndexBufferTexture(reservations, range, format);
|
SetIndexBufferTexture(reservations, range, format);
|
||||||
|
|
|
@ -684,8 +684,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
|
|
||||||
if (hasCount)
|
if (hasCount)
|
||||||
{
|
{
|
||||||
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange);
|
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||||
var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange);
|
var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferRange, BufferStage.Indirect);
|
||||||
|
|
||||||
if (indexed)
|
if (indexed)
|
||||||
{
|
{
|
||||||
|
@ -698,7 +698,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange);
|
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferRange, BufferStage.Indirect);
|
||||||
|
|
||||||
if (indexed)
|
if (indexed)
|
||||||
{
|
{
|
||||||
|
|
|
@ -393,17 +393,18 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
|
|
||||||
if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
|
if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
|
||||||
{
|
{
|
||||||
Renderer.CreateSync(SyncNumber, strict);
|
|
||||||
|
|
||||||
foreach (var action in SyncActions)
|
foreach (var action in SyncActions)
|
||||||
{
|
{
|
||||||
action.SyncPreAction(syncpoint);
|
action.SyncPreAction(syncpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var action in SyncpointActions)
|
foreach (var action in SyncpointActions)
|
||||||
{
|
{
|
||||||
action.SyncPreAction(syncpoint);
|
action.SyncPreAction(syncpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Renderer.CreateSync(SyncNumber, strict);
|
||||||
|
|
||||||
SyncNumber++;
|
SyncNumber++;
|
||||||
|
|
||||||
SyncActions.RemoveAll(action => action.SyncAction(syncpoint));
|
SyncActions.RemoveAll(action => action.SyncAction(syncpoint));
|
||||||
|
|
|
@ -19,6 +19,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Format Format { get; }
|
public Format Format { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shader texture host set index.
|
||||||
|
/// </summary>
|
||||||
|
public int Set { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shader texture host binding point.
|
/// Shader texture host binding point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -54,15 +59,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">The shader sampler target type</param>
|
/// <param name="target">The shader sampler target type</param>
|
||||||
/// <param name="format">Format of the image as declared on the shader</param>
|
/// <param name="format">Format of the image as declared on the shader</param>
|
||||||
|
/// <param name="set">Shader texture host set index</param>
|
||||||
/// <param name="binding">The shader texture binding point</param>
|
/// <param name="binding">The shader texture binding point</param>
|
||||||
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
|
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
|
||||||
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
|
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
|
||||||
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
|
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
|
||||||
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
|
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
|
||||||
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;
|
Target = target;
|
||||||
Format = format;
|
Format = format;
|
||||||
|
Set = set;
|
||||||
Binding = binding;
|
Binding = binding;
|
||||||
ArrayLength = arrayLength;
|
ArrayLength = arrayLength;
|
||||||
CbufSlot = cbufSlot;
|
CbufSlot = cbufSlot;
|
||||||
|
@ -74,6 +81,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// Constructs the texture binding information structure.
|
/// Constructs the texture binding information structure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">The shader sampler target type</param>
|
/// <param name="target">The shader sampler target type</param>
|
||||||
|
/// <param name="set">Shader texture host set index</param>
|
||||||
/// <param name="binding">The shader texture binding point</param>
|
/// <param name="binding">The shader texture binding point</param>
|
||||||
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
|
/// <param name="arrayLength">For array of textures, this indicates the length of the array. A value of one indicates it is not an array</param>
|
||||||
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
|
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
|
||||||
|
@ -82,12 +90,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="isSamplerOnly">Indicates that the binding is for a sampler</param>
|
/// <param name="isSamplerOnly">Indicates that the binding is for a sampler</param>
|
||||||
public TextureBindingInfo(
|
public TextureBindingInfo(
|
||||||
Target target,
|
Target target,
|
||||||
|
int set,
|
||||||
int binding,
|
int binding,
|
||||||
int arrayLength,
|
int arrayLength,
|
||||||
int cbufSlot,
|
int cbufSlot,
|
||||||
int handle,
|
int handle,
|
||||||
TextureUsageFlags flags,
|
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;
|
IsSamplerOnly = isSamplerOnly;
|
||||||
}
|
}
|
||||||
|
|
|
@ -566,7 +566,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
int stageIndex,
|
int stageIndex,
|
||||||
int textureBufferIndex,
|
int textureBufferIndex,
|
||||||
SamplerIndex samplerIndex,
|
SamplerIndex samplerIndex,
|
||||||
TextureBindingInfo bindingInfo)
|
in TextureBindingInfo bindingInfo)
|
||||||
{
|
{
|
||||||
Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo);
|
Update(texturePool, samplerPool, stage, stageIndex, textureBufferIndex, isImage: false, samplerIndex, bindingInfo);
|
||||||
}
|
}
|
||||||
|
@ -579,7 +579,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="stageIndex">Shader stage index where the array is used</param>
|
/// <param name="stageIndex">Shader stage index where the array is used</param>
|
||||||
/// <param name="textureBufferIndex">Texture constant buffer index</param>
|
/// <param name="textureBufferIndex">Texture constant buffer index</param>
|
||||||
/// <param name="bindingInfo">Array binding information</param>
|
/// <param name="bindingInfo">Array binding information</param>
|
||||||
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);
|
Update(texturePool, null, stage, stageIndex, textureBufferIndex, isImage: true, SamplerIndex.ViaHeaderIndex, bindingInfo);
|
||||||
}
|
}
|
||||||
|
@ -603,7 +603,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
int textureBufferIndex,
|
int textureBufferIndex,
|
||||||
bool isImage,
|
bool isImage,
|
||||||
SamplerIndex samplerIndex,
|
SamplerIndex samplerIndex,
|
||||||
TextureBindingInfo bindingInfo)
|
in TextureBindingInfo bindingInfo)
|
||||||
{
|
{
|
||||||
if (IsDirectHandleType(bindingInfo.Handle))
|
if (IsDirectHandleType(bindingInfo.Handle))
|
||||||
{
|
{
|
||||||
|
@ -623,7 +623,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="stage">Shader stage where the array is used</param>
|
/// <param name="stage">Shader stage where the array is used</param>
|
||||||
/// <param name="isImage">Whether the array is a image or texture array</param>
|
/// <param name="isImage">Whether the array is a image or texture array</param>
|
||||||
/// <param name="bindingInfo">Array binding information</param>
|
/// <param name="bindingInfo">Array binding information</param>
|
||||||
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);
|
CacheEntry entry = GetOrAddEntry(texturePool, samplerPool, bindingInfo, isImage, out bool isNewEntry);
|
||||||
|
|
||||||
|
@ -638,11 +638,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
if (isImage)
|
if (isImage)
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
|
SetImageArray(stage, bindingInfo, entry.ImageArray);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
|
SetTextureArray(stage, bindingInfo, entry.TextureArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -708,11 +708,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
format = texture.Format;
|
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
|
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)
|
else if (isImage)
|
||||||
|
@ -737,14 +737,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
entry.ImageArray.SetFormats(0, formats);
|
entry.ImageArray.SetFormats(0, formats);
|
||||||
entry.ImageArray.SetImages(0, textures);
|
entry.ImageArray.SetImages(0, textures);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
|
SetImageArray(stage, bindingInfo, entry.ImageArray);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
entry.TextureArray.SetSamplers(0, samplers);
|
entry.TextureArray.SetSamplers(0, samplers);
|
||||||
entry.TextureArray.SetTextures(0, textures);
|
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,
|
int textureBufferIndex,
|
||||||
bool isImage,
|
bool isImage,
|
||||||
SamplerIndex samplerIndex,
|
SamplerIndex samplerIndex,
|
||||||
TextureBindingInfo bindingInfo)
|
in TextureBindingInfo bindingInfo)
|
||||||
{
|
{
|
||||||
(textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
|
(textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, textureBufferIndex);
|
||||||
|
|
||||||
|
@ -800,11 +800,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
if (isImage)
|
if (isImage)
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
|
SetImageArray(stage, bindingInfo, entry.ImageArray);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
|
SetTextureArray(stage, bindingInfo, entry.TextureArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -829,11 +829,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
if (isImage)
|
if (isImage)
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
|
SetImageArray(stage, bindingInfo, entry.ImageArray);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
|
SetTextureArray(stage, bindingInfo, entry.TextureArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -921,11 +921,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
format = texture.Format;
|
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
|
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)
|
else if (isImage)
|
||||||
|
@ -950,14 +950,50 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
entry.ImageArray.SetFormats(0, formats);
|
entry.ImageArray.SetFormats(0, formats);
|
||||||
entry.ImageArray.SetImages(0, textures);
|
entry.ImageArray.SetImages(0, textures);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetImageArray(stage, bindingInfo.Binding, entry.ImageArray);
|
SetImageArray(stage, bindingInfo, entry.ImageArray);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
entry.TextureArray.SetSamplers(0, samplers);
|
entry.TextureArray.SetSamplers(0, samplers);
|
||||||
entry.TextureArray.SetTextures(0, textures);
|
entry.TextureArray.SetTextures(0, textures);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetTextureArray(stage, bindingInfo.Binding, entry.TextureArray);
|
SetTextureArray(stage, bindingInfo, entry.TextureArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a texture array binding on the host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stage">Shader stage where the array is used</param>
|
||||||
|
/// <param name="bindingInfo">Array binding information</param>
|
||||||
|
/// <param name="array">Texture array</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a image array binding on the host.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stage">Shader stage where the array is used</param>
|
||||||
|
/// <param name="bindingInfo">Array binding information</param>
|
||||||
|
/// <param name="array">Image array</param>
|
||||||
|
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(
|
private CacheEntry GetOrAddEntry(
|
||||||
TexturePool texturePool,
|
TexturePool texturePool,
|
||||||
SamplerPool samplerPool,
|
SamplerPool samplerPool,
|
||||||
TextureBindingInfo bindingInfo,
|
in TextureBindingInfo bindingInfo,
|
||||||
bool isImage,
|
bool isImage,
|
||||||
out bool isNew)
|
out bool isNew)
|
||||||
{
|
{
|
||||||
|
@ -1015,7 +1051,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
private CacheEntryFromBuffer GetOrAddEntry(
|
private CacheEntryFromBuffer GetOrAddEntry(
|
||||||
TexturePool texturePool,
|
TexturePool texturePool,
|
||||||
SamplerPool samplerPool,
|
SamplerPool samplerPool,
|
||||||
TextureBindingInfo bindingInfo,
|
in TextureBindingInfo bindingInfo,
|
||||||
bool isImage,
|
bool isImage,
|
||||||
ref BufferBounds textureBufferBounds,
|
ref BufferBounds textureBufferBounds,
|
||||||
out bool isNew)
|
out bool isNew)
|
||||||
|
@ -1077,6 +1113,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
nextNode = nextNode.Next;
|
nextNode = nextNode.Next;
|
||||||
_cacheFromBuffer.Remove(toRemove.Value.Key);
|
_cacheFromBuffer.Remove(toRemove.Value.Key);
|
||||||
_lruCache.Remove(toRemove);
|
_lruCache.Remove(toRemove);
|
||||||
|
|
||||||
|
if (toRemove.Value.Key.IsImage)
|
||||||
|
{
|
||||||
|
toRemove.Value.ImageArray.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toRemove.Value.TextureArray.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1088,11 +1133,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
List<CacheEntryFromPoolKey> keysToRemove = null;
|
List<CacheEntryFromPoolKey> keysToRemove = null;
|
||||||
|
|
||||||
foreach (CacheEntryFromPoolKey key in _cacheFromPool.Keys)
|
foreach ((CacheEntryFromPoolKey key, CacheEntry entry) in _cacheFromPool)
|
||||||
{
|
{
|
||||||
if (key.MatchesPool(pool))
|
if (key.MatchesPool(pool))
|
||||||
{
|
{
|
||||||
(keysToRemove ??= new()).Add(key);
|
(keysToRemove ??= new()).Add(key);
|
||||||
|
|
||||||
|
if (key.IsImage)
|
||||||
|
{
|
||||||
|
entry.ImageArray.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entry.TextureArray.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -645,7 +645,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.FlushPersistent);
|
_flushBuffer = _context.Renderer.CreateBuffer((int)Storage.Size, BufferAccess.HostMemory);
|
||||||
_flushBufferImported = false;
|
_flushBufferImported = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
|
delegate void BufferFlushAction(ulong address, ulong size, ulong syncNumber);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
|
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -23,7 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Host buffer handle.
|
/// Host buffer handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public BufferHandle Handle { get; }
|
public BufferHandle Handle { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start address of the buffer in guest memory.
|
/// Start address of the buffer in guest memory.
|
||||||
|
@ -60,6 +62,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private BufferModifiedRangeList _modifiedRanges = null;
|
private BufferModifiedRangeList _modifiedRanges = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private BufferPreFlush _preFlush = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Usage tracking state that determines what type of backing the buffer should use.
|
||||||
|
/// </summary>
|
||||||
|
public BufferBackingState BackingState;
|
||||||
|
|
||||||
private readonly MultiRegionHandle _memoryTrackingGranular;
|
private readonly MultiRegionHandle _memoryTrackingGranular;
|
||||||
private readonly RegionHandle _memoryTracking;
|
private readonly RegionHandle _memoryTracking;
|
||||||
|
|
||||||
|
@ -87,6 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="physicalMemory">Physical memory where the buffer is mapped</param>
|
/// <param name="physicalMemory">Physical memory where the buffer is mapped</param>
|
||||||
/// <param name="address">Start address of the buffer</param>
|
/// <param name="address">Start address of the buffer</param>
|
||||||
/// <param name="size">Size of the buffer in bytes</param>
|
/// <param name="size">Size of the buffer in bytes</param>
|
||||||
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
|
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
|
||||||
/// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param>
|
/// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param>
|
||||||
public Buffer(
|
public Buffer(
|
||||||
|
@ -94,6 +108,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
PhysicalMemory physicalMemory,
|
PhysicalMemory physicalMemory,
|
||||||
ulong address,
|
ulong address,
|
||||||
ulong size,
|
ulong size,
|
||||||
|
BufferStage stage,
|
||||||
bool sparseCompatible,
|
bool sparseCompatible,
|
||||||
IEnumerable<Buffer> baseBuffers = null)
|
IEnumerable<Buffer> baseBuffers = null)
|
||||||
{
|
{
|
||||||
|
@ -103,9 +118,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
Size = size;
|
Size = size;
|
||||||
SparseCompatible = sparseCompatible;
|
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;
|
_useGranular = size > GranularBufferThreshold;
|
||||||
|
|
||||||
|
@ -161,6 +178,29 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
_virtualDependenciesLock = new ReaderWriterLockSlim();
|
_virtualDependenciesLock = new ReaderWriterLockSlim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recreates the backing buffer based on the desired access type
|
||||||
|
/// reported by the backing state struct.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a sub-range from the buffer, from a start address til a page boundary after the given size.
|
/// Gets a sub-range from the buffer, from a start address til a page boundary after the given size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -246,6 +286,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
BackingState.RecordSet();
|
||||||
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
|
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
|
||||||
CopyToDependantVirtualBuffers();
|
CopyToDependantVirtualBuffers();
|
||||||
}
|
}
|
||||||
|
@ -283,15 +324,35 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
_modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush);
|
_modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stage">Buffer stage that can change backing type</param>
|
||||||
|
private void TryQueueBackingChange(BufferStage stage)
|
||||||
|
{
|
||||||
|
if (BackingState.ShouldChangeBacking(stage))
|
||||||
|
{
|
||||||
|
if (!_syncActionRegistered)
|
||||||
|
{
|
||||||
|
_context.RegisterSyncAction(this);
|
||||||
|
_syncActionRegistered = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signal that the given region of the buffer has been modified.
|
/// Signal that the given region of the buffer has been modified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">The start address of the modified region</param>
|
/// <param name="address">The start address of the modified region</param>
|
||||||
/// <param name="size">The size of the modified region</param>
|
/// <param name="size">The size of the modified region</param>
|
||||||
public void SignalModified(ulong address, ulong size)
|
/// <param name="stage">Buffer stage that triggered the modification</param>
|
||||||
|
public void SignalModified(ulong address, ulong size, BufferStage stage)
|
||||||
{
|
{
|
||||||
EnsureRangeList();
|
EnsureRangeList();
|
||||||
|
|
||||||
|
TryQueueBackingChange(stage);
|
||||||
|
|
||||||
_modifiedRanges.SignalModified(address, size);
|
_modifiedRanges.SignalModified(address, size);
|
||||||
|
|
||||||
if (!_syncActionRegistered)
|
if (!_syncActionRegistered)
|
||||||
|
@ -311,6 +372,37 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
_modifiedRanges?.Clear(address, size);
|
_modifiedRanges?.Clear(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Action to be performed immediately before sync is created.
|
||||||
|
/// This will copy any buffer ranges designated for pre-flushing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="syncpoint">True if the action is a guest syncpoint</param>
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Action to be performed when a syncpoint is reached after modification.
|
/// 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.
|
/// 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
|
||||||
/// <param name="mSize">Size of the modified region</param>
|
/// <param name="mSize">Size of the modified region</param>
|
||||||
private void LoadRegion(ulong mAddress, ulong mSize)
|
private void LoadRegion(ulong mAddress, ulong mSize)
|
||||||
{
|
{
|
||||||
|
BackingState.RecordSet();
|
||||||
|
|
||||||
int offset = (int)(mAddress - Address);
|
int offset = (int)(mAddress - Address);
|
||||||
|
|
||||||
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
|
_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.
|
/// Flushes a range of the buffer.
|
||||||
/// This writes the range data back into guest memory.
|
/// This writes the range data back into guest memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="handle">Buffer handle to flush data from</param>
|
||||||
/// <param name="address">Start address of the range</param>
|
/// <param name="address">Start address of the range</param>
|
||||||
/// <param name="size">Size in bytes of the range</param>
|
/// <param name="size">Size in bytes of the range</param>
|
||||||
public void Flush(ulong address, ulong size)
|
private void FlushImpl(BufferHandle handle, ulong address, ulong size)
|
||||||
{
|
{
|
||||||
int offset = (int)(address - Address);
|
int offset = (int)(address - Address);
|
||||||
|
|
||||||
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(handle, offset, (int)size);
|
||||||
|
|
||||||
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
||||||
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
|
_physicalMemory.WriteUntracked(address, CopyFromDependantVirtualBuffers(data.Get(), address, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes a range of the buffer.
|
||||||
|
/// This writes the range data back into guest memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Start address of the range</param>
|
||||||
|
/// <param name="size">Size in bytes of the range</param>
|
||||||
|
private void FlushImpl(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
FlushImpl(Handle, address, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flushes a range of the buffer from the most optimal source.
|
||||||
|
/// This writes the range data back into guest memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Start address of the range</param>
|
||||||
|
/// <param name="size">Size in bytes of the range</param>
|
||||||
|
/// <param name="syncNumber">Sync number waited for before flushing the data</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An action that flushes data from the specified range, using the buffer handle at the time the method is generated</returns>
|
||||||
|
public Action GetSnapshotDisposeAction()
|
||||||
|
{
|
||||||
|
BufferHandle handle = Handle;
|
||||||
|
BufferPreFlush preFlush = _preFlush;
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
_context.Renderer.DeleteBuffer(handle);
|
||||||
|
preFlush?.Dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An action that flushes data from the specified range, using the buffer handle at the time the method is generated</returns>
|
||||||
|
public BufferFlushAction GetSnapshotFlushAction()
|
||||||
|
{
|
||||||
|
BufferHandle handle = Handle;
|
||||||
|
|
||||||
|
return (ulong address, ulong size, ulong _) =>
|
||||||
|
{
|
||||||
|
FlushImpl(handle, address, size);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Align a given address and size region to page boundaries.
|
/// Align a given address and size region to page boundaries.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -857,6 +1017,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
_modifiedRanges?.Clear();
|
_modifiedRanges?.Clear();
|
||||||
|
|
||||||
_context.Renderer.DeleteBuffer(Handle);
|
_context.Renderer.DeleteBuffer(Handle);
|
||||||
|
_preFlush?.Dispose();
|
||||||
|
_preFlush = null;
|
||||||
|
|
||||||
UnmappedSequence++;
|
UnmappedSequence++;
|
||||||
}
|
}
|
||||||
|
|
294
src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
Normal file
294
src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type of backing memory.
|
||||||
|
/// In ascending order of priority when merging multiple buffer backing states.
|
||||||
|
/// </summary>
|
||||||
|
internal enum BufferBackingType
|
||||||
|
{
|
||||||
|
HostMemory,
|
||||||
|
DeviceMemory,
|
||||||
|
DeviceMemoryWithFlush
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the buffer backing state for a given parent buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">GPU context</param>
|
||||||
|
/// <param name="parent">Parent buffer</param>
|
||||||
|
/// <param name="stage">Initial buffer stage</param>
|
||||||
|
/// <param name="baseBuffers">Buffers to inherit state from</param>
|
||||||
|
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, IEnumerable<Buffer> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Combine buffer backing types, selecting the one with highest priority.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">First buffer backing type</param>
|
||||||
|
/// <param name="right">Second buffer backing type</param>
|
||||||
|
/// <returns>Combined buffer backing type</returns>
|
||||||
|
private static BufferBackingType CombineTypes(BufferBackingType left, BufferBackingType right)
|
||||||
|
{
|
||||||
|
return (BufferBackingType)Math.Max((int)left, (int)right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Combine the state from the given buffer backing state with this one,
|
||||||
|
/// so that the state isn't lost when migrating buffers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="oldState">Buffer state to combine into this state</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the buffer access for the desired backing type, and record that type as now being active.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">Parent buffer</param>
|
||||||
|
/// <returns>Buffer access</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Record when data has been uploaded to the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public void RecordSet()
|
||||||
|
{
|
||||||
|
_setCount++;
|
||||||
|
|
||||||
|
ConsiderUseCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Record when data has been flushed from the buffer.
|
||||||
|
/// </summary>
|
||||||
|
public void RecordFlush()
|
||||||
|
{
|
||||||
|
if (_lastFlushWrite != _writeCount)
|
||||||
|
{
|
||||||
|
// If it's on the same page as the last flush, ignore it.
|
||||||
|
_lastFlushWrite = _writeCount;
|
||||||
|
_flushCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if the buffer backing should be changed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the desired backing type is different from the current type</returns>
|
||||||
|
public readonly bool ShouldChangeBacking()
|
||||||
|
{
|
||||||
|
return _desiredType != _activeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if the buffer backing should be changed, considering a new use with the given buffer stage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stage">Buffer stage for the use</param>
|
||||||
|
/// <returns>True if the desired backing type is different from the current type</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,8 +107,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||||
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
||||||
/// <param name="size">Size in bytes of the buffer</param>
|
/// <param name="size">Size in bytes of the buffer</param>
|
||||||
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
/// <returns>Contiguous physical range of the buffer, after address translation</returns>
|
/// <returns>Contiguous physical range of the buffer, after address translation</returns>
|
||||||
public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size)
|
public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
|
||||||
{
|
{
|
||||||
if (gpuVa == 0)
|
if (gpuVa == 0)
|
||||||
{
|
{
|
||||||
|
@ -119,7 +120,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (address != MemoryManager.PteUnmapped)
|
if (address != MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
CreateBuffer(address, size);
|
CreateBuffer(address, size, stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MultiRange(address, size);
|
return new MultiRange(address, size);
|
||||||
|
@ -132,8 +133,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||||
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
||||||
/// <param name="size">Size in bytes of the buffer</param>
|
/// <param name="size">Size in bytes of the buffer</param>
|
||||||
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
/// <returns>Physical ranges of the buffer, after address translation</returns>
|
/// <returns>Physical ranges of the buffer, after address translation</returns>
|
||||||
public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size)
|
public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
|
||||||
{
|
{
|
||||||
if (gpuVa == 0)
|
if (gpuVa == 0)
|
||||||
{
|
{
|
||||||
|
@ -149,7 +151,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateBuffer(range);
|
CreateBuffer(range, stage);
|
||||||
|
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
@ -161,8 +163,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
/// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
|
||||||
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
||||||
/// <param name="size">Size in bytes of the buffer</param>
|
/// <param name="size">Size in bytes of the buffer</param>
|
||||||
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
/// <returns>Physical ranges of the buffer, after address translation</returns>
|
/// <returns>Physical ranges of the buffer, after address translation</returns>
|
||||||
public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size)
|
public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
|
||||||
{
|
{
|
||||||
if (gpuVa == 0)
|
if (gpuVa == 0)
|
||||||
{
|
{
|
||||||
|
@ -186,11 +189,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
if (range.Count > 1)
|
if (range.Count > 1)
|
||||||
{
|
{
|
||||||
CreateBuffer(subRange.Address, subRange.Size, SparseBufferAlignmentSize);
|
CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
|
||||||
}
|
}
|
||||||
else
|
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.
|
/// This can be used to ensure the existance of a buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="range">Physical ranges of memory where the buffer data is located</param>
|
/// <param name="range">Physical ranges of memory where the buffer data is located</param>
|
||||||
public void CreateBuffer(MultiRange range)
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
|
public void CreateBuffer(MultiRange range, BufferStage stage)
|
||||||
{
|
{
|
||||||
if (range.Count > 1)
|
if (range.Count > 1)
|
||||||
{
|
{
|
||||||
CreateMultiRangeBuffer(range);
|
CreateMultiRangeBuffer(range, stage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -215,7 +219,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
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
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the buffer in memory</param>
|
/// <param name="address">Address of the buffer in memory</param>
|
||||||
/// <param name="size">Size of the buffer in bytes</param>
|
/// <param name="size">Size of the buffer in bytes</param>
|
||||||
public void CreateBuffer(ulong address, ulong size)
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
|
public void CreateBuffer(ulong address, ulong size, BufferStage stage)
|
||||||
{
|
{
|
||||||
ulong endAddress = address + size;
|
ulong endAddress = address + size;
|
||||||
|
|
||||||
|
@ -239,7 +244,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
alignedEndAddress += BufferAlignmentSize;
|
alignedEndAddress += BufferAlignmentSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress);
|
CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -248,8 +253,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the buffer in memory</param>
|
/// <param name="address">Address of the buffer in memory</param>
|
||||||
/// <param name="size">Size of the buffer in bytes</param>
|
/// <param name="size">Size of the buffer in bytes</param>
|
||||||
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
/// <param name="alignment">Alignment of the start address of the buffer in bytes</param>
|
/// <param name="alignment">Alignment of the start address of the buffer in bytes</param>
|
||||||
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 alignmentMask = alignment - 1;
|
||||||
ulong pageAlignmentMask = BufferAlignmentMask;
|
ulong pageAlignmentMask = BufferAlignmentMask;
|
||||||
|
@ -264,7 +270,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
alignedEndAddress += pageAlignmentMask;
|
alignedEndAddress += pageAlignmentMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, alignment);
|
CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -272,7 +278,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// if it does not exist yet.
|
/// if it does not exist yet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="range">Physical ranges of memory</param>
|
/// <param name="range">Physical ranges of memory</param>
|
||||||
private void CreateMultiRangeBuffer(MultiRange range)
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
|
private void CreateMultiRangeBuffer(MultiRange range, BufferStage stage)
|
||||||
{
|
{
|
||||||
// Ensure all non-contiguous buffer we might use are sparse aligned.
|
// Ensure all non-contiguous buffer we might use are sparse aligned.
|
||||||
for (int i = 0; i < range.Count; i++)
|
for (int i = 0; i < range.Count; i++)
|
||||||
|
@ -281,7 +288,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (subRange.Address != MemoryManager.PteUnmapped)
|
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.EndGpuAddress < gpuVa + size ||
|
||||||
result.UnmappedSequence != result.Buffer.UnmappedSequence)
|
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;
|
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;
|
_dirtyCache[gpuVa] = result;
|
||||||
}
|
}
|
||||||
|
@ -466,9 +473,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
result.EndGpuAddress < alignedEndGpuVa ||
|
result.EndGpuAddress < alignedEndGpuVa ||
|
||||||
result.UnmappedSequence != result.Buffer.UnmappedSequence)
|
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;
|
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;
|
_modifiedCache[alignedGpuVa] = result;
|
||||||
}
|
}
|
||||||
|
@ -485,7 +492,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the buffer in guest memory</param>
|
/// <param name="address">Address of the buffer in guest memory</param>
|
||||||
/// <param name="size">Size in bytes of the buffer</param>
|
/// <param name="size">Size in bytes of the buffer</param>
|
||||||
private void CreateBufferAligned(ulong address, ulong size)
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
|
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
|
||||||
{
|
{
|
||||||
Buffer[] overlaps = _bufferOverlaps;
|
Buffer[] overlaps = _bufferOverlaps;
|
||||||
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
|
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||||
|
@ -546,13 +554,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
ulong newSize = endAddress - address;
|
ulong newSize = endAddress - address;
|
||||||
|
|
||||||
CreateBufferAligned(address, newSize, anySparseCompatible, overlaps, overlapsCount);
|
CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps, overlapsCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No overlap, just create a new buffer.
|
// 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)
|
lock (_buffers)
|
||||||
{
|
{
|
||||||
|
@ -570,8 +578,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the buffer in guest memory</param>
|
/// <param name="address">Address of the buffer in guest memory</param>
|
||||||
/// <param name="size">Size in bytes of the buffer</param>
|
/// <param name="size">Size in bytes of the buffer</param>
|
||||||
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
/// <param name="alignment">Alignment of the start address of the buffer</param>
|
/// <param name="alignment">Alignment of the start address of the buffer</param>
|
||||||
private void CreateBufferAligned(ulong address, ulong size, ulong alignment)
|
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
|
||||||
{
|
{
|
||||||
Buffer[] overlaps = _bufferOverlaps;
|
Buffer[] overlaps = _bufferOverlaps;
|
||||||
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
|
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
|
||||||
|
@ -624,13 +633,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
ulong newSize = endAddress - address;
|
ulong newSize = endAddress - address;
|
||||||
|
|
||||||
CreateBufferAligned(address, newSize, sparseAligned, overlaps, overlapsCount);
|
CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps, overlapsCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No overlap, just create a new buffer.
|
// 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)
|
lock (_buffers)
|
||||||
{
|
{
|
||||||
|
@ -648,12 +657,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Address of the buffer in guest memory</param>
|
/// <param name="address">Address of the buffer in guest memory</param>
|
||||||
/// <param name="size">Size in bytes of the buffer</param>
|
/// <param name="size">Size in bytes of the buffer</param>
|
||||||
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
|
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
|
||||||
/// <param name="overlaps">Buffers overlapping the range</param>
|
/// <param name="overlaps">Buffers overlapping the range</param>
|
||||||
/// <param name="overlapsCount">Total of overlaps</param>
|
/// <param name="overlapsCount">Total of overlaps</param>
|
||||||
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)
|
lock (_buffers)
|
||||||
{
|
{
|
||||||
|
@ -704,7 +714,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
for (int index = 0; index < overlapCount; index++)
|
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
|
||||||
/// <param name="size">Size in bytes of the copy</param>
|
/// <param name="size">Size in bytes of the copy</param>
|
||||||
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
|
||||||
{
|
{
|
||||||
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size);
|
MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
|
||||||
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size);
|
MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
|
||||||
|
|
||||||
if (srcRange.Count == 1 && dstRange.Count == 1)
|
if (srcRange.Count == 1 && dstRange.Count == 1)
|
||||||
{
|
{
|
||||||
|
@ -788,8 +798,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="size">Size in bytes of the copy</param>
|
/// <param name="size">Size in bytes of the copy</param>
|
||||||
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
|
private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
|
||||||
{
|
{
|
||||||
Buffer srcBuffer = GetBuffer(srcAddress, size);
|
Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
|
||||||
Buffer dstBuffer = GetBuffer(dstAddress, size);
|
Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy);
|
||||||
|
|
||||||
int srcOffset = (int)(srcAddress - srcBuffer.Address);
|
int srcOffset = (int)(srcAddress - srcBuffer.Address);
|
||||||
int dstOffset = (int)(dstAddress - dstBuffer.Address);
|
int dstOffset = (int)(dstAddress - dstBuffer.Address);
|
||||||
|
@ -803,7 +813,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (srcBuffer.IsModified(srcAddress, size))
|
if (srcBuffer.IsModified(srcAddress, size))
|
||||||
{
|
{
|
||||||
dstBuffer.SignalModified(dstAddress, size);
|
dstBuffer.SignalModified(dstAddress, size, BufferStage.Copy);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -828,12 +838,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="value">Value to be written into the buffer</param>
|
/// <param name="value">Value to be written into the buffer</param>
|
||||||
public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
|
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++)
|
for (int index = 0; index < range.Count; index++)
|
||||||
{
|
{
|
||||||
MemoryRange subRange = range.GetSubRange(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);
|
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.
|
/// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="range">Physical regions of memory where the buffer is mapped</param>
|
/// <param name="range">Physical regions of memory where the buffer is mapped</param>
|
||||||
|
/// <param name="stage">Buffer stage that triggered the access</param>
|
||||||
/// <param name="write">Whether the buffer will be written to by this use</param>
|
/// <param name="write">Whether the buffer will be written to by this use</param>
|
||||||
/// <returns>The buffer sub-range starting at the given memory address</returns>
|
/// <returns>The buffer sub-range starting at the given memory address</returns>
|
||||||
public BufferRange GetBufferRangeAligned(MultiRange range, bool write = false)
|
public BufferRange GetBufferRangeAligned(MultiRange range, BufferStage stage, bool write = false)
|
||||||
{
|
{
|
||||||
if (range.Count > 1)
|
if (range.Count > 1)
|
||||||
{
|
{
|
||||||
return GetBuffer(range, write).GetRange(range);
|
return GetBuffer(range, stage, write).GetRange(range);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MemoryRange subRange = range.GetSubRange(0);
|
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.
|
/// Gets a buffer sub-range for a given memory range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="range">Physical regions of memory where the buffer is mapped</param>
|
/// <param name="range">Physical regions of memory where the buffer is mapped</param>
|
||||||
|
/// <param name="stage">Buffer stage that triggered the access</param>
|
||||||
/// <param name="write">Whether the buffer will be written to by this use</param>
|
/// <param name="write">Whether the buffer will be written to by this use</param>
|
||||||
/// <returns>The buffer sub-range for the given range</returns>
|
/// <returns>The buffer sub-range for the given range</returns>
|
||||||
public BufferRange GetBufferRange(MultiRange range, bool write = false)
|
public BufferRange GetBufferRange(MultiRange range, BufferStage stage, bool write = false)
|
||||||
{
|
{
|
||||||
if (range.Count > 1)
|
if (range.Count > 1)
|
||||||
{
|
{
|
||||||
return GetBuffer(range, write).GetRange(range);
|
return GetBuffer(range, stage, write).GetRange(range);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MemoryRange subRange = range.GetSubRange(0);
|
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.
|
/// A buffer overlapping with the specified range is assumed to already exist on the cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="range">Physical regions of memory where the buffer is mapped</param>
|
/// <param name="range">Physical regions of memory where the buffer is mapped</param>
|
||||||
|
/// <param name="stage">Buffer stage that triggered the access</param>
|
||||||
/// <param name="write">Whether the buffer will be written to by this use</param>
|
/// <param name="write">Whether the buffer will be written to by this use</param>
|
||||||
/// <returns>The buffer where the range is fully contained</returns>
|
/// <returns>The buffer where the range is fully contained</returns>
|
||||||
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++)
|
for (int i = 0; i < range.Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -902,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (write)
|
if (write)
|
||||||
{
|
{
|
||||||
subBuffer.SignalModified(subRange.Address, subRange.Size);
|
subBuffer.SignalModified(subRange.Address, subRange.Size, stage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -935,9 +948,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Start address of the memory range</param>
|
/// <param name="address">Start address of the memory range</param>
|
||||||
/// <param name="size">Size in bytes of the memory range</param>
|
/// <param name="size">Size in bytes of the memory range</param>
|
||||||
|
/// <param name="stage">Buffer stage that triggered the access</param>
|
||||||
/// <param name="write">Whether the buffer will be written to by this use</param>
|
/// <param name="write">Whether the buffer will be written to by this use</param>
|
||||||
/// <returns>The buffer where the range is fully contained</returns>
|
/// <returns>The buffer where the range is fully contained</returns>
|
||||||
private Buffer GetBuffer(ulong address, ulong size, bool write = false)
|
private Buffer GetBuffer(ulong address, ulong size, BufferStage stage, bool write = false)
|
||||||
{
|
{
|
||||||
Buffer buffer;
|
Buffer buffer;
|
||||||
|
|
||||||
|
@ -950,7 +964,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (write)
|
if (write)
|
||||||
{
|
{
|
||||||
buffer.SignalModified(address, size);
|
buffer.SignalModified(address, size, stage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1004,6 +1018,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signal that the given buffer's handle has changed,
|
||||||
|
/// forcing rebind and any overlapping multi-range buffers to be recreated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The buffer that has changed handle</param>
|
||||||
|
public void BufferBackingChanged(Buffer buffer)
|
||||||
|
{
|
||||||
|
NotifyBuffersModified?.Invoke();
|
||||||
|
|
||||||
|
RecreateMultiRangeBuffers(buffer.Address, buffer.Size);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prune any invalid entries from a quick access dictionary.
|
/// Prune any invalid entries from a quick access dictionary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="type">Type of each index buffer element</param>
|
/// <param name="type">Type of each index buffer element</param>
|
||||||
public void SetIndexBuffer(ulong gpuVa, ulong size, IndexType type)
|
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.Range = range;
|
||||||
_indexBuffer.Type = type;
|
_indexBuffer.Type = type;
|
||||||
|
@ -186,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param>
|
/// <param name="divisor">Vertex divisor of the buffer, for instanced draws</param>
|
||||||
public void SetVertexBuffer(int index, ulong gpuVa, ulong size, int stride, int divisor)
|
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].Range = range;
|
||||||
_vertexBuffers[index].Stride = stride;
|
_vertexBuffers[index].Stride = stride;
|
||||||
|
@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="size">Size in bytes of the transform feedback buffer</param>
|
/// <param name="size">Size in bytes of the transform feedback buffer</param>
|
||||||
public void SetTransformFeedbackBuffer(int index, ulong gpuVa, ulong size)
|
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);
|
_transformFeedbackBuffers[index] = new BufferBounds(range);
|
||||||
_transformFeedbackBuffersDirty = true;
|
_transformFeedbackBuffersDirty = true;
|
||||||
|
@ -260,7 +260,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
gpuVa = BitUtils.AlignDown<ulong>(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);
|
_cpStorageBuffers.SetBounds(index, range, flags);
|
||||||
}
|
}
|
||||||
|
@ -284,7 +284,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
gpuVa = BitUtils.AlignDown<ulong>(gpuVa, (ulong)_context.Capabilities.StorageBufferOffsetAlignment);
|
gpuVa = BitUtils.AlignDown<ulong>(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))
|
if (!buffers.Buffers[index].Range.Equals(range))
|
||||||
{
|
{
|
||||||
|
@ -303,7 +303,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="size">Size in bytes of the storage buffer</param>
|
/// <param name="size">Size in bytes of the storage buffer</param>
|
||||||
public void SetComputeUniformBuffer(int index, ulong gpuVa, ulong size)
|
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);
|
_cpUniformBuffers.SetBounds(index, range);
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="size">Size in bytes of the storage buffer</param>
|
/// <param name="size">Size in bytes of the storage buffer</param>
|
||||||
public void SetGraphicsUniformBuffer(int stage, int index, ulong gpuVa, ulong size)
|
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);
|
_gpUniformBuffers[stage].SetBounds(index, range);
|
||||||
_gpUniformBuffersDirty = true;
|
_gpUniformBuffersDirty = true;
|
||||||
|
@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
foreach (var binding in _bufferTextures)
|
foreach (var binding in _bufferTextures)
|
||||||
{
|
{
|
||||||
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
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);
|
binding.Texture.SetStorage(range);
|
||||||
|
|
||||||
// The texture must be rebound to use the new storage if it was updated.
|
// 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)
|
foreach (var binding in _bufferTextureArrays)
|
||||||
{
|
{
|
||||||
var range = bufferCache.GetBufferRange(binding.Range);
|
var range = bufferCache.GetBufferRange(binding.Range, BufferStage.None);
|
||||||
binding.Texture.SetStorage(range);
|
binding.Texture.SetStorage(range);
|
||||||
|
|
||||||
textureArray[0] = binding.Texture;
|
textureArray[0] = binding.Texture;
|
||||||
|
@ -536,7 +536,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
foreach (var binding in _bufferImageArrays)
|
foreach (var binding in _bufferImageArrays)
|
||||||
{
|
{
|
||||||
var isStore = binding.BindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
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);
|
binding.Texture.SetStorage(range);
|
||||||
|
|
||||||
textureArray[0] = binding.Texture;
|
textureArray[0] = binding.Texture;
|
||||||
|
@ -565,7 +565,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (!_indexBuffer.Range.IsUnmapped)
|
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);
|
_context.Renderer.Pipeline.SetIndexBuffer(buffer, _indexBuffer.Type);
|
||||||
}
|
}
|
||||||
|
@ -597,7 +597,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferRange buffer = bufferCache.GetBufferRange(vb.Range);
|
BufferRange buffer = bufferCache.GetBufferRange(vb.Range, BufferStage.VertexBuffer);
|
||||||
|
|
||||||
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
|
vertexBuffers[index] = new VertexBufferDescriptor(buffer, vb.Stride, vb.Divisor);
|
||||||
}
|
}
|
||||||
|
@ -637,7 +637,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tfbs[index] = bufferCache.GetBufferRange(tfb.Range, write: true);
|
tfbs[index] = bufferCache.GetBufferRange(tfb.Range, BufferStage.TransformFeedback, write: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
|
_context.Renderer.Pipeline.SetTransformFeedbackBuffers(tfbs);
|
||||||
|
@ -684,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
_context.SupportBufferUpdater.SetTfeOffset(index, tfeOffset);
|
_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++)
|
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
|
||||||
{
|
{
|
||||||
ref var buffers = ref bindings[(int)stage - 1];
|
ref var buffers = ref bindings[(int)stage - 1];
|
||||||
|
BufferStage bufferStage = BufferStageUtils.FromShaderStage(stage);
|
||||||
|
|
||||||
for (int index = 0; index < buffers.Count; index++)
|
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 isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||||
var range = isStorage
|
var range = isStorage
|
||||||
? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite)
|
? bufferCache.GetBufferRangeAligned(bounds.Range, bufferStage | BufferStageUtils.FromUsage(bounds.Flags), isWrite)
|
||||||
: bufferCache.GetBufferRange(bounds.Range);
|
: bufferCache.GetBufferRange(bounds.Range, bufferStage);
|
||||||
|
|
||||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||||
}
|
}
|
||||||
|
@ -799,8 +800,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
var isWrite = bounds.Flags.HasFlag(BufferUsageFlags.Write);
|
||||||
var range = isStorage
|
var range = isStorage
|
||||||
? bufferCache.GetBufferRangeAligned(bounds.Range, isWrite)
|
? bufferCache.GetBufferRangeAligned(bounds.Range, BufferStageUtils.ComputeStorage(bounds.Flags), isWrite)
|
||||||
: bufferCache.GetBufferRange(bounds.Range);
|
: bufferCache.GetBufferRange(bounds.Range, BufferStage.Compute);
|
||||||
|
|
||||||
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
|
||||||
}
|
}
|
||||||
|
@ -875,7 +876,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
Format format,
|
Format format,
|
||||||
bool isImage)
|
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));
|
_bufferTextures.Add(new BufferTextureBinding(stage, texture, range, bindingInfo, format, isImage));
|
||||||
}
|
}
|
||||||
|
@ -883,6 +884,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
|
/// Sets the buffer storage of a buffer texture array element. This will be bound when the buffer manager commits bindings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="stage">Shader stage accessing the texture</param>
|
||||||
/// <param name="array">Texture array where the element will be inserted</param>
|
/// <param name="array">Texture array where the element will be inserted</param>
|
||||||
/// <param name="texture">Buffer texture</param>
|
/// <param name="texture">Buffer texture</param>
|
||||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||||
|
@ -890,6 +892,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="index">Index of the binding on the array</param>
|
/// <param name="index">Index of the binding on the array</param>
|
||||||
/// <param name="format">Format of the buffer texture</param>
|
/// <param name="format">Format of the buffer texture</param>
|
||||||
public void SetBufferTextureStorage(
|
public void SetBufferTextureStorage(
|
||||||
|
ShaderStage stage,
|
||||||
ITextureArray array,
|
ITextureArray array,
|
||||||
ITexture texture,
|
ITexture texture,
|
||||||
MultiRange range,
|
MultiRange range,
|
||||||
|
@ -897,7 +900,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
int index,
|
int index,
|
||||||
Format format)
|
Format format)
|
||||||
{
|
{
|
||||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
|
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||||
|
|
||||||
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format));
|
_bufferTextureArrays.Add(new BufferTextureArrayBinding<ITextureArray>(array, texture, range, bindingInfo, index, format));
|
||||||
}
|
}
|
||||||
|
@ -905,6 +908,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
|
/// Sets the buffer storage of a buffer image array element. This will be bound when the buffer manager commits bindings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="stage">Shader stage accessing the texture</param>
|
||||||
/// <param name="array">Image array where the element will be inserted</param>
|
/// <param name="array">Image array where the element will be inserted</param>
|
||||||
/// <param name="texture">Buffer texture</param>
|
/// <param name="texture">Buffer texture</param>
|
||||||
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
/// <param name="range">Physical ranges of memory where the buffer texture data is located</param>
|
||||||
|
@ -912,6 +916,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="index">Index of the binding on the array</param>
|
/// <param name="index">Index of the binding on the array</param>
|
||||||
/// <param name="format">Format of the buffer texture</param>
|
/// <param name="format">Format of the buffer texture</param>
|
||||||
public void SetBufferTextureStorage(
|
public void SetBufferTextureStorage(
|
||||||
|
ShaderStage stage,
|
||||||
IImageArray array,
|
IImageArray array,
|
||||||
ITexture texture,
|
ITexture texture,
|
||||||
MultiRange range,
|
MultiRange range,
|
||||||
|
@ -919,7 +924,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
int index,
|
int index,
|
||||||
Format format)
|
Format format)
|
||||||
{
|
{
|
||||||
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range);
|
_channel.MemoryManager.Physical.BufferCache.CreateBuffer(range, BufferStageUtils.TextureBuffer(stage, bindingInfo.Flags));
|
||||||
|
|
||||||
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format));
|
_bufferImageArrays.Add(new BufferTextureArrayBinding<IImageArray>(array, texture, range, bindingInfo, index, format));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,21 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete.
|
/// A record of when buffer data was copied from multiple buffers to one migration target,
|
||||||
/// Keeps the source buffer alive for data flushes until the migration is complete.
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class BufferMigration : IDisposable
|
internal class BufferMigration : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The offset for the migrated region.
|
/// Ranges from source buffers that were copied as part of this migration.
|
||||||
|
/// Ordered by increasing base address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ulong _offset;
|
public BufferMigrationSpan[] Spans { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The size for the migrated region.
|
|
||||||
/// </summary>
|
|
||||||
private readonly ulong _size;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The buffer that was migrated from.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Buffer _buffer;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The source range action, to be called on overlap with an unreached sync number.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Action<ulong, ulong> _sourceRangeAction;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The source range list.
|
|
||||||
/// </summary>
|
|
||||||
private readonly BufferModifiedRangeList _source;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The destination range list. This range list must be updated when flushing the source.
|
/// The destination range list. This range list must be updated when flushing the source.
|
||||||
|
@ -43,55 +27,193 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ulong SyncNumber;
|
public readonly ulong SyncNumber;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of active users there are traversing this migration's spans.
|
||||||
|
/// </summary>
|
||||||
|
private int _refCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new buffer migration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spans">Source spans for the migration</param>
|
||||||
|
/// <param name="destination">Destination buffer range list</param>
|
||||||
|
/// <param name="syncNumber">Sync number where this migration will be complete</param>
|
||||||
|
public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber)
|
||||||
|
{
|
||||||
|
Spans = spans;
|
||||||
|
Destination = destination;
|
||||||
|
SyncNumber = syncNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a span to the migration. Allocates a new array with the target size, and replaces it.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The base address for the span is assumed to be higher than all other spans in the migration,
|
||||||
|
/// to keep the span array ordered.
|
||||||
|
/// </remarks>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs the given range action, or one from a migration that overlaps and has not synced yet.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset to pass to the action</param>
|
||||||
|
/// <param name="size">The size to pass to the action</param>
|
||||||
|
/// <param name="syncNumber">The sync number that has been reached</param>
|
||||||
|
/// <param name="rangeAction">The action to perform</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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)
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly struct BufferMigrationSpan : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The offset for the migrated region.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ulong Address;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size for the migrated region.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ulong Size;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The action to perform when the migration isn't needed anymore.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Action _disposeAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The source range action, to be called on overlap with an unreached sync number.
|
||||||
|
/// </summary>
|
||||||
|
private readonly BufferFlushAction _sourceRangeAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private readonly BufferMigration _source;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a record for a buffer migration.
|
/// Creates a record for a buffer migration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buffer">The source buffer for this migration</param>
|
/// <param name="buffer">The source buffer for this migration</param>
|
||||||
|
/// <param name="disposeAction">The action to perform when the migration isn't needed anymore</param>
|
||||||
/// <param name="sourceRangeAction">The flush action for the source buffer</param>
|
/// <param name="sourceRangeAction">The flush action for the source buffer</param>
|
||||||
/// <param name="source">The modified range list for the source buffer</param>
|
/// <param name="source">Pending migration for the source buffer</param>
|
||||||
/// <param name="dest">The modified range list for the destination buffer</param>
|
public BufferMigrationSpan(
|
||||||
/// <param name="syncNumber">The sync number for when the migration is complete</param>
|
|
||||||
public BufferMigration(
|
|
||||||
Buffer buffer,
|
Buffer buffer,
|
||||||
Action<ulong, ulong> sourceRangeAction,
|
Action disposeAction,
|
||||||
BufferModifiedRangeList source,
|
BufferFlushAction sourceRangeAction,
|
||||||
BufferModifiedRangeList dest,
|
BufferMigration source)
|
||||||
ulong syncNumber)
|
|
||||||
{
|
{
|
||||||
_offset = buffer.Address;
|
Address = buffer.Address;
|
||||||
_size = buffer.Size;
|
Size = buffer.Size;
|
||||||
_buffer = buffer;
|
_disposeAction = disposeAction;
|
||||||
_sourceRangeAction = sourceRangeAction;
|
_sourceRangeAction = sourceRangeAction;
|
||||||
_source = source;
|
_source = source;
|
||||||
Destination = dest;
|
|
||||||
SyncNumber = syncNumber;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a record for a buffer migration, using the default buffer dispose action.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">The source buffer for this migration</param>
|
||||||
|
/// <param name="sourceRangeAction">The flush action for the source buffer</param>
|
||||||
|
/// <param name="source">Pending migration for the source buffer</param>
|
||||||
|
public BufferMigrationSpan(
|
||||||
|
Buffer buffer,
|
||||||
|
BufferFlushAction sourceRangeAction,
|
||||||
|
BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determine if the given range overlaps this migration, and has not been completed yet.
|
/// Determine if the given range overlaps this migration, and has not been completed yet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="offset">Start offset</param>
|
/// <param name="offset">Start offset</param>
|
||||||
/// <param name="size">Range size</param>
|
/// <param name="size">Range size</param>
|
||||||
/// <param name="syncNumber">The sync number that was waited on</param>
|
|
||||||
/// <returns>True if overlapping and in progress, false otherwise</returns>
|
/// <returns>True if overlapping and in progress, false otherwise</returns>
|
||||||
public bool Overlaps(ulong offset, ulong size, ulong syncNumber)
|
public bool Overlaps(ulong offset, ulong size)
|
||||||
{
|
{
|
||||||
ulong end = offset + size;
|
ulong end = offset + size;
|
||||||
ulong destEnd = _offset + _size;
|
ulong destEnd = Address + Size;
|
||||||
long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed.
|
|
||||||
|
|
||||||
return !(end <= _offset || offset >= destEnd) && syncDiff < 0;
|
return !(end <= Address || offset >= destEnd);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determine if the given range matches this migration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="offset">Start offset</param>
|
|
||||||
/// <param name="size">Range size</param>
|
|
||||||
/// <returns>True if the range exactly matches, false otherwise</returns>
|
|
||||||
public bool FullyMatches(ulong offset, ulong size)
|
|
||||||
{
|
|
||||||
return _offset == offset && _size == size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -100,26 +222,30 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="offset">Start offset</param>
|
/// <param name="offset">Start offset</param>
|
||||||
/// <param name="size">Range size</param>
|
/// <param name="size">Range size</param>
|
||||||
/// <param name="syncNumber">Current sync number</param>
|
/// <param name="syncNumber">Current sync number</param>
|
||||||
/// <param name="parent">The modified range list that originally owned this range</param>
|
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber)
|
||||||
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
|
|
||||||
{
|
{
|
||||||
ulong end = offset + size;
|
ulong end = offset + size;
|
||||||
end = Math.Min(_offset + _size, end);
|
end = Math.Min(Address + Size, end);
|
||||||
offset = Math.Max(_offset, offset);
|
offset = Math.Max(Address, offset);
|
||||||
|
|
||||||
size = end - 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Destination.RemoveMigration(this);
|
_disposeAction();
|
||||||
|
|
||||||
_buffer.DecrementReferenceCount();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using Ryujinx.Common.Pools;
|
using Ryujinx.Common.Pools;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Memory
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
@ -72,10 +71,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
private readonly Buffer _parent;
|
private readonly Buffer _parent;
|
||||||
private readonly Action<ulong, ulong> _flushAction;
|
private readonly BufferFlushAction _flushAction;
|
||||||
|
|
||||||
private List<BufferMigration> _sources;
|
private BufferMigration _source;
|
||||||
private BufferMigration _migrationTarget;
|
private BufferModifiedRangeList _migrationTarget;
|
||||||
|
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
@ -99,7 +98,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="context">GPU context that the buffer range list belongs to</param>
|
/// <param name="context">GPU context that the buffer range list belongs to</param>
|
||||||
/// <param name="parent">The parent buffer that owns this range list</param>
|
/// <param name="parent">The parent buffer that owns this range list</param>
|
||||||
/// <param name="flushAction">The flush action for the parent buffer</param>
|
/// <param name="flushAction">The flush action for the parent buffer</param>
|
||||||
public BufferModifiedRangeList(GpuContext context, Buffer parent, Action<ulong, ulong> flushAction) : base(BackingInitialSize)
|
public BufferModifiedRangeList(GpuContext context, Buffer parent, BufferFlushAction flushAction) : base(BackingInitialSize)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
|
@ -199,6 +198,36 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets modified ranges within the specified region, and then fires the given action for each range individually.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Start address to query</param>
|
||||||
|
/// <param name="size">Size to query</param>
|
||||||
|
/// <param name="syncNumber">Sync number required for a range to be signalled</param>
|
||||||
|
/// <param name="rangeAction">The action to call for each modified range</param>
|
||||||
|
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
ref var overlaps = ref ThreadStaticArray<BufferModifiedRange>.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets modified ranges within the specified region, and then fires the given action for each range individually.
|
/// Gets modified ranges within the specified region, and then fires the given action for each range individually.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -245,41 +274,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <param name="offset">The offset to pass to the action</param>
|
/// <param name="offset">The offset to pass to the action</param>
|
||||||
/// <param name="size">The size to pass to the action</param>
|
/// <param name="size">The size to pass to the action</param>
|
||||||
/// <param name="syncNumber">The sync number that has been reached</param>
|
/// <param name="syncNumber">The sync number that has been reached</param>
|
||||||
/// <param name="parent">The modified range list that originally owned this range</param>
|
|
||||||
/// <param name="rangeAction">The action to perform</param>
|
/// <param name="rangeAction">The action to perform</param>
|
||||||
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action<ulong, ulong> rangeAction)
|
public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
|
||||||
{
|
{
|
||||||
bool firstSource = true;
|
if (_source != null)
|
||||||
|
|
||||||
if (parent != this)
|
|
||||||
{
|
{
|
||||||
lock (_lock)
|
_source.RangeActionWithMigration(offset, size, syncNumber, rangeAction);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (firstSource)
|
|
||||||
{
|
{
|
||||||
// No overlapping migrations, or they are not meant for this range, flush the data using the given action.
|
rangeAction(offset, size, syncNumber);
|
||||||
rangeAction(offset, size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +323,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
ClearPart(overlap, clampAddress, clampEnd);
|
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.
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -367,7 +371,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
if (rangeCount == -1)
|
if (rangeCount == -1)
|
||||||
{
|
{
|
||||||
_migrationTarget.Destination.WaitForAndFlushRanges(address, size);
|
_migrationTarget.WaitForAndFlushRanges(address, size);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -407,6 +411,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inherit ranges from another modified range list.
|
/// Inherit ranges from another modified range list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Assumes that ranges will be inherited in address ascending order.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="ranges">The range list to inherit from</param>
|
/// <param name="ranges">The range list to inherit from</param>
|
||||||
/// <param name="registerRangeAction">The action to call for each modified range</param>
|
/// <param name="registerRangeAction">The action to call for each modified range</param>
|
||||||
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
|
public void InheritRanges(BufferModifiedRangeList ranges, Action<ulong, ulong> registerRangeAction)
|
||||||
|
@ -415,18 +422,31 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
lock (ranges._lock)
|
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();
|
inheritRanges = ranges.ToArray();
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
(_sources ??= new List<BufferMigration>()).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)
|
foreach (BufferModifiedRange range in inheritRanges)
|
||||||
{
|
{
|
||||||
|
@ -445,6 +465,27 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes a source buffer migration, indicating its copy has completed.
|
/// Removes a source buffer migration, indicating its copy has completed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -453,7 +494,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_sources.Remove(migration);
|
if (_source == migration)
|
||||||
|
{
|
||||||
|
_source = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
295
src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs
Normal file
295
src/Ryujinx.Graphics.Gpu/Memory/BufferPreFlush.cs
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
internal class BufferPreFlush : IDisposable
|
||||||
|
{
|
||||||
|
private const ulong PageSize = MemoryManager.PageSize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Threshold for the number of copies without a flush required to disable preflush on a page.
|
||||||
|
/// </summary>
|
||||||
|
private const int DeactivateCopyThreshold = 200;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value that indicates whether a page has been flushed or copied before.
|
||||||
|
/// </summary>
|
||||||
|
private enum PreFlushState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
HasFlushed,
|
||||||
|
HasCopied
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
private struct PreFlushPage
|
||||||
|
{
|
||||||
|
public PreFlushState State;
|
||||||
|
public ulong FirstActivatedSync;
|
||||||
|
public ulong LastCopiedSync;
|
||||||
|
public int CopyCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if there are ranges that should copy to the flush buffer, false otherwise.
|
||||||
|
/// </summary>
|
||||||
|
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<BufferHandle, ulong, ulong> _flushAction;
|
||||||
|
|
||||||
|
private BufferHandle _flushBuffer;
|
||||||
|
|
||||||
|
public BufferPreFlush(GpuContext context, Buffer parent, Action<BufferHandle, ulong, ulong> flushAction)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_buffer = parent;
|
||||||
|
_address = parent.Address;
|
||||||
|
_size = parent.Size;
|
||||||
|
_pages = new PreFlushPage[BitUtils.DivRoundUp(_size, PageSize)];
|
||||||
|
_misalignment = _address & (PageSize - 1);
|
||||||
|
|
||||||
|
_flushAction = flushAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure that the flush buffer exists.
|
||||||
|
/// </summary>
|
||||||
|
private void EnsureFlushBuffer()
|
||||||
|
{
|
||||||
|
if (_flushBuffer == BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_flushBuffer = _context.Renderer.CreateBuffer((int)_size, BufferAccess.HostMemory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a page range from an address and size byte range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Range address</param>
|
||||||
|
/// <param name="size">Range size</param>
|
||||||
|
/// <returns>A page index and count</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an offset and size range in the parent buffer from a page index and count.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startPage">Range start page</param>
|
||||||
|
/// <param name="count">Range page count</param>
|
||||||
|
/// <returns>Offset and size range</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy a range of pages from the parent buffer into the flush buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startPage">Range start page</param>
|
||||||
|
/// <param name="count">Range page count</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Range address</param>
|
||||||
|
/// <param name="size">Range size</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Address range start</param>
|
||||||
|
/// <param name="size">Address range size</param>
|
||||||
|
/// <param name="startPage">Page range start</param>
|
||||||
|
/// <param name="count">Page range count</param>
|
||||||
|
/// <param name="preFlush">True if the data should come from the flush buffer</param>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Range address</param>
|
||||||
|
/// <param name="size">Range size</param>
|
||||||
|
/// <param name="syncNumber">Sync number that has been waited for</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose the flush buffer, if present.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_flushBuffer != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_context.Renderer.DeleteBuffer(_flushBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs
Normal file
99
src/Ryujinx.Graphics.Gpu/Memory/BufferStage.cs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods to convert shader stages and binding flags into buffer stages.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,6 +62,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
var result = new TextureBindingInfo(
|
var result = new TextureBindingInfo(
|
||||||
target,
|
target,
|
||||||
|
descriptor.Set,
|
||||||
descriptor.Binding,
|
descriptor.Binding,
|
||||||
descriptor.ArrayLength,
|
descriptor.ArrayLength,
|
||||||
descriptor.CbufSlot,
|
descriptor.CbufSlot,
|
||||||
|
@ -90,6 +91,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
var result = new TextureBindingInfo(
|
var result = new TextureBindingInfo(
|
||||||
target,
|
target,
|
||||||
format,
|
format,
|
||||||
|
descriptor.Set,
|
||||||
descriptor.Binding,
|
descriptor.Binding,
|
||||||
descriptor.ArrayLength,
|
descriptor.ArrayLength,
|
||||||
descriptor.CbufSlot,
|
descriptor.CbufSlot,
|
||||||
|
|
|
@ -125,9 +125,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
CompressionAlgorithm algorithm = CompressionAlgorithm.None;
|
CompressionAlgorithm algorithm = CompressionAlgorithm.None;
|
||||||
Read(ref algorithm);
|
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);
|
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)
|
switch (algorithm)
|
||||||
{
|
{
|
||||||
case CompressionAlgorithm.None:
|
case CompressionAlgorithm.None:
|
||||||
stream.Read(data);
|
stream.ReadExactly(data);
|
||||||
break;
|
break;
|
||||||
case CompressionAlgorithm.Deflate:
|
case CompressionAlgorithm.Deflate:
|
||||||
stream = new DeflateStream(stream, CompressionMode.Decompress, true);
|
stream = new DeflateStream(stream, CompressionMode.Decompress, true);
|
||||||
|
@ -187,6 +205,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
}
|
}
|
||||||
stream.Dispose();
|
stream.Dispose();
|
||||||
break;
|
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);
|
stream.Write(data);
|
||||||
break;
|
break;
|
||||||
case CompressionAlgorithm.Deflate:
|
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.Write(data);
|
||||||
stream.Dispose();
|
stream.Dispose();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -14,5 +14,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// Deflate compression (RFC 1951).
|
/// Deflate compression (RFC 1951).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Deflate,
|
Deflate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Brotli compression (RFC 7932).
|
||||||
|
/// </summary>
|
||||||
|
Brotli,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
/// <returns>Compression algorithm</returns>
|
/// <returns>Compression algorithm</returns>
|
||||||
public static CompressionAlgorithm GetCompressionAlgorithm()
|
public static CompressionAlgorithm GetCompressionAlgorithm()
|
||||||
{
|
{
|
||||||
return CompressionAlgorithm.Deflate;
|
return CompressionAlgorithm.Brotli;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
}
|
}
|
||||||
|
|
||||||
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
|
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
|
||||||
dataFileStream.Read(cb1Data);
|
dataFileStream.ReadExactly(cb1Data);
|
||||||
BinarySerializer.ReadCompressed(dataFileStream, guestCode);
|
BinarySerializer.ReadCompressed(dataFileStream, guestCode);
|
||||||
|
|
||||||
_cache[index] = (guestCode, cb1Data);
|
_cache[index] = (guestCode, cb1Data);
|
||||||
|
@ -279,7 +279,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
|
dataFileStream.Seek((long)entry.Offset, SeekOrigin.Begin);
|
||||||
byte[] cachedCode = new byte[entry.CodeSize];
|
byte[] cachedCode = new byte[entry.CodeSize];
|
||||||
byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
|
byte[] cachedCb1Data = new byte[entry.Cb1DataSize];
|
||||||
dataFileStream.Read(cachedCb1Data);
|
dataFileStream.ReadExactly(cachedCb1Data);
|
||||||
BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
|
BinarySerializer.ReadCompressed(dataFileStream, cachedCode);
|
||||||
|
|
||||||
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
|
if (data.SequenceEqual(cachedCode) && cb1Data.SequenceEqual(cachedCb1Data))
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 5936;
|
private const uint CodeGenVersion = 6921;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
|
|
@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
_reservedImages = rrc.ReservedImages;
|
_reservedImages = rrc.ReservedImages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CreateConstantBufferBinding(int index)
|
public SetBindingPair CreateConstantBufferBinding(int index)
|
||||||
{
|
{
|
||||||
int binding;
|
int binding;
|
||||||
|
|
||||||
|
@ -64,10 +64,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
binding = _resourceCounts.UniformBuffersCount++;
|
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;
|
int binding;
|
||||||
|
|
||||||
|
@ -96,10 +96,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
_resourceCounts.ImagesCount += count;
|
_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;
|
int binding;
|
||||||
|
|
||||||
|
@ -112,10 +112,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
binding = _resourceCounts.StorageBuffersCount++;
|
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;
|
int binding;
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
_resourceCounts.TexturesCount += count;
|
_resourceCounts.TexturesCount += count;
|
||||||
}
|
}
|
||||||
|
|
||||||
return binding + _reservedTextures;
|
return new SetBindingPair(_context.Capabilities.TextureSetIndex, binding + _reservedTextures);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
|
private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName)
|
||||||
|
@ -183,6 +183,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
return maxPerStage * Constants.ShaderStages;
|
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 int QueryHostGatherBiasPrecision() => _context.Capabilities.GatherBiasPrecision;
|
||||||
|
|
||||||
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
|
public bool QueryHostReducedPrecision() => _context.Capabilities.ReduceShaderPrecision;
|
||||||
|
|
|
@ -24,5 +24,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// Total of images used by the shaders.
|
/// Total of images used by the shaders.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ImagesCount;
|
public int ImagesCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total of extra sets used by the shaders.
|
||||||
|
/// </summary>
|
||||||
|
public int SetsCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Shader
|
namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
@ -9,13 +10,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class ShaderInfoBuilder
|
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 =
|
private const ResourceStages SupportBufferStages =
|
||||||
ResourceStages.Compute |
|
ResourceStages.Compute |
|
||||||
ResourceStages.Vertex |
|
ResourceStages.Vertex |
|
||||||
|
@ -36,8 +30,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
private readonly int _reservedTextures;
|
private readonly int _reservedTextures;
|
||||||
private readonly int _reservedImages;
|
private readonly int _reservedImages;
|
||||||
|
|
||||||
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
|
private List<ResourceDescriptor>[] _resourceDescriptors;
|
||||||
private readonly List<ResourceUsage>[] _resourceUsages;
|
private List<ResourceUsage>[] _resourceUsages;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new shader info builder.
|
/// Creates a new shader info builder.
|
||||||
|
@ -51,17 +45,27 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
|
|
||||||
_fragmentOutputMap = -1;
|
_fragmentOutputMap = -1;
|
||||||
|
|
||||||
_resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
|
int uniformSetIndex = context.Capabilities.UniformBufferSetIndex;
|
||||||
_resourceUsages = new List<ResourceUsage>[TotalSets];
|
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<ResourceDescriptor>[totalSets];
|
||||||
|
_resourceUsages = new List<ResourceUsage>[totalSets];
|
||||||
|
|
||||||
|
for (int index = 0; index < totalSets; index++)
|
||||||
{
|
{
|
||||||
_resourceDescriptors[index] = new();
|
_resourceDescriptors[index] = new();
|
||||||
_resourceUsages[index] = new();
|
_resourceUsages[index] = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
AddDescriptor(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1);
|
||||||
AddUsage(SupportBufferStages, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
AddUsage(SupportBufferStages, ResourceType.UniformBuffer, uniformSetIndex, 0, 1);
|
||||||
|
|
||||||
ResourceReservationCounts rrc = new(!context.Capabilities.SupportsTransformFeedback && tfEnabled, vertexAsCompute);
|
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.
|
// 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;
|
ResourceStages stages = vertexAsCompute ? ResourceStages.Compute | ResourceStages.Vertex : VtgStages;
|
||||||
|
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, UniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
|
PopulateDescriptorAndUsages(stages, ResourceType.UniformBuffer, uniformSetIndex, 1, rrc.ReservedConstantBuffers - 1);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, StorageSetIndex, 0, rrc.ReservedStorageBuffers);
|
PopulateDescriptorAndUsages(stages, ResourceType.StorageBuffer, storageSetIndex, 0, rrc.ReservedStorageBuffers);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, TextureSetIndex, 0, rrc.ReservedTextures);
|
PopulateDescriptorAndUsages(stages, ResourceType.BufferTexture, textureSetIndex, 0, rrc.ReservedTextures);
|
||||||
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, ImageSetIndex, 0, rrc.ReservedImages);
|
PopulateDescriptorAndUsages(stages, ResourceType.BufferImage, imageSetIndex, 0, rrc.ReservedImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Populates descriptors and usages for vertex as compute and transform feedback emulation reserved resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stages">Shader stages where the resources are used</param>
|
||||||
|
/// <param name="type">Resource type</param>
|
||||||
|
/// <param name="setIndex">Resource set index where the resources are used</param>
|
||||||
|
/// <param name="start">First binding number</param>
|
||||||
|
/// <param name="count">Amount of bindings</param>
|
||||||
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
|
private void PopulateDescriptorAndUsages(ResourceStages stages, ResourceType type, int setIndex, int start, int count)
|
||||||
{
|
{
|
||||||
AddDescriptor(stages, type, setIndex, start, count);
|
AddDescriptor(stages, type, setIndex, start, count);
|
||||||
|
@ -127,18 +139,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2;
|
int textureBinding = _reservedTextures + stageIndex * texturesPerStage * 2;
|
||||||
int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2;
|
int imageBinding = _reservedImages + stageIndex * imagesPerStage * 2;
|
||||||
|
|
||||||
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
|
int uniformSetIndex = _context.Capabilities.UniformBufferSetIndex;
|
||||||
AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
|
int storageSetIndex = _context.Capabilities.StorageBufferSetIndex;
|
||||||
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
|
int textureSetIndex = _context.Capabilities.TextureSetIndex;
|
||||||
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
|
int imageSetIndex = _context.Capabilities.ImageSetIndex;
|
||||||
|
|
||||||
AddArrayDescriptors(info.Textures, stages, TextureSetIndex, isImage: false);
|
AddDescriptor(stages, ResourceType.UniformBuffer, uniformSetIndex, uniformBinding, uniformsPerStage);
|
||||||
AddArrayDescriptors(info.Images, stages, TextureSetIndex, isImage: true);
|
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);
|
AddArrayDescriptors(info.Textures, stages, isImage: false);
|
||||||
AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
|
AddArrayDescriptors(info.Images, stages, isImage: true);
|
||||||
AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
|
|
||||||
AddUsage(info.Images, stages, ImageSetIndex, 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -177,9 +194,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="textures">Textures to be added</param>
|
/// <param name="textures">Textures to be added</param>
|
||||||
/// <param name="stages">Stages where the textures are used</param>
|
/// <param name="stages">Stages where the textures are used</param>
|
||||||
/// <param name="setIndex">Descriptor set index where the textures will be bound</param>
|
|
||||||
/// <param name="isImage">True for images, false for textures</param>
|
/// <param name="isImage">True for images, false for textures</param>
|
||||||
private void AddArrayDescriptors(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage)
|
private void AddArrayDescriptors(IEnumerable<TextureDescriptor> textures, ResourceStages stages, bool isImage)
|
||||||
{
|
{
|
||||||
foreach (TextureDescriptor texture in textures)
|
foreach (TextureDescriptor texture in textures)
|
||||||
{
|
{
|
||||||
|
@ -187,7 +203,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
ResourceType type = GetTextureResourceType(texture, isImage);
|
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
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buffers">Buffers to be added</param>
|
/// <param name="buffers">Buffers to be added</param>
|
||||||
/// <param name="stages">Stages where the buffers are used</param>
|
/// <param name="stages">Stages where the buffers are used</param>
|
||||||
/// <param name="setIndex">Descriptor set index where the buffers will be bound</param>
|
|
||||||
/// <param name="isStorage">True for storage buffers, false for uniform buffers</param>
|
/// <param name="isStorage">True for storage buffers, false for uniform buffers</param>
|
||||||
private void AddUsage(IEnumerable<BufferDescriptor> buffers, ResourceStages stages, int setIndex, bool isStorage)
|
private void AddUsage(IEnumerable<BufferDescriptor> buffers, ResourceStages stages, bool isStorage)
|
||||||
{
|
{
|
||||||
foreach (BufferDescriptor buffer in buffers)
|
foreach (BufferDescriptor buffer in buffers)
|
||||||
{
|
{
|
||||||
_resourceUsages[setIndex].Add(new ResourceUsage(
|
GetUsages(buffer.Set).Add(new ResourceUsage(
|
||||||
buffer.Binding,
|
buffer.Binding,
|
||||||
1,
|
1,
|
||||||
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
||||||
|
@ -232,18 +247,65 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="textures">Textures to be added</param>
|
/// <param name="textures">Textures to be added</param>
|
||||||
/// <param name="stages">Stages where the textures are used</param>
|
/// <param name="stages">Stages where the textures are used</param>
|
||||||
/// <param name="setIndex">Descriptor set index where the textures will be bound</param>
|
|
||||||
/// <param name="isImage">True for images, false for textures</param>
|
/// <param name="isImage">True for images, false for textures</param>
|
||||||
private void AddUsage(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage)
|
private void AddUsage(IEnumerable<TextureDescriptor> textures, ResourceStages stages, bool isImage)
|
||||||
{
|
{
|
||||||
foreach (TextureDescriptor texture in textures)
|
foreach (TextureDescriptor texture in textures)
|
||||||
{
|
{
|
||||||
ResourceType type = GetTextureResourceType(texture, isImage);
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of resource descriptors for a given set index. A new list will be created if needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="setIndex">Resource set index</param>
|
||||||
|
/// <returns>List of resource descriptors</returns>
|
||||||
|
private List<ResourceDescriptor> 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of resource usages for a given set index. A new list will be created if needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="setIndex">Resource set index</param>
|
||||||
|
/// <returns>List of resource usages</returns>
|
||||||
|
private List<ResourceUsage> 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a resource type from a texture descriptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture descriptor</param>
|
||||||
|
/// <param name="isImage">Whether the texture is a image texture (writable) or not (sampled)</param>
|
||||||
|
/// <returns>Resource type</returns>
|
||||||
private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage)
|
private static ResourceType GetTextureResourceType(TextureDescriptor texture, bool isImage)
|
||||||
{
|
{
|
||||||
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
|
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
|
||||||
|
@ -278,10 +340,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
/// <returns>Shader information</returns>
|
/// <returns>Shader information</returns>
|
||||||
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
|
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
|
||||||
{
|
{
|
||||||
var descriptors = new ResourceDescriptorCollection[TotalSets];
|
int totalSets = _resourceDescriptors.Length;
|
||||||
var usages = new ResourceUsageCollection[TotalSets];
|
|
||||||
|
|
||||||
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());
|
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
|
||||||
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
|
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
|
||||||
|
|
|
@ -63,5 +63,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,5 +48,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,9 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
{
|
{
|
||||||
BufferCount++;
|
BufferCount++;
|
||||||
|
|
||||||
if (access.HasFlag(GAL.BufferAccess.FlushPersistent))
|
var memType = access & GAL.BufferAccess.MemoryTypeMask;
|
||||||
|
|
||||||
|
if (memType == GAL.BufferAccess.HostMemory)
|
||||||
{
|
{
|
||||||
BufferHandle handle = Buffer.CreatePersistent(size);
|
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)
|
public BufferHandle CreateBuffer(nint pointer, int size)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
|
@ -148,6 +145,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
return new Capabilities(
|
return new Capabilities(
|
||||||
api: TargetApi.OpenGL,
|
api: TargetApi.OpenGL,
|
||||||
vendorName: GpuVendor,
|
vendorName: GpuVendor,
|
||||||
|
memoryType: SystemMemoryType.BackendManaged,
|
||||||
hasFrontFacingBug: intelWindows,
|
hasFrontFacingBug: intelWindows,
|
||||||
hasVectorIndexingBug: amdWindows,
|
hasVectorIndexingBug: amdWindows,
|
||||||
needsFragmentOutputSpecialization: false,
|
needsFragmentOutputSpecialization: false,
|
||||||
|
@ -189,6 +187,12 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||||
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
||||||
supportsDepthClipControl: true,
|
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?
|
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
|
||||||
maximumStorageBuffersPerStage: 16,
|
maximumStorageBuffersPerStage: 16,
|
||||||
maximumTexturesPerStage: 32,
|
maximumTexturesPerStage: 32,
|
||||||
|
|
|
@ -963,6 +963,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
(array as ImageArray).Bind(binding);
|
(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)
|
public void SetIndexBuffer(BufferRange buffer, IndexType type)
|
||||||
{
|
{
|
||||||
_elementsType = type.Convert();
|
_elementsType = type.Convert();
|
||||||
|
@ -1312,6 +1317,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
(array as TextureArray).Bind(binding);
|
(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<BufferRange> buffers)
|
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
||||||
{
|
{
|
||||||
if (_tfEnabled)
|
if (_tfEnabled)
|
||||||
|
|
|
@ -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.
|
// 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 int Binding;
|
||||||
public readonly byte Slot;
|
public readonly byte Slot;
|
||||||
public readonly byte SbCbSlot;
|
public readonly byte SbCbSlot;
|
||||||
public readonly ushort SbCbOffset;
|
public readonly ushort SbCbOffset;
|
||||||
public readonly BufferUsageFlags Flags;
|
public readonly BufferUsageFlags Flags;
|
||||||
|
|
||||||
public BufferDescriptor(int binding, int slot)
|
public BufferDescriptor(int set, int binding, int slot)
|
||||||
{
|
{
|
||||||
|
Set = set;
|
||||||
Binding = binding;
|
Binding = binding;
|
||||||
Slot = (byte)slot;
|
Slot = (byte)slot;
|
||||||
SbCbSlot = 0;
|
SbCbSlot = 0;
|
||||||
|
@ -19,8 +21,9 @@ namespace Ryujinx.Graphics.Shader
|
||||||
Flags = BufferUsageFlags.None;
|
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;
|
Binding = binding;
|
||||||
Slot = (byte)slot;
|
Slot = (byte)slot;
|
||||||
SbCbSlot = (byte)sbCbSlot;
|
SbCbSlot = (byte)sbCbSlot;
|
||||||
|
|
|
@ -462,7 +462,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
}
|
}
|
||||||
else
|
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;
|
bool hasLod = !definition.Type.HasFlag(SamplerType.Multisample) && (definition.Type & SamplerType.Mask) != SamplerType.TextureBuffer;
|
||||||
string texCall;
|
string texCall;
|
||||||
|
|
||||||
|
@ -639,7 +639,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
|
|
||||||
private static string GetSamplerName(CodeGenContext context, AstTextureOperation texOp, ref int srcIndex)
|
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;
|
string name = textureDefinition.Name;
|
||||||
|
|
||||||
if (textureDefinition.ArrayLength != 1)
|
if (textureDefinition.ArrayLength != 1)
|
||||||
|
@ -649,7 +649,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||||
|
|
||||||
if (texOp.IsSeparate)
|
if (texOp.IsSeparate)
|
||||||
{
|
{
|
||||||
TextureDefinition samplerDefinition = context.Properties.Textures[texOp.SamplerBinding];
|
TextureDefinition samplerDefinition = context.Properties.Textures[texOp.GetSamplerSetAndBinding()];
|
||||||
string samplerName = samplerDefinition.Name;
|
string samplerName = samplerDefinition.Name;
|
||||||
|
|
||||||
if (samplerDefinition.ArrayLength != 1)
|
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)
|
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;
|
string name = definition.Name;
|
||||||
|
|
||||||
if (definition.ArrayLength != 1)
|
if (definition.ArrayLength != 1)
|
||||||
|
|
|
@ -33,9 +33,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
public Dictionary<int, Instruction> LocalMemories { get; } = new();
|
public Dictionary<int, Instruction> LocalMemories { get; } = new();
|
||||||
public Dictionary<int, Instruction> SharedMemories { get; } = new();
|
public Dictionary<int, Instruction> SharedMemories { get; } = new();
|
||||||
|
|
||||||
public Dictionary<int, SamplerType> SamplersTypes { get; } = new();
|
public Dictionary<SetBindingPair, SamplerType> SamplersTypes { get; } = new();
|
||||||
public Dictionary<int, SamplerDeclaration> Samplers { get; } = new();
|
public Dictionary<SetBindingPair, SamplerDeclaration> Samplers { get; } = new();
|
||||||
public Dictionary<int, ImageDeclaration> Images { get; } = new();
|
public Dictionary<SetBindingPair, ImageDeclaration> Images { get; } = new();
|
||||||
|
|
||||||
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
|
public Dictionary<IoDefinition, Instruction> Inputs { get; } = new();
|
||||||
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();
|
public Dictionary<IoDefinition, Instruction> Outputs { get; } = new();
|
||||||
|
@ -98,11 +98,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
Logger = parameters.Logger;
|
Logger = parameters.Logger;
|
||||||
TargetApi = parameters.TargetApi;
|
TargetApi = parameters.TargetApi;
|
||||||
|
|
||||||
AddCapability(Capability.Shader);
|
|
||||||
AddCapability(Capability.Float64);
|
|
||||||
|
|
||||||
SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
|
|
||||||
|
|
||||||
Delegates = new SpirvDelegates(this);
|
Delegates = new SpirvDelegates(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -208,13 +208,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
|
|
||||||
var sampledImageVariable = context.Variable(sampledImageArrayPointerType, StorageClass.UniformConstant);
|
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,
|
imageType,
|
||||||
sampledImageType,
|
sampledImageType,
|
||||||
sampledImagePointerType,
|
sampledImagePointerType,
|
||||||
sampledImageVariable,
|
sampledImageVariable,
|
||||||
sampler.ArrayLength != 1));
|
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.Name(sampledImageVariable, sampler.Name);
|
||||||
context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
context.Decorate(sampledImageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
||||||
|
@ -256,7 +256,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
|
|
||||||
var imageVariable = context.Variable(imageArrayPointerType, StorageClass.UniformConstant);
|
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.Name(imageVariable, image.Name);
|
||||||
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
context.Decorate(imageVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
||||||
|
|
|
@ -602,7 +602,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
return context.Get(type, texOp.GetSource(srcIndex++));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDeclaration declaration = context.Images[texOp.Binding];
|
ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
|
||||||
SpvInstruction image = declaration.Image;
|
SpvInstruction image = declaration.Image;
|
||||||
|
|
||||||
SpvInstruction resultType = context.GetType(componentType);
|
SpvInstruction resultType = context.GetType(componentType);
|
||||||
|
@ -681,7 +681,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
return context.Get(type, texOp.GetSource(srcIndex++));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDeclaration declaration = context.Images[texOp.Binding];
|
ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
|
||||||
SpvInstruction image = declaration.Image;
|
SpvInstruction image = declaration.Image;
|
||||||
|
|
||||||
if (declaration.IsIndexed)
|
if (declaration.IsIndexed)
|
||||||
|
@ -738,7 +738,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
return context.Get(type, texOp.GetSource(srcIndex++));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDeclaration declaration = context.Images[texOp.Binding];
|
ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()];
|
||||||
SpvInstruction image = declaration.Image;
|
SpvInstruction image = declaration.Image;
|
||||||
|
|
||||||
if (declaration.IsIndexed)
|
if (declaration.IsIndexed)
|
||||||
|
@ -837,7 +837,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
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);
|
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
|
||||||
|
|
||||||
int pCount = texOp.Type.GetDimensions();
|
int pCount = texOp.Type.GetDimensions();
|
||||||
|
@ -1161,7 +1161,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
return context.Get(type, texOp.GetSource(srcIndex++));
|
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);
|
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
|
||||||
|
|
||||||
int coordsCount = texOp.Type.GetDimensions();
|
int coordsCount = texOp.Type.GetDimensions();
|
||||||
|
@ -1433,7 +1433,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
|
|
||||||
int srcIndex = 0;
|
int srcIndex = 0;
|
||||||
|
|
||||||
SamplerDeclaration declaration = context.Samplers[texOp.Binding];
|
SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
|
||||||
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
|
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
|
||||||
|
|
||||||
image = context.Image(declaration.ImageType, image);
|
image = context.Image(declaration.ImageType, image);
|
||||||
|
@ -1449,7 +1449,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
|
|
||||||
int srcIndex = 0;
|
int srcIndex = 0;
|
||||||
|
|
||||||
SamplerDeclaration declaration = context.Samplers[texOp.Binding];
|
SamplerDeclaration declaration = context.Samplers[texOp.GetTextureSetAndBinding()];
|
||||||
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
|
SpvInstruction image = GenerateSampledImageLoad(context, texOp, declaration, ref srcIndex);
|
||||||
|
|
||||||
image = context.Image(declaration.ImageType, image);
|
image = context.Image(declaration.ImageType, image);
|
||||||
|
@ -1460,7 +1460,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var type = context.SamplersTypes[texOp.Binding];
|
var type = context.SamplersTypes[texOp.GetTextureSetAndBinding()];
|
||||||
bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
|
bool hasLod = !type.HasFlag(SamplerType.Multisample) && type != SamplerType.TextureBuffer;
|
||||||
|
|
||||||
int dimensions = (type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : type.GetDimensions();
|
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);
|
image = context.Load(declaration.ImageType, image);
|
||||||
|
|
||||||
SamplerDeclaration samplerDeclaration = context.Samplers[texOp.SamplerBinding];
|
SamplerDeclaration samplerDeclaration = context.Samplers[texOp.GetSamplerSetAndBinding()];
|
||||||
|
|
||||||
SpvInstruction sampler = samplerDeclaration.Image;
|
SpvInstruction sampler = samplerDeclaration.Image;
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
|
|
||||||
CodeGenContext context = new(info, parameters, instPool, integerPool);
|
CodeGenContext context = new(info, parameters, instPool, integerPool);
|
||||||
|
|
||||||
|
context.AddCapability(Capability.Shader);
|
||||||
|
|
||||||
|
context.SetMemoryModel(AddressingModel.Logical, MemoryModel.GLSL450);
|
||||||
|
|
||||||
context.AddCapability(Capability.GroupNonUniformBallot);
|
context.AddCapability(Capability.GroupNonUniformBallot);
|
||||||
context.AddCapability(Capability.GroupNonUniformShuffle);
|
context.AddCapability(Capability.GroupNonUniformShuffle);
|
||||||
context.AddCapability(Capability.GroupNonUniformVote);
|
context.AddCapability(Capability.GroupNonUniformVote);
|
||||||
|
@ -51,6 +55,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
context.AddCapability(Capability.ImageQuery);
|
context.AddCapability(Capability.ImageQuery);
|
||||||
context.AddCapability(Capability.SampledBuffer);
|
context.AddCapability(Capability.SampledBuffer);
|
||||||
|
|
||||||
|
if (parameters.HostCapabilities.SupportsShaderFloat64)
|
||||||
|
{
|
||||||
|
context.AddCapability(Capability.Float64);
|
||||||
|
}
|
||||||
|
|
||||||
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
|
if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline)
|
||||||
{
|
{
|
||||||
context.AddCapability(Capability.TransformFeedback);
|
context.AddCapability(Capability.TransformFeedback);
|
||||||
|
@ -58,7 +67,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||||
|
|
||||||
if (parameters.Definitions.Stage == ShaderStage.Fragment)
|
if (parameters.Definitions.Stage == ShaderStage.Fragment)
|
||||||
{
|
{
|
||||||
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)))
|
if (context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.Layer)) ||
|
||||||
|
context.Info.IoDefinitions.Contains(new IoDefinition(StorageKind.Input, IoVariable.PrimitiveId)))
|
||||||
{
|
{
|
||||||
context.AddCapability(Capability.Geometry);
|
context.AddCapability(Capability.Geometry);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,34 +27,43 @@ namespace Ryujinx.Graphics.Shader
|
||||||
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
|
ReadOnlySpan<ulong> GetCode(ulong address, int minimumSize);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries the binding number of a constant buffer.
|
/// Gets the binding number of a constant buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">Constant buffer index</param>
|
/// <param name="index">Constant buffer index</param>
|
||||||
/// <returns>Binding number</returns>
|
/// <returns>Binding number</returns>
|
||||||
int CreateConstantBufferBinding(int index);
|
SetBindingPair CreateConstantBufferBinding(int index);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries the binding number of an image.
|
/// Gets the binding number of an image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="count">For array of images, the number of elements of the array, otherwise it should be 1</param>
|
/// <param name="count">For array of images, the number of elements of the array, otherwise it should be 1</param>
|
||||||
/// <param name="isBuffer">Indicates if the image is a buffer image</param>
|
/// <param name="isBuffer">Indicates if the image is a buffer image</param>
|
||||||
/// <returns>Binding number</returns>
|
/// <returns>Binding number</returns>
|
||||||
int CreateImageBinding(int count, bool isBuffer);
|
SetBindingPair CreateImageBinding(int count, bool isBuffer);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries the binding number of a storage buffer.
|
/// Gets the binding number of a storage buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">Storage buffer index</param>
|
/// <param name="index">Storage buffer index</param>
|
||||||
/// <returns>Binding number</returns>
|
/// <returns>Binding number</returns>
|
||||||
int CreateStorageBufferBinding(int index);
|
SetBindingPair CreateStorageBufferBinding(int index);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries the binding number of a texture.
|
/// Gets the binding number of a texture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="count">For array of textures, the number of elements of the array, otherwise it should be 1</param>
|
/// <param name="count">For array of textures, the number of elements of the array, otherwise it should be 1</param>
|
||||||
/// <param name="isBuffer">Indicates if the texture is a buffer texture</param>
|
/// <param name="isBuffer">Indicates if the texture is a buffer texture</param>
|
||||||
/// <returns>Binding number</returns>
|
/// <returns>Binding number</returns>
|
||||||
int CreateTextureBinding(int count, bool isBuffer);
|
SetBindingPair CreateTextureBinding(int count, bool isBuffer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the set index for an additional set, or -1 if there's no extra set available.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Extra set index, or -1 if not available</returns>
|
||||||
|
int CreateExtraSet()
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries Local Size X for compute shaders.
|
/// Queries Local Size X for compute shaders.
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
if (op.BVal)
|
if (op.BVal)
|
||||||
{
|
{
|
||||||
context.Copy(dest, context.ConditionalSelect(res, ConstF(1), Const(0)));
|
context.Copy(dest, context.ConditionalSelect(res, ConstF(1), ConstF(0)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -278,7 +278,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
flags |= TextureFlags.Bindless;
|
flags |= TextureFlags.Bindless;
|
||||||
}
|
}
|
||||||
|
|
||||||
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageAtomic,
|
Instruction.ImageAtomic,
|
||||||
type,
|
type,
|
||||||
format,
|
format,
|
||||||
|
@ -286,7 +286,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
TextureOperation.DefaultCbufSlot,
|
TextureOperation.DefaultCbufSlot,
|
||||||
imm);
|
imm);
|
||||||
|
|
||||||
Operand res = context.ImageAtomic(type, format, flags, binding, sources);
|
Operand res = context.ImageAtomic(type, format, flags, setAndBinding, sources);
|
||||||
|
|
||||||
context.Copy(d, res);
|
context.Copy(d, res);
|
||||||
}
|
}
|
||||||
|
@ -389,7 +389,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
TextureFormat format = isBindless ? TextureFormat.Unknown : ShaderProperties.GetTextureFormat(context.TranslatorContext.GpuAccessor, handle);
|
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,
|
Instruction.ImageLoad,
|
||||||
type,
|
type,
|
||||||
format,
|
format,
|
||||||
|
@ -397,7 +397,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
TextureOperation.DefaultCbufSlot,
|
TextureOperation.DefaultCbufSlot,
|
||||||
handle);
|
handle);
|
||||||
|
|
||||||
context.ImageLoad(type, format, flags, binding, (int)componentMask, dests, sources);
|
context.ImageLoad(type, format, flags, setAndBinding, (int)componentMask, dests, sources);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -432,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
TextureFormat format = GetTextureFormat(size);
|
TextureFormat format = GetTextureFormat(size);
|
||||||
|
|
||||||
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageLoad,
|
Instruction.ImageLoad,
|
||||||
type,
|
type,
|
||||||
format,
|
format,
|
||||||
|
@ -440,7 +440,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
TextureOperation.DefaultCbufSlot,
|
TextureOperation.DefaultCbufSlot,
|
||||||
handle);
|
handle);
|
||||||
|
|
||||||
context.ImageLoad(type, format, flags, binding, compMask, dests, sources);
|
context.ImageLoad(type, format, flags, setAndBinding, compMask, dests, sources);
|
||||||
|
|
||||||
switch (size)
|
switch (size)
|
||||||
{
|
{
|
||||||
|
@ -552,7 +552,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
flags |= TextureFlags.Bindless;
|
flags |= TextureFlags.Bindless;
|
||||||
}
|
}
|
||||||
|
|
||||||
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageAtomic,
|
Instruction.ImageAtomic,
|
||||||
type,
|
type,
|
||||||
format,
|
format,
|
||||||
|
@ -560,7 +560,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
TextureOperation.DefaultCbufSlot,
|
TextureOperation.DefaultCbufSlot,
|
||||||
imm);
|
imm);
|
||||||
|
|
||||||
context.ImageAtomic(type, format, flags, binding, sources);
|
context.ImageAtomic(type, format, flags, setAndBinding, sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitSust(
|
private static void EmitSust(
|
||||||
|
@ -679,7 +679,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
flags |= TextureFlags.Coherent;
|
flags |= TextureFlags.Coherent;
|
||||||
}
|
}
|
||||||
|
|
||||||
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.ImageStore,
|
Instruction.ImageStore,
|
||||||
type,
|
type,
|
||||||
format,
|
format,
|
||||||
|
@ -687,7 +687,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
TextureOperation.DefaultCbufSlot,
|
TextureOperation.DefaultCbufSlot,
|
||||||
handle);
|
handle);
|
||||||
|
|
||||||
context.ImageStore(type, format, flags, binding, sources);
|
context.ImageStore(type, format, flags, setAndBinding, sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetComponentSizeInBytesLog2(SuatomSize size)
|
private static int GetComponentSizeInBytesLog2(SuatomSize size)
|
||||||
|
|
|
@ -885,7 +885,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
return Register(dest++, RegisterType.Gpr);
|
return Register(dest++, RegisterType.Gpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
int binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
SetBindingPair setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.Lod,
|
Instruction.Lod,
|
||||||
type,
|
type,
|
||||||
TextureFormat.Unknown,
|
TextureFormat.Unknown,
|
||||||
|
@ -913,7 +913,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The instruction component order is the inverse of GLSL's.
|
// 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));
|
res = context.FPMultiply(res, ConstF(256.0f));
|
||||||
|
|
||||||
|
@ -1116,12 +1116,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
|
TextureFlags flags = isBindless ? TextureFlags.Bindless : TextureFlags.None;
|
||||||
int binding;
|
SetBindingPair setAndBinding;
|
||||||
|
|
||||||
switch (query)
|
switch (query)
|
||||||
{
|
{
|
||||||
case TexQuery.TexHeaderDimension:
|
case TexQuery.TexHeaderDimension:
|
||||||
binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.TextureQuerySize,
|
Instruction.TextureQuerySize,
|
||||||
type,
|
type,
|
||||||
TextureFormat.Unknown,
|
TextureFormat.Unknown,
|
||||||
|
@ -1140,13 +1140,13 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Copy(d, context.TextureQuerySize(type, flags, binding, compIndex, sources));
|
context.Copy(d, context.TextureQuerySize(type, flags, setAndBinding, compIndex, sources));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TexQuery.TexHeaderTextureType:
|
case TexQuery.TexHeaderTextureType:
|
||||||
binding = isBindless ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
setAndBinding = isBindless ? default : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.TextureQuerySamples,
|
Instruction.TextureQuerySamples,
|
||||||
type,
|
type,
|
||||||
TextureFormat.Unknown,
|
TextureFormat.Unknown,
|
||||||
|
@ -1171,7 +1171,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
|
|
||||||
if (d != null)
|
if (d != null)
|
||||||
{
|
{
|
||||||
context.Copy(d, context.TextureQuerySamples(type, flags, binding, sources));
|
context.Copy(d, context.TextureQuerySamples(type, flags, setAndBinding, sources));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1191,7 +1191,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
Operand[] dests,
|
Operand[] dests,
|
||||||
Operand[] sources)
|
Operand[] sources)
|
||||||
{
|
{
|
||||||
int binding = flags.HasFlag(TextureFlags.Bindless) ? 0 : context.ResourceManager.GetTextureOrImageBinding(
|
SetBindingPair setAndBinding = flags.HasFlag(TextureFlags.Bindless) ? default : context.ResourceManager.GetTextureOrImageBinding(
|
||||||
Instruction.TextureSample,
|
Instruction.TextureSample,
|
||||||
type,
|
type,
|
||||||
TextureFormat.Unknown,
|
TextureFormat.Unknown,
|
||||||
|
@ -1199,7 +1199,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||||
TextureOperation.DefaultCbufSlot,
|
TextureOperation.DefaultCbufSlot,
|
||||||
handle);
|
handle);
|
||||||
|
|
||||||
context.TextureSample(type, flags, binding, componentMask, dests, sources);
|
context.TextureSample(type, flags, setAndBinding, componentMask, dests, sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SamplerType ConvertSamplerType(TexDim dimensions)
|
private static SamplerType ConvertSamplerType(TexDim dimensions)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue