0
0
Fork 0

Compare commits

...

10 commits

Author SHA1 Message Date
gdkchan
a2c0035013
Update audio renderer to REV13: Add support for compressor statistics and volume reset (#7372)
Some checks failed
Release job / Create tag (push) Has been cancelled
Release job / Release for linux-arm64 (push) Has been cancelled
Release job / Release for linux-x64 (push) Has been cancelled
Release job / Release for win-x64 (push) Has been cancelled
Release job / Release MacOS universal (push) Has been cancelled
Release job / flatpak_release (push) Has been cancelled
* Update audio renderer to REV13: Add support for compressor statistics and volume reset

* XML docs

* Disable stats reset

* Wrong comment

* Fix more XML docs

* PR feedback
2024-10-01 11:30:57 +01:00
gdkchan
7d158acc3b
Do not try to create a texture pool if shader does not use textures (#7379) 2024-09-30 11:41:07 -03:00
e2dk4r
5dbba07e33
sdl: set app name (#7370)
Ryujinx was not hinting application name, so on some platforms (e.g.
Linux) volume control shows Ryujinx as 'SDL Application'. This can cause
confusion.

This commit fixes name in volume control applets on some platforms.

see: https://wiki.libsdl.org/SDL2/SDL_HINT_APP_NAME
2024-09-28 10:44:23 +02:00
MaxLastBreath
d86249cb0a
Convert MaxTextureCacheCapacity to Dynamic MaxTextureCacheCapacity for High Resolution Mod support. (#7307)
* Add Texture Size Capacity and 8GB Dram Build

* Update AutoDeleteCache.cs

* Dynamic Texture Cache (WIP)

* Change to float Multiplier, in-case it needs fine-tuning.

* Delete src/src.sln

* Update AutoDeleteCache.cs

* Format

* Fix Formatting

* Add DefaultTextureSizeCapacity and MemoryScaleFactor

- Also remove redundant New Lines

* Fix 4GB dram crashing

* Format newline

* Refractor

- Added Initialize() function to TextureCache and AutoDeleteCache
- Removed GetMaxTextureCapacity() function and instead added _maxCacheMemoryUsage
- Added private const MaxTextureSizeCapacity to AutoDelete Cache
- Added TextureCache.Initialize() to MemoryManager in order to fetch MaxGpuMemory at the right time.
- Moved and Changed Logger.Info for Gpu Memory to Logger.Notice and Moved it to PrintGpuInformation function.
- Opted to use a ternary operator for the Initialize function, I think it looks cleaner than bunch of if statements.

* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* maxMemory to CacheMemory, use Clamp instead of Ternary. Changed MinTextureCapacity 1GiB to 512 MiB

* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Format comment

* comment context

* Increase TextureSize capacity for OpenGL back to 1024

- Added a new const ulong for OpenGLTextureSizeCapacity

* Fix changes from last commit.

* Adjust last OpenGL changes.

* Remove garbage VSC file

* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

---------

Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-09-26 14:33:38 -03:00
riperiperi
04d68ca616
GPU: Ensure all clip distances are initialized when used (#7363)
* GPU: Ensure all clip distances are initialized when used

* Shader cache version
2024-09-26 14:19:12 -03:00
Jason Youngberg
050f22977f
Update bug_report.yml to provide better instructions for finding log file (#7333) 2024-09-24 11:10:36 +02:00
gdkchan
319507f2a1
Fix quads draws after DrawTexture on Vulkan (#7336) 2024-09-22 19:36:53 -03:00
gdkchan
d717aef2be
Shader: Assume the only remaining source is the right one when all others are undefined (#7331)
* Shader: Assume the only remaining source is the right one when all other are undefined

* Shader cache version bump

* Improve comment
2024-09-19 21:23:09 -03:00
gdkchan
24ee8c39f1
Add support for sampler sRGB disable (#7312) 2024-09-19 14:38:30 -03:00
jhorv
73f985d27c
Replace passing by IMemoryOwner<byte> with passing by concrete MemoryOwner<byte> (#7171)
* refactor(perf): pass MemoryOwner<byte> around as itself rather than IMemoryOwner<byte>

* fix(perf): get span via MemoryOwner<byte>.Span property instead of through Memory property

* fix: incorrect comment change
2024-09-18 23:00:54 -03:00
51 changed files with 582 additions and 187 deletions

View file

@ -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

View file

@ -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])
{ {

View file

@ -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])
{ {

View file

@ -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])
{ {

View file

@ -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.

View file

@ -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();
}
}
}

View file

@ -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>

View file

@ -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.

View file

@ -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.

View file

@ -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>

View file

@ -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);

View file

@ -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}");

View file

@ -168,16 +168,30 @@ namespace Ryujinx.Audio.Renderer.Server
if (SampleCount == 160) if (SampleCount == 160)
{ {
if (command.Enabled) if (command.Enabled)
{
if (command.Parameter.StatisticsEnabled)
{ {
return command.Parameter.ChannelCount switch return command.Parameter.ChannelCount switch
{ {
1 => 34431, 1 => 22100,
2 => 44253, 2 => 33211,
4 => 63827, 4 => 41587,
6 => 83361, 6 => 58819,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), _ => 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
{ {
@ -190,16 +204,30 @@ namespace Ryujinx.Audio.Renderer.Server
} }
if (command.Enabled) if (command.Enabled)
{
if (command.Parameter.StatisticsEnabled)
{ {
return command.Parameter.ChannelCount switch return command.Parameter.ChannelCount switch
{ {
1 => 51095, 1 => 32518,
2 => 65693, 2 => 49102,
4 => 95383, 4 => 61685,
6 => 124510, 6 => 87250,
_ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), _ => 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
{ {

View file

@ -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;
} }
} }
} }

View file

@ -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;

View file

@ -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);
} }
} }

View file

@ -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);

View file

@ -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);

View file

@ -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;
} }
} }
} }

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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(

View file

@ -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();
} }

View file

@ -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();

View file

@ -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>

View file

@ -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);
} }
} }
} }

View file

@ -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);

View file

@ -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>

View file

@ -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);
} }

View file

@ -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);
} }

View file

@ -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>

View file

@ -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";

View file

@ -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);

View file

@ -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();
} }

View file

@ -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);
} }
} }

View file

@ -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)

View file

@ -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;
}
}
} }
} }

View file

@ -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)

View file

@ -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);

View file

@ -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();
} }

View file

@ -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();
} }

View file

@ -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)

View file

@ -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];

View file

@ -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);
} }
} }

View file

@ -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)

View file

@ -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;

View file

@ -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");

View file

@ -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());