Compare commits
10 commits
ef81658fbd
...
a2c0035013
Author | SHA1 | Date | |
---|---|---|---|
|
a2c0035013 | ||
|
7d158acc3b | ||
|
5dbba07e33 | ||
|
d86249cb0a | ||
|
04d68ca616 | ||
|
050f22977f | ||
|
319507f2a1 | ||
|
d717aef2be | ||
|
24ee8c39f1 | ||
|
73f985d27c |
51 changed files with 582 additions and 187 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -23,7 +23,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Log file
|
label: Log file
|
||||||
description: A log file will help our developers to better diagnose and fix the issue.
|
description: A log file will help our developers to better diagnose and fix the issue.
|
||||||
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. You can drag and drop the log on to the text area
|
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. They can also be accessed by opening Ryujinx, then going to File > Open Logs Folder. You can drag and drop the log on to the text area (do not copy paste).
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||||
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||||
using Ryujinx.Audio.Renderer.Server.Effect;
|
using Ryujinx.Audio.Renderer.Server.Effect;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
{
|
{
|
||||||
|
@ -21,18 +23,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
public CompressorParameter Parameter => _parameter;
|
public CompressorParameter Parameter => _parameter;
|
||||||
public Memory<CompressorState> State { get; }
|
public Memory<CompressorState> State { get; }
|
||||||
|
public Memory<EffectResultState> ResultState { get; }
|
||||||
public ushort[] OutputBufferIndices { get; }
|
public ushort[] OutputBufferIndices { get; }
|
||||||
public ushort[] InputBufferIndices { get; }
|
public ushort[] InputBufferIndices { get; }
|
||||||
public bool IsEffectEnabled { get; }
|
public bool IsEffectEnabled { get; }
|
||||||
|
|
||||||
private CompressorParameter _parameter;
|
private CompressorParameter _parameter;
|
||||||
|
|
||||||
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
|
public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, Memory<EffectResultState> resultState, bool isEnabled, int nodeId)
|
||||||
{
|
{
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
NodeId = nodeId;
|
NodeId = nodeId;
|
||||||
_parameter = parameter;
|
_parameter = parameter;
|
||||||
State = state;
|
State = state;
|
||||||
|
ResultState = resultState;
|
||||||
|
|
||||||
IsEffectEnabled = isEnabled;
|
IsEffectEnabled = isEnabled;
|
||||||
|
|
||||||
|
@ -71,9 +75,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
||||||
{
|
{
|
||||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
if (!ResultState.IsEmpty && _parameter.StatisticsReset)
|
||||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
{
|
||||||
Span<float> channelInput = stackalloc float[Parameter.ChannelCount];
|
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(ResultState.Span[0].SpecificData)[0];
|
||||||
|
|
||||||
|
statistics.Reset(_parameter.ChannelCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
|
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
|
Span<float> channelInput = stackalloc float[_parameter.ChannelCount];
|
||||||
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
|
ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
|
||||||
float unknown4 = state.Unknown4;
|
float unknown4 = state.Unknown4;
|
||||||
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
|
ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
|
||||||
|
@ -92,7 +103,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
|
float mean = FloatingPointHelper.MeanSquare(channelInput);
|
||||||
|
float newMean = inputMovingAverage.Update(mean, _parameter.InputGain);
|
||||||
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
|
float y = FloatingPointHelper.Log10(newMean) * 10.0f;
|
||||||
float z = 1.0f;
|
float z = 1.0f;
|
||||||
|
|
||||||
|
@ -111,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
if (y >= state.Unknown14)
|
if (y >= state.Unknown14)
|
||||||
{
|
{
|
||||||
tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
|
tmpGain = ((1.0f / _parameter.Ratio) - 1.0f) * (y - _parameter.Threshold);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -126,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
if ((unknown4 - z) <= 0.08f)
|
if ((unknown4 - z) <= 0.08f)
|
||||||
{
|
{
|
||||||
compressionEmaAlpha = Parameter.ReleaseCoefficient;
|
compressionEmaAlpha = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if ((unknown4 - z) >= -0.08f)
|
if ((unknown4 - z) >= -0.08f)
|
||||||
{
|
{
|
||||||
|
@ -140,18 +152,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
compressionEmaAlpha = Parameter.AttackCoefficient;
|
compressionEmaAlpha = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
|
float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
|
||||||
|
|
||||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||||
{
|
{
|
||||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
|
*((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
|
||||||
}
|
}
|
||||||
|
|
||||||
unknown4 = unknown4New;
|
unknown4 = unknown4New;
|
||||||
previousCompressionEmaAlpha = compressionEmaAlpha;
|
previousCompressionEmaAlpha = compressionEmaAlpha;
|
||||||
|
|
||||||
|
if (!ResultState.IsEmpty)
|
||||||
|
{
|
||||||
|
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(ResultState.Span[0].SpecificData)[0];
|
||||||
|
|
||||||
|
statistics.MinimumGain = MathF.Min(statistics.MinimumGain, compressionGain * state.OutputGain);
|
||||||
|
statistics.MaximumMean = MathF.Max(statistics.MaximumMean, mean);
|
||||||
|
|
||||||
|
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||||
|
{
|
||||||
|
statistics.LastSamples[channelIndex] = MathF.Abs(channelInput[channelIndex] * (1f / 32768f));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.InputMovingAverage = inputMovingAverage;
|
state.InputMovingAverage = inputMovingAverage;
|
||||||
|
@ -161,7 +186,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||||
{
|
{
|
||||||
|
|
|
@ -38,10 +38,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||||
|
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
|
||||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +51,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
if (IsEffectEnabled)
|
if (IsEffectEnabled)
|
||||||
{
|
{
|
||||||
if (Parameter.Status == UsageState.Invalid)
|
if (_parameter.Status == UsageState.Invalid)
|
||||||
{
|
{
|
||||||
state = new LimiterState(ref _parameter, WorkBuffer);
|
state = new LimiterState(ref _parameter, WorkBuffer);
|
||||||
}
|
}
|
||||||
else if (Parameter.Status == UsageState.New)
|
else if (_parameter.Status == UsageState.New)
|
||||||
{
|
{
|
||||||
LimiterState.UpdateParameter(ref _parameter);
|
LimiterState.UpdateParameter(ref _parameter);
|
||||||
}
|
}
|
||||||
|
@ -66,56 +66,56 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
|
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
|
||||||
{
|
{
|
||||||
Debug.Assert(Parameter.IsChannelCountValid());
|
Debug.Assert(_parameter.IsChannelCountValid());
|
||||||
|
|
||||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
||||||
{
|
{
|
||||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
|
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||||
{
|
{
|
||||||
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
||||||
{
|
{
|
||||||
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||||
|
|
||||||
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
|
float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;
|
||||||
|
|
||||||
float sampleInputMax = Math.Abs(inputSample);
|
float sampleInputMax = Math.Abs(inputSample);
|
||||||
|
|
||||||
float inputCoefficient = Parameter.ReleaseCoefficient;
|
float inputCoefficient = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
||||||
{
|
{
|
||||||
inputCoefficient = Parameter.AttackCoefficient;
|
inputCoefficient = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
||||||
float attenuation = 1.0f;
|
float attenuation = 1.0f;
|
||||||
|
|
||||||
if (detectorValue > Parameter.Threshold)
|
if (detectorValue > _parameter.Threshold)
|
||||||
{
|
{
|
||||||
attenuation = Parameter.Threshold / detectorValue;
|
attenuation = _parameter.Threshold / detectorValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float outputCoefficient = Parameter.ReleaseCoefficient;
|
float outputCoefficient = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
||||||
{
|
{
|
||||||
outputCoefficient = Parameter.AttackCoefficient;
|
outputCoefficient = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
||||||
|
|
||||||
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
||||||
|
|
||||||
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
|
float outputSample = delayedSample * compressionGain * _parameter.OutputGain;
|
||||||
|
|
||||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
||||||
|
|
||||||
|
@ -123,16 +123,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
state.DelayedSampleBufferPosition[channelIndex]++;
|
state.DelayedSampleBufferPosition[channelIndex]++;
|
||||||
|
|
||||||
while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
|
while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
|
||||||
{
|
{
|
||||||
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
|
state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||||
{
|
{
|
||||||
|
|
|
@ -49,10 +49,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||||
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
|
||||||
|
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
|
||||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,11 +62,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
if (IsEffectEnabled)
|
if (IsEffectEnabled)
|
||||||
{
|
{
|
||||||
if (Parameter.Status == UsageState.Invalid)
|
if (_parameter.Status == UsageState.Invalid)
|
||||||
{
|
{
|
||||||
state = new LimiterState(ref _parameter, WorkBuffer);
|
state = new LimiterState(ref _parameter, WorkBuffer);
|
||||||
}
|
}
|
||||||
else if (Parameter.Status == UsageState.New)
|
else if (_parameter.Status == UsageState.New)
|
||||||
{
|
{
|
||||||
LimiterState.UpdateParameter(ref _parameter);
|
LimiterState.UpdateParameter(ref _parameter);
|
||||||
}
|
}
|
||||||
|
@ -77,63 +77,63 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
|
private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
|
||||||
{
|
{
|
||||||
Debug.Assert(Parameter.IsChannelCountValid());
|
Debug.Assert(_parameter.IsChannelCountValid());
|
||||||
|
|
||||||
if (IsEffectEnabled && Parameter.IsChannelCountValid())
|
if (IsEffectEnabled && _parameter.IsChannelCountValid())
|
||||||
{
|
{
|
||||||
if (!ResultState.IsEmpty && Parameter.StatisticsReset)
|
if (!ResultState.IsEmpty && _parameter.StatisticsReset)
|
||||||
{
|
{
|
||||||
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
|
ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
|
||||||
|
|
||||||
statistics.Reset();
|
statistics.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<IntPtr> inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
Span<IntPtr> inputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
Span<IntPtr> outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
|
Span<IntPtr> outputBuffers = stackalloc IntPtr[_parameter.ChannelCount];
|
||||||
|
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
|
||||||
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
|
||||||
{
|
{
|
||||||
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
|
||||||
{
|
{
|
||||||
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
|
||||||
|
|
||||||
float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
|
float inputSample = (rawInputSample / short.MaxValue) * _parameter.InputGain;
|
||||||
|
|
||||||
float sampleInputMax = Math.Abs(inputSample);
|
float sampleInputMax = Math.Abs(inputSample);
|
||||||
|
|
||||||
float inputCoefficient = Parameter.ReleaseCoefficient;
|
float inputCoefficient = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
|
||||||
{
|
{
|
||||||
inputCoefficient = Parameter.AttackCoefficient;
|
inputCoefficient = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
|
||||||
float attenuation = 1.0f;
|
float attenuation = 1.0f;
|
||||||
|
|
||||||
if (detectorValue > Parameter.Threshold)
|
if (detectorValue > _parameter.Threshold)
|
||||||
{
|
{
|
||||||
attenuation = Parameter.Threshold / detectorValue;
|
attenuation = _parameter.Threshold / detectorValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float outputCoefficient = Parameter.ReleaseCoefficient;
|
float outputCoefficient = _parameter.ReleaseCoefficient;
|
||||||
|
|
||||||
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
|
||||||
{
|
{
|
||||||
outputCoefficient = Parameter.AttackCoefficient;
|
outputCoefficient = _parameter.AttackCoefficient;
|
||||||
}
|
}
|
||||||
|
|
||||||
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
|
||||||
|
|
||||||
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * _parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
|
||||||
|
|
||||||
float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
|
float outputSample = delayedSample * compressionGain * _parameter.OutputGain;
|
||||||
|
|
||||||
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
*((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
|
||||||
|
|
||||||
|
@ -141,9 +141,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
|
|
||||||
state.DelayedSampleBufferPosition[channelIndex]++;
|
state.DelayedSampleBufferPosition[channelIndex]++;
|
||||||
|
|
||||||
while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
|
while (state.DelayedSampleBufferPosition[channelIndex] >= _parameter.DelayBufferSampleCountMin)
|
||||||
{
|
{
|
||||||
state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
|
state.DelayedSampleBufferPosition[channelIndex] -= _parameter.DelayBufferSampleCountMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ResultState.IsEmpty)
|
if (!ResultState.IsEmpty)
|
||||||
|
@ -158,7 +158,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Parameter.ChannelCount; i++)
|
for (int i = 0; i < _parameter.ChannelCount; i++)
|
||||||
{
|
{
|
||||||
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
if (InputBufferIndices[i] != OutputBufferIndices[i])
|
||||||
{
|
{
|
||||||
|
|
|
@ -90,9 +90,16 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
|
||||||
public bool MakeupGainEnabled;
|
public bool MakeupGainEnabled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserved/padding.
|
/// Indicate if the compressor effect should output statistics.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Array2<byte> _reserved;
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool StatisticsEnabled;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicate to the DSP that the user did a statistics reset.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool StatisticsReset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the <see cref="ChannelCount"/> is valid.
|
/// Check if the <see cref="ChannelCount"/> is valid.
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Audio.Renderer.Parameter.Effect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Effect result state for <seealso cref="Common.EffectType.Compressor"/>.
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct CompressorStatistics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum input mean value since last reset.
|
||||||
|
/// </summary>
|
||||||
|
public float MaximumMean;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum output gain since last reset.
|
||||||
|
/// </summary>
|
||||||
|
public float MinimumGain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Last processed input sample, per channel.
|
||||||
|
/// </summary>
|
||||||
|
public Array6<float> LastSamples;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset the statistics.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelCount">Number of channels to reset.</param>
|
||||||
|
public void Reset(ushort channelCount)
|
||||||
|
{
|
||||||
|
MaximumMean = 0.0f;
|
||||||
|
MinimumGain = 1.0f;
|
||||||
|
LastSamples.AsSpan()[..channelCount].Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,11 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsUsed { get; }
|
bool IsUsed { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true to force resetting the previous mix volumes.
|
||||||
|
/// </summary>
|
||||||
|
bool ResetPrevVolume { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mix buffer volumes.
|
/// Mix buffer volumes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -37,10 +37,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
public bool IsUsed;
|
public bool IsUsed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true to force resetting the previous mix volumes.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool ResetPrevVolume;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserved/padding.
|
/// Reserved/padding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private unsafe fixed byte _reserved[3];
|
private unsafe fixed byte _reserved[2];
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
||||||
private struct MixArray { }
|
private struct MixArray { }
|
||||||
|
@ -58,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
|
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => default;
|
||||||
|
|
||||||
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
||||||
|
readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The expected constant of any input header.
|
/// The expected constant of any input header.
|
||||||
|
|
|
@ -42,10 +42,16 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
public bool IsUsed;
|
public bool IsUsed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set to true to force resetting the previous mix volumes.
|
||||||
|
/// </summary>
|
||||||
|
[MarshalAs(UnmanagedType.I1)]
|
||||||
|
public bool ResetPrevVolume;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reserved/padding.
|
/// Reserved/padding.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private unsafe fixed byte _reserved[11];
|
private unsafe fixed byte _reserved[10];
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)]
|
||||||
private struct MixArray { }
|
private struct MixArray { }
|
||||||
|
@ -63,6 +69,7 @@ namespace Ryujinx.Audio.Renderer.Parameter
|
||||||
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
|
readonly Array2<BiquadFilterParameter> ISplitterDestinationInParameter.BiquadFilters => BiquadFilters;
|
||||||
|
|
||||||
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed;
|
||||||
|
readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The expected constant of any input header.
|
/// The expected constant of any input header.
|
||||||
|
|
|
@ -108,10 +108,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <remarks>This was added in system update 17.0.0</remarks>
|
/// <remarks>This was added in system update 17.0.0</remarks>
|
||||||
public const int Revision12 = 12 << 24;
|
public const int Revision12 = 12 << 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// REV13:
|
||||||
|
/// The compressor effect can now output statistics.
|
||||||
|
/// Splitter destinations now explicitly reset the previous mix volume, instead of doing so on first use.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This was added in system update 18.0.0</remarks>
|
||||||
|
public const int Revision13 = 13 << 24;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last revision supported by the implementation.
|
/// Last revision supported by the implementation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int LastRevision = Revision12;
|
public const int LastRevision = Revision13;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Target revision magic supported by the implementation.
|
/// Target revision magic supported by the implementation.
|
||||||
|
@ -384,6 +392,15 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the audio renderer should support explicit previous mix volume reset on splitter.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the audio renderer support explicit previous mix volume reset on splitter</returns>
|
||||||
|
public bool IsSplitterPrevVolumeResetSupported()
|
||||||
|
{
|
||||||
|
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision13);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -583,11 +583,20 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, bool isEnabled, int nodeId)
|
/// <summary>
|
||||||
|
/// Generate a new <see cref="CompressorCommand"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bufferOffset">The target buffer offset.</param>
|
||||||
|
/// <param name="parameter">The compressor parameter.</param>
|
||||||
|
/// <param name="state">The compressor state.</param>
|
||||||
|
/// <param name="effectResultState">The DSP effect result state.</param>
|
||||||
|
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||||
|
/// <param name="nodeId">The node id associated to this command.</param>
|
||||||
|
public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory<CompressorState> state, Memory<EffectResultState> effectResultState, bool isEnabled, int nodeId)
|
||||||
{
|
{
|
||||||
if (parameter.IsChannelCountValid())
|
if (parameter.IsChannelCountValid())
|
||||||
{
|
{
|
||||||
CompressorCommand command = new(bufferOffset, parameter, state, isEnabled, nodeId);
|
CompressorCommand command = new(bufferOffset, parameter, state, effectResultState, isEnabled, nodeId);
|
||||||
|
|
||||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||||
|
|
||||||
|
|
|
@ -735,14 +735,26 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId)
|
private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId, int effectId)
|
||||||
{
|
{
|
||||||
Debug.Assert(effect.Type == EffectType.Compressor);
|
Debug.Assert(effect.Type == EffectType.Compressor);
|
||||||
|
|
||||||
|
Memory<EffectResultState> dspResultState;
|
||||||
|
|
||||||
|
if (effect.Parameter.StatisticsEnabled)
|
||||||
|
{
|
||||||
|
dspResultState = _effectContext.GetDspStateMemory(effectId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dspResultState = Memory<EffectResultState>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
_commandBuffer.GenerateCompressorEffect(
|
_commandBuffer.GenerateCompressorEffect(
|
||||||
bufferOffset,
|
bufferOffset,
|
||||||
effect.Parameter,
|
effect.Parameter,
|
||||||
effect.State,
|
effect.State,
|
||||||
|
dspResultState,
|
||||||
effect.IsEnabled,
|
effect.IsEnabled,
|
||||||
nodeId);
|
nodeId);
|
||||||
}
|
}
|
||||||
|
@ -795,7 +807,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
|
GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
|
||||||
break;
|
break;
|
||||||
case EffectType.Compressor:
|
case EffectType.Compressor:
|
||||||
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId);
|
GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId, effectId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
|
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
|
||||||
|
|
|
@ -169,14 +169,28 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
if (command.Enabled)
|
if (command.Enabled)
|
||||||
{
|
{
|
||||||
return command.Parameter.ChannelCount switch
|
if (command.Parameter.StatisticsEnabled)
|
||||||
{
|
{
|
||||||
1 => 34431,
|
return command.Parameter.ChannelCount switch
|
||||||
2 => 44253,
|
{
|
||||||
4 => 63827,
|
1 => 22100,
|
||||||
6 => 83361,
|
2 => 33211,
|
||||||
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
|
4 => 41587,
|
||||||
};
|
6 => 58819,
|
||||||
|
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return command.Parameter.ChannelCount switch
|
||||||
|
{
|
||||||
|
1 => 19052,
|
||||||
|
2 => 29852,
|
||||||
|
4 => 37904,
|
||||||
|
6 => 55020,
|
||||||
|
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return command.Parameter.ChannelCount switch
|
return command.Parameter.ChannelCount switch
|
||||||
|
@ -191,14 +205,28 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
|
|
||||||
if (command.Enabled)
|
if (command.Enabled)
|
||||||
{
|
{
|
||||||
return command.Parameter.ChannelCount switch
|
if (command.Parameter.StatisticsEnabled)
|
||||||
{
|
{
|
||||||
1 => 51095,
|
return command.Parameter.ChannelCount switch
|
||||||
2 => 65693,
|
{
|
||||||
4 => 95383,
|
1 => 32518,
|
||||||
6 => 124510,
|
2 => 49102,
|
||||||
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
|
4 => 61685,
|
||||||
};
|
6 => 87250,
|
||||||
|
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return command.Parameter.ChannelCount switch
|
||||||
|
{
|
||||||
|
1 => 27963,
|
||||||
|
2 => 44016,
|
||||||
|
4 => 56183,
|
||||||
|
6 => 81862,
|
||||||
|
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return command.Parameter.ChannelCount switch
|
return command.Parameter.ChannelCount switch
|
||||||
|
|
|
@ -62,6 +62,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
|
||||||
UpdateUsageStateForCommandGeneration();
|
UpdateUsageStateForCommandGeneration();
|
||||||
|
|
||||||
Parameter.Status = UsageState.Enabled;
|
Parameter.Status = UsageState.Enabled;
|
||||||
|
Parameter.StatisticsReset = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void InitializeResultState(ref EffectResultState state)
|
||||||
|
{
|
||||||
|
ref CompressorStatistics statistics = ref MemoryMarshal.Cast<byte, CompressorStatistics>(state.SpecificData)[0];
|
||||||
|
|
||||||
|
statistics.Reset(Parameter.ChannelCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState)
|
||||||
|
{
|
||||||
|
destState = srcState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsBugFixed { get; private set; }
|
public bool IsBugFixed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set to true, the previous mix volume is explicitly resetted using the input parameter, instead of implicitly on first use.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSplitterPrevVolumeResetSupported { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize <see cref="SplitterContext"/>.
|
/// Initialize <see cref="SplitterContext"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -139,6 +144,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsSplitterPrevVolumeResetSupported = behaviourContext.IsSplitterPrevVolumeResetSupported();
|
||||||
|
|
||||||
SplitterState.InitializeSplitters(splitters.Span);
|
SplitterState.InitializeSplitters(splitters.Span);
|
||||||
|
|
||||||
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
|
Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed());
|
||||||
|
@ -277,7 +284,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
{
|
{
|
||||||
SplitterDestination destination = GetDestination(parameter.Id);
|
SplitterDestination destination = GetDestination(parameter.Id);
|
||||||
|
|
||||||
destination.Update(parameter);
|
destination.Update(parameter, IsSplitterPrevVolumeResetSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -184,15 +184,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// Update the splitter destination data from user parameter.
|
/// 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<T>(in T parameter) where T : ISplitterDestinationInParameter
|
/// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
|
||||||
|
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
|
||||||
{
|
{
|
||||||
if (Unsafe.IsNullRef(ref _v2))
|
if (Unsafe.IsNullRef(ref _v2))
|
||||||
{
|
{
|
||||||
_v1.Update(parameter);
|
_v1.Update(parameter, isPrevVolumeResetSupported);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_v2.Update(parameter);
|
_v2.Update(parameter, isPrevVolumeResetSupported);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
|
/// Update the <see cref="SplitterDestinationVersion1"/> from user parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="parameter">The user parameter.</param>
|
/// <param name="parameter">The user parameter.</param>
|
||||||
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
|
/// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
|
||||||
|
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
|
||||||
{
|
{
|
||||||
Debug.Assert(Id == parameter.Id);
|
Debug.Assert(Id == parameter.Id);
|
||||||
|
|
||||||
|
@ -103,7 +104,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
|
|
||||||
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
|
parameter.MixBufferVolume.CopyTo(MixBufferVolume);
|
||||||
|
|
||||||
if (!IsUsed && parameter.IsUsed)
|
bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
|
||||||
|
if (resetPrevVolume)
|
||||||
{
|
{
|
||||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
/// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
|
/// Update the <see cref="SplitterDestinationVersion2"/> from user parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="parameter">The user parameter.</param>
|
/// <param name="parameter">The user parameter.</param>
|
||||||
public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter
|
/// <param name="isPrevVolumeResetSupported">Indicates that the audio renderer revision in use supports explicitly resetting the volume.</param>
|
||||||
|
public void Update<T>(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter
|
||||||
{
|
{
|
||||||
Debug.Assert(Id == parameter.Id);
|
Debug.Assert(Id == parameter.Id);
|
||||||
|
|
||||||
|
@ -110,7 +111,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter
|
||||||
|
|
||||||
_biquadFilters = parameter.BiquadFilters;
|
_biquadFilters = parameter.BiquadFilters;
|
||||||
|
|
||||||
if (!IsUsed && parameter.IsUsed)
|
bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed;
|
||||||
|
if (resetPrevVolume)
|
||||||
{
|
{
|
||||||
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
MixBufferVolume.CopyTo(PreviousMixBufferVolume);
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,8 @@ namespace Ryujinx.Graphics.GAL
|
||||||
|
|
||||||
public readonly int GatherBiasPrecision;
|
public readonly int GatherBiasPrecision;
|
||||||
|
|
||||||
|
public readonly ulong MaximumGpuMemory;
|
||||||
|
|
||||||
public Capabilities(
|
public Capabilities(
|
||||||
TargetApi api,
|
TargetApi api,
|
||||||
string vendorName,
|
string vendorName,
|
||||||
|
@ -131,7 +133,8 @@ namespace Ryujinx.Graphics.GAL
|
||||||
int shaderSubgroupSize,
|
int shaderSubgroupSize,
|
||||||
int storageBufferOffsetAlignment,
|
int storageBufferOffsetAlignment,
|
||||||
int textureBufferOffsetAlignment,
|
int textureBufferOffsetAlignment,
|
||||||
int gatherBiasPrecision)
|
int gatherBiasPrecision,
|
||||||
|
ulong maximumGpuMemory)
|
||||||
{
|
{
|
||||||
Api = api;
|
Api = api;
|
||||||
VendorName = vendorName;
|
VendorName = vendorName;
|
||||||
|
@ -193,6 +196,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
|
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
|
||||||
TextureBufferOffsetAlignment = textureBufferOffsetAlignment;
|
TextureBufferOffsetAlignment = textureBufferOffsetAlignment;
|
||||||
GatherBiasPrecision = gatherBiasPrecision;
|
GatherBiasPrecision = gatherBiasPrecision;
|
||||||
|
MaximumGpuMemory = maximumGpuMemory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Buffers;
|
using Ryujinx.Common.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL
|
namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
|
@ -18,30 +18,30 @@ namespace Ryujinx.Graphics.GAL
|
||||||
PinnedSpan<byte> GetData(int layer, int level);
|
PinnedSpan<byte> GetData(int layer, int level);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
|
/// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
|
||||||
/// the operation completes.
|
/// the operation completes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">Texture data bytes</param>
|
/// <param name="data">Texture data bytes</param>
|
||||||
void SetData(IMemoryOwner<byte> data);
|
void SetData(MemoryOwner<byte> data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
|
/// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
|
||||||
/// the operation completes.
|
/// the operation completes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">Texture data bytes</param>
|
/// <param name="data">Texture data bytes</param>
|
||||||
/// <param name="layer">Target layer</param>
|
/// <param name="layer">Target layer</param>
|
||||||
/// <param name="level">Target level</param>
|
/// <param name="level">Target level</param>
|
||||||
void SetData(IMemoryOwner<byte> data, int layer, int level);
|
void SetData(MemoryOwner<byte> data, int layer, int level);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the texture data. The data passed as a <see cref="IMemoryOwner{Byte}" /> will be disposed when
|
/// Sets the texture data. The data passed as a <see cref="MemoryOwner{Byte}" /> will be disposed when
|
||||||
/// the operation completes.
|
/// the operation completes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">Texture data bytes</param>
|
/// <param name="data">Texture data bytes</param>
|
||||||
/// <param name="layer">Target layer</param>
|
/// <param name="layer">Target layer</param>
|
||||||
/// <param name="level">Target level</param>
|
/// <param name="level">Target level</param>
|
||||||
/// <param name="region">Target sub-region of the texture to update</param>
|
/// <param name="region">Target sub-region of the texture to update</param>
|
||||||
void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region);
|
void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region);
|
||||||
|
|
||||||
void SetStorage(BufferRange buffer);
|
void SetStorage(BufferRange buffer);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||||
{
|
{
|
||||||
|
@ -8,9 +8,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||||
{
|
{
|
||||||
public readonly CommandType CommandType => CommandType.TextureSetData;
|
public readonly CommandType CommandType => CommandType.TextureSetData;
|
||||||
private TableRef<ThreadedTexture> _texture;
|
private TableRef<ThreadedTexture> _texture;
|
||||||
private TableRef<IMemoryOwner<byte>> _data;
|
private TableRef<MemoryOwner<byte>> _data;
|
||||||
|
|
||||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data)
|
public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data)
|
||||||
{
|
{
|
||||||
_texture = texture;
|
_texture = texture;
|
||||||
_data = data;
|
_data = data;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||||
{
|
{
|
||||||
|
@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||||
{
|
{
|
||||||
public readonly CommandType CommandType => CommandType.TextureSetDataSlice;
|
public readonly CommandType CommandType => CommandType.TextureSetDataSlice;
|
||||||
private TableRef<ThreadedTexture> _texture;
|
private TableRef<ThreadedTexture> _texture;
|
||||||
private TableRef<IMemoryOwner<byte>> _data;
|
private TableRef<MemoryOwner<byte>> _data;
|
||||||
private int _layer;
|
private int _layer;
|
||||||
private int _level;
|
private int _level;
|
||||||
|
|
||||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level)
|
public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data, int layer, int level)
|
||||||
{
|
{
|
||||||
_texture = texture;
|
_texture = texture;
|
||||||
_data = data;
|
_data = data;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||||
{
|
{
|
||||||
|
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
||||||
{
|
{
|
||||||
public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion;
|
public readonly CommandType CommandType => CommandType.TextureSetDataSliceRegion;
|
||||||
private TableRef<ThreadedTexture> _texture;
|
private TableRef<ThreadedTexture> _texture;
|
||||||
private TableRef<IMemoryOwner<byte>> _data;
|
private TableRef<MemoryOwner<byte>> _data;
|
||||||
private int _layer;
|
private int _layer;
|
||||||
private int _level;
|
private int _level;
|
||||||
private Rectangle<int> _region;
|
private Rectangle<int> _region;
|
||||||
|
|
||||||
public void Set(TableRef<ThreadedTexture> texture, TableRef<IMemoryOwner<byte>> data, int layer, int level, Rectangle<int> region)
|
public void Set(TableRef<ThreadedTexture> texture, TableRef<MemoryOwner<byte>> data, int layer, int level, Rectangle<int> region)
|
||||||
{
|
{
|
||||||
_texture = texture;
|
_texture = texture;
|
||||||
_data = data;
|
_data = data;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
using Ryujinx.Graphics.GAL.Multithreading.Commands.Texture;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||||
{
|
{
|
||||||
|
@ -111,21 +111,21 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data)
|
public void SetData(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data));
|
_renderer.New<TextureSetDataCommand>().Set(Ref(this), Ref(data));
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level)
|
public void SetData(MemoryOwner<byte> data, int layer, int level)
|
||||||
{
|
{
|
||||||
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data), layer, level);
|
_renderer.New<TextureSetDataSliceCommand>().Set(Ref(this), Ref(data), layer, level);
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
||||||
{
|
{
|
||||||
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data), layer, level, region);
|
_renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data), layer, level, region);
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.Device;
|
using Ryujinx.Graphics.Device;
|
||||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Texture;
|
using Ryujinx.Graphics.Texture;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -353,7 +353,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||||
|
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
IMemoryOwner<byte> data;
|
MemoryOwner<byte> data;
|
||||||
if (srcLinear)
|
if (srcLinear)
|
||||||
{
|
{
|
||||||
data = LayoutConverter.ConvertLinearStridedToLinear(
|
data = LayoutConverter.ConvertLinearStridedToLinear(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
@ -46,7 +47,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
private const int MinCountForDeletion = 32;
|
private const int MinCountForDeletion = 32;
|
||||||
private const int MaxCapacity = 2048;
|
private const int MaxCapacity = 2048;
|
||||||
private const ulong MaxTextureSizeCapacity = 1024 * 1024 * 1024; // MB;
|
private const ulong MinTextureSizeCapacity = 512 * 1024 * 1024;
|
||||||
|
private const ulong MaxTextureSizeCapacity = 4UL * 1024 * 1024 * 1024;
|
||||||
|
private const ulong DefaultTextureSizeCapacity = 1UL * 1024 * 1024 * 1024;
|
||||||
|
private const float MemoryScaleFactor = 0.50f;
|
||||||
|
private ulong _maxCacheMemoryUsage = 0;
|
||||||
|
|
||||||
private readonly LinkedList<Texture> _textures;
|
private readonly LinkedList<Texture> _textures;
|
||||||
private ulong _totalSize;
|
private ulong _totalSize;
|
||||||
|
@ -56,6 +61,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
private readonly Dictionary<TextureDescriptor, ShortTextureCacheEntry> _shortCacheLookup;
|
private readonly Dictionary<TextureDescriptor, ShortTextureCacheEntry> _shortCacheLookup;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the cache, setting the maximum texture capacity for the specified GPU context.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the backend GPU has 0 memory capacity, the cache size defaults to `DefaultTextureSizeCapacity`.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="context">The GPU context that the cache belongs to</param>
|
||||||
|
public void Initialize(GpuContext context)
|
||||||
|
{
|
||||||
|
var cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor);
|
||||||
|
|
||||||
|
_maxCacheMemoryUsage = Math.Clamp(cacheMemory, MinTextureSizeCapacity, MaxTextureSizeCapacity);
|
||||||
|
|
||||||
|
if (context.Capabilities.MaximumGpuMemory == 0)
|
||||||
|
{
|
||||||
|
_maxCacheMemoryUsage = DefaultTextureSizeCapacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the automatic deletion cache.
|
/// Creates a new instance of the automatic deletion cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -85,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
texture.CacheNode = _textures.AddLast(texture);
|
texture.CacheNode = _textures.AddLast(texture);
|
||||||
|
|
||||||
if (_textures.Count > MaxCapacity ||
|
if (_textures.Count > MaxCapacity ||
|
||||||
(_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion))
|
(_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion))
|
||||||
{
|
{
|
||||||
RemoveLeastUsedTexture();
|
RemoveLeastUsedTexture();
|
||||||
}
|
}
|
||||||
|
@ -110,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
_textures.AddLast(texture.CacheNode);
|
_textures.AddLast(texture.CacheNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
|
if (_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion)
|
||||||
{
|
{
|
||||||
RemoveLeastUsedTexture();
|
RemoveLeastUsedTexture();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the sampler has sRGB conversion enabled, false otherwise.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSrgb { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Host sampler object.
|
/// Host sampler object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -30,6 +35,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="descriptor">The Maxwell sampler descriptor</param>
|
/// <param name="descriptor">The Maxwell sampler descriptor</param>
|
||||||
public Sampler(GpuContext context, SamplerDescriptor descriptor)
|
public Sampler(GpuContext context, SamplerDescriptor descriptor)
|
||||||
{
|
{
|
||||||
|
IsSrgb = descriptor.UnpackSrgb();
|
||||||
|
|
||||||
MinFilter minFilter = descriptor.UnpackMinFilter();
|
MinFilter minFilter = descriptor.UnpackMinFilter();
|
||||||
MagFilter magFilter = descriptor.UnpackMagFilter();
|
MagFilter magFilter = descriptor.UnpackMagFilter();
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
return (CompareOp)(((Word0 >> 10) & 7) + 1);
|
return (CompareOp)(((Word0 >> 10) & 7) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unpacks the sampler sRGB format flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the has sampler is sRGB conversion enabled, false otherwise</returns>
|
||||||
|
public readonly bool UnpackSrgb()
|
||||||
|
{
|
||||||
|
return (Word0 & (1 << 13)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
|
/// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -7,7 +7,6 @@ using Ryujinx.Graphics.Texture.Astc;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -662,7 +661,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IMemoryOwner<byte> result = ConvertToHostCompatibleFormat(data);
|
MemoryOwner<byte> result = ConvertToHostCompatibleFormat(data);
|
||||||
|
|
||||||
if (ScaleFactor != 1f && AllowScaledSetData())
|
if (ScaleFactor != 1f && AllowScaledSetData())
|
||||||
{
|
{
|
||||||
|
@ -685,7 +684,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// Uploads new texture data to the host GPU.
|
/// Uploads new texture data to the host GPU.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">New data</param>
|
/// <param name="data">New data</param>
|
||||||
public void SetData(IMemoryOwner<byte> data)
|
public void SetData(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
BlacklistScale();
|
BlacklistScale();
|
||||||
|
|
||||||
|
@ -704,7 +703,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="data">New data</param>
|
/// <param name="data">New data</param>
|
||||||
/// <param name="layer">Target layer</param>
|
/// <param name="layer">Target layer</param>
|
||||||
/// <param name="level">Target level</param>
|
/// <param name="level">Target level</param>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level)
|
public void SetData(MemoryOwner<byte> data, int layer, int level)
|
||||||
{
|
{
|
||||||
BlacklistScale();
|
BlacklistScale();
|
||||||
|
|
||||||
|
@ -722,7 +721,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="layer">Target layer</param>
|
/// <param name="layer">Target layer</param>
|
||||||
/// <param name="level">Target level</param>
|
/// <param name="level">Target level</param>
|
||||||
/// <param name="region">Target sub-region of the texture to update</param>
|
/// <param name="region">Target sub-region of the texture to update</param>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
||||||
{
|
{
|
||||||
BlacklistScale();
|
BlacklistScale();
|
||||||
|
|
||||||
|
@ -740,7 +739,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="level">Mip level to convert</param>
|
/// <param name="level">Mip level to convert</param>
|
||||||
/// <param name="single">True to convert a single slice</param>
|
/// <param name="single">True to convert a single slice</param>
|
||||||
/// <returns>Converted data</returns>
|
/// <returns>Converted data</returns>
|
||||||
public IMemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
|
public MemoryOwner<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data, int level = 0, bool single = false)
|
||||||
{
|
{
|
||||||
int width = Info.Width;
|
int width = Info.Width;
|
||||||
int height = Info.Height;
|
int height = Info.Height;
|
||||||
|
@ -755,7 +754,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
int sliceDepth = single ? 1 : depth;
|
int sliceDepth = single ? 1 : depth;
|
||||||
|
|
||||||
IMemoryOwner<byte> linear;
|
MemoryOwner<byte> linear;
|
||||||
|
|
||||||
if (Info.IsLinear)
|
if (Info.IsLinear)
|
||||||
{
|
{
|
||||||
|
@ -788,7 +787,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
data);
|
data);
|
||||||
}
|
}
|
||||||
|
|
||||||
IMemoryOwner<byte> result = linear;
|
MemoryOwner<byte> result = linear;
|
||||||
|
|
||||||
// Handle compressed cases not supported by the host:
|
// Handle compressed cases not supported by the host:
|
||||||
// - ASTC is usually not supported on desktop cards.
|
// - ASTC is usually not supported on desktop cards.
|
||||||
|
@ -832,19 +831,19 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
case Format.Etc2RgbaUnorm:
|
case Format.Etc2RgbaUnorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return ETC2Decoder.DecodeRgba(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
return ETC2Decoder.DecodeRgba(result.Span, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
case Format.Etc2RgbPtaSrgb:
|
case Format.Etc2RgbPtaSrgb:
|
||||||
case Format.Etc2RgbPtaUnorm:
|
case Format.Etc2RgbPtaUnorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return ETC2Decoder.DecodePta(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
return ETC2Decoder.DecodePta(result.Span, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
case Format.Etc2RgbSrgb:
|
case Format.Etc2RgbSrgb:
|
||||||
case Format.Etc2RgbUnorm:
|
case Format.Etc2RgbUnorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return ETC2Decoder.DecodeRgb(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
return ETC2Decoder.DecodeRgb(result.Span, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -856,43 +855,43 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
case Format.Bc1RgbaUnorm:
|
case Format.Bc1RgbaUnorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return BCnDecoder.DecodeBC1(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
return BCnDecoder.DecodeBC1(result.Span, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
case Format.Bc2Srgb:
|
case Format.Bc2Srgb:
|
||||||
case Format.Bc2Unorm:
|
case Format.Bc2Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return BCnDecoder.DecodeBC2(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
return BCnDecoder.DecodeBC2(result.Span, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
case Format.Bc3Srgb:
|
case Format.Bc3Srgb:
|
||||||
case Format.Bc3Unorm:
|
case Format.Bc3Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return BCnDecoder.DecodeBC3(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
return BCnDecoder.DecodeBC3(result.Span, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
case Format.Bc4Snorm:
|
case Format.Bc4Snorm:
|
||||||
case Format.Bc4Unorm:
|
case Format.Bc4Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return BCnDecoder.DecodeBC4(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
|
return BCnDecoder.DecodeBC4(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
|
||||||
}
|
}
|
||||||
case Format.Bc5Snorm:
|
case Format.Bc5Snorm:
|
||||||
case Format.Bc5Unorm:
|
case Format.Bc5Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return BCnDecoder.DecodeBC5(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
|
return BCnDecoder.DecodeBC5(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
|
||||||
}
|
}
|
||||||
case Format.Bc6HSfloat:
|
case Format.Bc6HSfloat:
|
||||||
case Format.Bc6HUfloat:
|
case Format.Bc6HUfloat:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return BCnDecoder.DecodeBC6(result.Memory.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
|
return BCnDecoder.DecodeBC6(result.Span, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
|
||||||
}
|
}
|
||||||
case Format.Bc7Srgb:
|
case Format.Bc7Srgb:
|
||||||
case Format.Bc7Unorm:
|
case Format.Bc7Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return BCnDecoder.DecodeBC7(result.Memory.Span, width, height, sliceDepth, levels, layers);
|
return BCnDecoder.DecodeBC7(result.Span, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -900,7 +899,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Memory.Span, width);
|
var converted = PixelConverter.ConvertR4G4ToR4G4B4A4(result.Span, width);
|
||||||
|
|
||||||
if (_context.Capabilities.SupportsR4G4B4A4Format)
|
if (_context.Capabilities.SupportsR4G4B4A4Format)
|
||||||
{
|
{
|
||||||
|
@ -910,7 +909,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
using (converted)
|
using (converted)
|
||||||
{
|
{
|
||||||
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Memory.Span, width);
|
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(converted.Span, width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -921,7 +920,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
|
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -933,24 +932,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
case Format.R5G6B5Unorm:
|
case Format.R5G6B5Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Memory.Span, width);
|
return PixelConverter.ConvertR5G6B5ToR8G8B8A8(result.Span, width);
|
||||||
}
|
}
|
||||||
case Format.B5G5R5A1Unorm:
|
case Format.B5G5R5A1Unorm:
|
||||||
case Format.R5G5B5X1Unorm:
|
case Format.R5G5B5X1Unorm:
|
||||||
case Format.R5G5B5A1Unorm:
|
case Format.R5G5B5A1Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Memory.Span, width, Format == Format.R5G5B5X1Unorm);
|
return PixelConverter.ConvertR5G5B5ToR8G8B8A8(result.Span, width, Format == Format.R5G5B5X1Unorm);
|
||||||
}
|
}
|
||||||
case Format.A1B5G5R5Unorm:
|
case Format.A1B5G5R5Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Memory.Span, width);
|
return PixelConverter.ConvertA1B5G5R5ToR8G8B8A8(result.Span, width);
|
||||||
}
|
}
|
||||||
case Format.R4G4B4A4Unorm:
|
case Format.R4G4B4A4Unorm:
|
||||||
using (result)
|
using (result)
|
||||||
{
|
{
|
||||||
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Memory.Span, width);
|
return PixelConverter.ConvertR4G4B4A4ToR8G8B8A8(result.Span, width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
(TexturePool texturePool, SamplerPool samplerPool) = GetPools();
|
(TexturePool texturePool, SamplerPool samplerPool) = GetPools();
|
||||||
|
|
||||||
return (texturePool.Get(textureId), samplerPool.Get(samplerId));
|
Sampler sampler = samplerPool?.Get(samplerId);
|
||||||
|
|
||||||
|
return (texturePool.Get(textureId, sampler?.IsSrgb ?? true), sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -508,12 +510,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
state.TextureHandle = textureId;
|
state.TextureHandle = textureId;
|
||||||
state.SamplerHandle = samplerId;
|
state.SamplerHandle = samplerId;
|
||||||
|
|
||||||
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture);
|
Sampler sampler = samplerPool?.Get(samplerId);
|
||||||
|
|
||||||
|
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, sampler?.IsSrgb ?? true, out Texture texture);
|
||||||
|
|
||||||
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
|
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
|
||||||
|
|
||||||
Sampler sampler = samplerPool?.Get(samplerId);
|
|
||||||
|
|
||||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
_cache = new AutoDeleteCache();
|
_cache = new AutoDeleteCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the cache, setting the maximum texture capacity for the specified GPU context.
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_cache.Initialize(_context);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles marking of textures written to a memory region being (partially) remapped.
|
/// Handles marking of textures written to a memory region being (partially) remapped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Texture;
|
using Ryujinx.Graphics.Texture;
|
||||||
|
@ -5,7 +6,6 @@ using Ryujinx.Memory;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using Ryujinx.Memory.Tracking;
|
using Ryujinx.Memory.Tracking;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
@ -445,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..];
|
ReadOnlySpan<byte> data = dataSpan[(offset - spanBase)..];
|
||||||
|
|
||||||
IMemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
|
MemoryOwner<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
|
||||||
|
|
||||||
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
|
Storage.SetData(result, info.BaseLayer + layer, info.BaseLevel + level);
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,6 +227,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||||
/// <returns>The texture with the given ID</returns>
|
/// <returns>The texture with the given ID</returns>
|
||||||
public override Texture Get(int id)
|
public override Texture Get(int id)
|
||||||
|
{
|
||||||
|
return Get(id, srgbSampler: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the texture with the given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||||
|
/// <param name="srgbSampler">Whether the texture is being accessed with a sampler that has sRGB conversion enabled</param>
|
||||||
|
/// <returns>The texture with the given ID</returns>
|
||||||
|
public Texture Get(int id, bool srgbSampler)
|
||||||
{
|
{
|
||||||
if ((uint)id >= Items.Length)
|
if ((uint)id >= Items.Length)
|
||||||
{
|
{
|
||||||
|
@ -240,7 +251,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
SynchronizeMemory();
|
SynchronizeMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
GetInternal(id, out Texture texture);
|
GetForBinding(id, srgbSampler, out Texture texture);
|
||||||
|
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
@ -252,9 +263,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// This method assumes that the pool has been manually synchronized before doing binding.
|
/// This method assumes that the pool has been manually synchronized before doing binding.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||||
|
/// <param name="srgbSampler">Whether the texture is being accessed with a sampler that has sRGB conversion enabled</param>
|
||||||
/// <param name="texture">The texture with the given ID</param>
|
/// <param name="texture">The texture with the given ID</param>
|
||||||
/// <returns>The texture descriptor with the given ID</returns>
|
/// <returns>The texture descriptor with the given ID</returns>
|
||||||
public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
|
public ref readonly TextureDescriptor GetForBinding(int id, bool srgbSampler, out Texture texture)
|
||||||
{
|
{
|
||||||
if ((uint)id >= Items.Length)
|
if ((uint)id >= Items.Length)
|
||||||
{
|
{
|
||||||
|
@ -264,6 +276,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
// When getting for binding, assume the pool has already been synchronized.
|
// When getting for binding, assume the pool has already been synchronized.
|
||||||
|
|
||||||
|
if (!srgbSampler)
|
||||||
|
{
|
||||||
|
// If the sampler does not have the sRGB bit enabled, then the texture can't use a sRGB format.
|
||||||
|
ref readonly TextureDescriptor tempDescriptor = ref GetDescriptorRef(id);
|
||||||
|
|
||||||
|
if (tempDescriptor.UnpackSrgb() && FormatTable.TryGetTextureFormat(tempDescriptor.UnpackFormat(), isSrgb: false, out FormatInfo formatInfo))
|
||||||
|
{
|
||||||
|
// Get a view of the texture with the right format.
|
||||||
|
return ref GetForBinding(id, formatInfo, out texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ref GetInternal(id, out texture);
|
return ref GetInternal(id, out texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Graphics.Gpu.Image;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
|
@ -64,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
||||||
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
||||||
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
||||||
|
Physical.TextureCache.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -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 = 7320;
|
private const uint CodeGenVersion = 7353;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
|
|
@ -743,7 +743,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
constantBufferUsePerStageMask &= ~(1 << index);
|
constantBufferUsePerStageMask &= ~(1 << index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkTextures)
|
if (checkTextures && _allTextures.Length > 0)
|
||||||
{
|
{
|
||||||
TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
|
TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.OpenGL.Image
|
namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
{
|
{
|
||||||
|
@ -55,9 +55,9 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data)
|
public void SetData(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
var dataSpan = data.Memory.Span;
|
var dataSpan = data.Span;
|
||||||
|
|
||||||
Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]);
|
Buffer.SetData(_buffer, _bufferOffset, dataSpan[..Math.Min(dataSpan.Length, _bufferSize)]);
|
||||||
|
|
||||||
|
@ -65,13 +65,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level)
|
public void SetData(MemoryOwner<byte> data, int layer, int level)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.OpenGL.Image
|
namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
|
@ -448,13 +448,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetData(IMemoryOwner<byte> data)
|
public void SetData(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
using (data = EnsureDataFormat(data))
|
using (data = EnsureDataFormat(data))
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
var dataSpan = data.Memory.Span;
|
var dataSpan = data.Span;
|
||||||
fixed (byte* ptr = dataSpan)
|
fixed (byte* ptr = dataSpan)
|
||||||
{
|
{
|
||||||
ReadFrom((IntPtr)ptr, dataSpan.Length);
|
ReadFrom((IntPtr)ptr, dataSpan.Length);
|
||||||
|
@ -463,13 +463,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level)
|
public void SetData(MemoryOwner<byte> data, int layer, int level)
|
||||||
{
|
{
|
||||||
using (data = EnsureDataFormat(data))
|
using (data = EnsureDataFormat(data))
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = data.Memory.Span)
|
fixed (byte* ptr = data.Span)
|
||||||
{
|
{
|
||||||
int width = Math.Max(Info.Width >> level, 1);
|
int width = Math.Max(Info.Width >> level, 1);
|
||||||
int height = Math.Max(Info.Height >> level, 1);
|
int height = Math.Max(Info.Height >> level, 1);
|
||||||
|
@ -480,7 +480,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
||||||
{
|
{
|
||||||
using (data = EnsureDataFormat(data))
|
using (data = EnsureDataFormat(data))
|
||||||
{
|
{
|
||||||
|
@ -489,7 +489,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = data.Memory.Span)
|
fixed (byte* ptr = data.Span)
|
||||||
{
|
{
|
||||||
ReadFrom2D(
|
ReadFrom2D(
|
||||||
(IntPtr)ptr,
|
(IntPtr)ptr,
|
||||||
|
@ -522,13 +522,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||||
ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
|
ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMemoryOwner<byte> EnsureDataFormat(IMemoryOwner<byte> data)
|
private MemoryOwner<byte> EnsureDataFormat(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
if (Format == Format.S8UintD24Unorm)
|
if (Format == Format.S8UintD24Unorm)
|
||||||
{
|
{
|
||||||
using (data)
|
using (data)
|
||||||
{
|
{
|
||||||
return FormatConverter.ConvertS8D24ToD24S8(data.Memory.Span);
|
return FormatConverter.ConvertS8D24ToD24S8(data.Span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -202,7 +202,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
shaderSubgroupSize: Constants.MaxSubgroupSize,
|
shaderSubgroupSize: Constants.MaxSubgroupSize,
|
||||||
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
|
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
|
||||||
textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment,
|
textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment,
|
||||||
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan.
|
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0, // Precision is 8 for these vendors on Vulkan.
|
||||||
|
maximumGpuMemory: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
|
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
|
||||||
|
|
|
@ -138,6 +138,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||||
// Ensure that conditions met for that branch are also met for the current one.
|
// Ensure that conditions met for that branch are also met for the current one.
|
||||||
// Prefer the latest sources for the phi node.
|
// Prefer the latest sources for the phi node.
|
||||||
|
|
||||||
|
int undefCount = 0;
|
||||||
|
|
||||||
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
|
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
BasicBlock phiBlock = phiNode.GetBlock(i);
|
BasicBlock phiBlock = phiNode.GetBlock(i);
|
||||||
|
@ -159,6 +161,26 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (phiSource.Type == OperandType.Undefined)
|
||||||
|
{
|
||||||
|
undefCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all sources but one are undefined, we can assume that the one
|
||||||
|
// that is not undefined is the right one.
|
||||||
|
|
||||||
|
if (undefCount == phiNode.SourcesCount - 1)
|
||||||
|
{
|
||||||
|
for (int i = phiNode.SourcesCount - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
Operand phiSource = phiNode.GetSource(i);
|
||||||
|
|
||||||
|
if (phiSource.Type != OperandType.Undefined)
|
||||||
|
{
|
||||||
|
return phiSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
|
|
||||||
if (stage == ShaderStage.Vertex)
|
if (stage == ShaderStage.Vertex)
|
||||||
{
|
{
|
||||||
InitializePositionOutput(context);
|
InitializeVertexOutputs(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 usedAttributes = context.TranslatorContext.AttributeUsage.NextInputAttributesComponents;
|
UInt128 usedAttributes = context.TranslatorContext.AttributeUsage.NextInputAttributesComponents;
|
||||||
|
@ -236,12 +236,20 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitializePositionOutput(EmitterContext context)
|
private static void InitializeVertexOutputs(EmitterContext context)
|
||||||
{
|
{
|
||||||
for (int c = 0; c < 4; c++)
|
for (int c = 0; c < 4; c++)
|
||||||
{
|
{
|
||||||
context.Store(StorageKind.Output, IoVariable.Position, null, Const(c), ConstF(c == 3 ? 1f : 0f));
|
context.Store(StorageKind.Output, IoVariable.Position, null, Const(c), ConstF(c == 3 ? 1f : 0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Program.ClipDistancesWritten != 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
context.Store(StorageKind.Output, IoVariable.ClipDistance, null, Const(i), ConstF(0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitializeOutput(EmitterContext context, int location, bool perPatch)
|
private static void InitializeOutput(EmitterContext context, int location, bool perPatch)
|
||||||
|
|
|
@ -636,9 +636,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
var oldStencilTestEnable = _newState.StencilTestEnable;
|
var oldStencilTestEnable = _newState.StencilTestEnable;
|
||||||
var oldDepthTestEnable = _newState.DepthTestEnable;
|
var oldDepthTestEnable = _newState.DepthTestEnable;
|
||||||
var oldDepthWriteEnable = _newState.DepthWriteEnable;
|
var oldDepthWriteEnable = _newState.DepthWriteEnable;
|
||||||
var oldTopology = _newState.Topology;
|
|
||||||
var oldViewports = DynamicState.Viewports;
|
var oldViewports = DynamicState.Viewports;
|
||||||
var oldViewportsCount = _newState.ViewportsCount;
|
var oldViewportsCount = _newState.ViewportsCount;
|
||||||
|
var oldTopology = _topology;
|
||||||
|
|
||||||
_newState.CullMode = CullModeFlags.None;
|
_newState.CullMode = CullModeFlags.None;
|
||||||
_newState.StencilTestEnable = false;
|
_newState.StencilTestEnable = false;
|
||||||
|
@ -658,7 +658,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_newState.StencilTestEnable = oldStencilTestEnable;
|
_newState.StencilTestEnable = oldStencilTestEnable;
|
||||||
_newState.DepthTestEnable = oldDepthTestEnable;
|
_newState.DepthTestEnable = oldDepthTestEnable;
|
||||||
_newState.DepthWriteEnable = oldDepthWriteEnable;
|
_newState.DepthWriteEnable = oldDepthWriteEnable;
|
||||||
_newState.Topology = oldTopology;
|
SetPrimitiveTopology(oldTopology);
|
||||||
|
|
||||||
DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
|
DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Format = Ryujinx.Graphics.GAL.Format;
|
using Format = Ryujinx.Graphics.GAL.Format;
|
||||||
using VkFormat = Silk.NET.Vulkan.Format;
|
using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
|
@ -84,20 +84,20 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data)
|
public void SetData(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
_gd.SetBufferData(_bufferHandle, _offset, data.Memory.Span);
|
_gd.SetBufferData(_bufferHandle, _offset, data.Span);
|
||||||
data.Dispose();
|
data.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level)
|
public void SetData(MemoryOwner<byte> data, int layer, int level)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -746,23 +746,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data)
|
public void SetData(MemoryOwner<byte> data)
|
||||||
{
|
{
|
||||||
SetData(data.Memory.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
|
SetData(data.Span, 0, 0, Info.GetLayers(), Info.Levels, singleSlice: false);
|
||||||
data.Dispose();
|
data.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level)
|
public void SetData(MemoryOwner<byte> data, int layer, int level)
|
||||||
{
|
{
|
||||||
SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true);
|
SetData(data.Span, layer, level, 1, 1, singleSlice: true);
|
||||||
data.Dispose();
|
data.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetData(IMemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
public void SetData(MemoryOwner<byte> data, int layer, int level, Rectangle<int> region)
|
||||||
{
|
{
|
||||||
SetData(data.Memory.Span, layer, level, 1, 1, singleSlice: true, region);
|
SetData(data.Span, layer, level, 1, 1, singleSlice: true, region);
|
||||||
data.Dispose();
|
data.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -781,7 +781,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
shaderSubgroupSize: (int)Capabilities.SubgroupSize,
|
shaderSubgroupSize: (int)Capabilities.SubgroupSize,
|
||||||
storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
|
storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
|
||||||
textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment,
|
textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment,
|
||||||
gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0);
|
gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0,
|
||||||
|
maximumGpuMemory: GetTotalGPUMemory());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong GetTotalGPUMemory()
|
||||||
|
{
|
||||||
|
ulong totalMemory = 0;
|
||||||
|
|
||||||
|
Api.GetPhysicalDeviceMemoryProperties(_physicalDevice.PhysicalDevice, out PhysicalDeviceMemoryProperties memoryProperties);
|
||||||
|
|
||||||
|
for (int i = 0; i < memoryProperties.MemoryHeapCount; i++)
|
||||||
|
{
|
||||||
|
var heap = memoryProperties.MemoryHeaps[i];
|
||||||
|
if ((heap.Flags & MemoryHeapFlags.DeviceLocalBit) == MemoryHeapFlags.DeviceLocalBit)
|
||||||
|
{
|
||||||
|
totalMemory += heap.Size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HardwareInfo GetHardwareInfo()
|
public HardwareInfo GetHardwareInfo()
|
||||||
|
@ -865,6 +884,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private void PrintGpuInformation()
|
private void PrintGpuInformation()
|
||||||
{
|
{
|
||||||
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
|
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
|
||||||
|
Logger.Notice.Print(LogClass.Gpu, $"GPU Memory: {GetTotalGPUMemory() / (1024 * 1024)} MiB");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(GraphicsDebugLevel logLevel)
|
public void Initialize(GraphicsDebugLevel logLevel)
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||||
{
|
{
|
||||||
private readonly MemoryOwner<byte> _rawDataOwner;
|
private readonly MemoryOwner<byte> _rawDataOwner;
|
||||||
|
|
||||||
private Span<byte> Raw => _rawDataOwner.Memory.Span;
|
private Span<byte> Raw => _rawDataOwner.Span;
|
||||||
|
|
||||||
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(Raw)[0];
|
private ref ParcelHeader Header => ref MemoryMarshal.Cast<byte, ParcelHeader>(Raw)[0];
|
||||||
|
|
||||||
|
|
|
@ -8,5 +8,6 @@ namespace Ryujinx.Horizon.Sdk.Audio
|
||||||
|
|
||||||
public static Result DeviceNotFound => new(ModuleId, 1);
|
public static Result DeviceNotFound => new(ModuleId, 1);
|
||||||
public static Result UnsupportedRevision => new(ModuleId, 2);
|
public static Result UnsupportedRevision => new(ModuleId, 2);
|
||||||
|
public static Result NotImplemented => new(ModuleId, 513);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,6 +233,48 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CmifCommand(15)] // 17.0.0+
|
||||||
|
public Result AcquireAudioOutputDeviceNotification([CopyHandle] out int eventHandle, ulong deviceId)
|
||||||
|
{
|
||||||
|
eventHandle = 0;
|
||||||
|
|
||||||
|
return AudioResult.NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(16)] // 17.0.0+
|
||||||
|
public Result ReleaseAudioOutputDeviceNotification(ulong deviceId)
|
||||||
|
{
|
||||||
|
return AudioResult.NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(17)] // 17.0.0+
|
||||||
|
public Result AcquireAudioInputDeviceNotification([CopyHandle] out int eventHandle, ulong deviceId)
|
||||||
|
{
|
||||||
|
eventHandle = 0;
|
||||||
|
|
||||||
|
return AudioResult.NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(18)] // 17.0.0+
|
||||||
|
public Result ReleaseAudioInputDeviceNotification(ulong deviceId)
|
||||||
|
{
|
||||||
|
return AudioResult.NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(19)] // 18.0.0+
|
||||||
|
public Result SetAudioDeviceOutputVolumeAutoTuneEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
return AudioResult.NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(20)] // 18.0.0+
|
||||||
|
public Result IsAudioDeviceOutputVolumeAutoTuneEnabled(out bool enabled)
|
||||||
|
{
|
||||||
|
enabled = false;
|
||||||
|
|
||||||
|
return AudioResult.NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace Ryujinx.Memory
|
namespace Ryujinx.Memory
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@ namespace Ryujinx.Memory
|
||||||
{
|
{
|
||||||
private readonly IWritableBlock _block;
|
private readonly IWritableBlock _block;
|
||||||
private readonly ulong _va;
|
private readonly ulong _va;
|
||||||
private readonly IMemoryOwner<byte> _memoryOwner;
|
private readonly MemoryOwner<byte> _memoryOwner;
|
||||||
private readonly bool _tracked;
|
private readonly bool _tracked;
|
||||||
|
|
||||||
private bool NeedsWriteback => _block != null;
|
private bool NeedsWriteback => _block != null;
|
||||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Memory
|
||||||
Memory = memory;
|
Memory = memory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WritableRegion(IWritableBlock block, ulong va, IMemoryOwner<byte> memoryOwner, bool tracked = false)
|
public WritableRegion(IWritableBlock block, ulong va, MemoryOwner<byte> memoryOwner, bool tracked = false)
|
||||||
: this(block, va, memoryOwner.Memory, tracked)
|
: this(block, va, memoryOwner.Memory, tracked)
|
||||||
{
|
{
|
||||||
_memoryOwner = memoryOwner;
|
_memoryOwner = memoryOwner;
|
||||||
|
|
|
@ -53,6 +53,7 @@ namespace Ryujinx.SDL2.Common
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx");
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||||
|
|
|
@ -55,6 +55,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -83,6 +84,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -111,6 +113,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -139,6 +142,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -167,6 +171,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -195,6 +200,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -223,6 +229,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -251,6 +258,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -279,6 +287,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -307,6 +316,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -335,6 +345,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
@ -363,6 +374,36 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
|
||||||
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
|
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
|
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRevision13()
|
||||||
|
{
|
||||||
|
BehaviourContext behaviourContext = new();
|
||||||
|
|
||||||
|
behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision13);
|
||||||
|
|
||||||
|
Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
|
||||||
|
Assert.IsTrue(behaviourContext.IsSplitterSupported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
|
||||||
|
Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
|
||||||
|
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
|
||||||
|
Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing());
|
||||||
|
Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported());
|
||||||
|
Assert.IsTrue(behaviourContext.IsBiquadFilterParameterForSplitterEnabled());
|
||||||
|
Assert.IsTrue(behaviourContext.IsSplitterPrevVolumeResetSupported());
|
||||||
|
|
||||||
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
|
||||||
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
|
||||||
|
|
Reference in a new issue