From d86249cb0add1504c0d3a5c41527cbef43346742 Mon Sep 17 00:00:00 2001 From: MaxLastBreath <136052075+MaxLastBreath@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:33:38 +0300 Subject: [PATCH 1/4] 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 * 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 * 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 * Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs Co-authored-by: gdkchan * Update src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs Co-authored-by: gdkchan --------- Co-authored-by: gdkchan --- src/Ryujinx.Graphics.GAL/Capabilities.cs | 6 +++- .../Image/AutoDeleteCache.cs | 30 +++++++++++++++++-- .../Image/TextureCache.cs | 8 +++++ .../Memory/MemoryManager.cs | 2 ++ src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 3 +- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 22 +++++++++++++- 6 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index a5c6eb5c8e..1eec80e51b 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -71,6 +71,8 @@ namespace Ryujinx.Graphics.GAL public readonly int GatherBiasPrecision; + public readonly ulong MaximumGpuMemory; + public Capabilities( TargetApi api, string vendorName, @@ -131,7 +133,8 @@ namespace Ryujinx.Graphics.GAL int shaderSubgroupSize, int storageBufferOffsetAlignment, int textureBufferOffsetAlignment, - int gatherBiasPrecision) + int gatherBiasPrecision, + ulong maximumGpuMemory) { Api = api; VendorName = vendorName; @@ -193,6 +196,7 @@ namespace Ryujinx.Graphics.GAL StorageBufferOffsetAlignment = storageBufferOffsetAlignment; TextureBufferOffsetAlignment = textureBufferOffsetAlignment; GatherBiasPrecision = gatherBiasPrecision; + MaximumGpuMemory = maximumGpuMemory; } } } diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 5e66a3b543..ad6c1fecb2 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; @@ -46,7 +47,11 @@ namespace Ryujinx.Graphics.Gpu.Image { private const int MinCountForDeletion = 32; 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 _textures; private ulong _totalSize; @@ -56,6 +61,25 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly Dictionary _shortCacheLookup; + /// + /// Initializes the cache, setting the maximum texture capacity for the specified GPU context. + /// + /// + /// If the backend GPU has 0 memory capacity, the cache size defaults to `DefaultTextureSizeCapacity`. + /// + /// The GPU context that the cache belongs to + 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; + } + } + /// /// Creates a new instance of the automatic deletion cache. /// @@ -85,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu.Image texture.CacheNode = _textures.AddLast(texture); if (_textures.Count > MaxCapacity || - (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)) + (_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion)) { RemoveLeastUsedTexture(); } @@ -110,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Image _textures.AddLast(texture.CacheNode); } - if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion) + if (_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion) { RemoveLeastUsedTexture(); } diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index 5a3319b06a..1587e20189 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -68,6 +68,14 @@ namespace Ryujinx.Graphics.Gpu.Image _cache = new AutoDeleteCache(); } + /// + /// Initializes the cache, setting the maximum texture capacity for the specified GPU context. + /// + public void Initialize() + { + _cache.Initialize(_context); + } + /// /// Handles marking of textures written to a memory region being (partially) remapped. /// diff --git a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index 59a940a4f9..d1065431d1 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Memory; +using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Memory; using Ryujinx.Memory.Range; using System; @@ -64,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Memory MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler; MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler; MemoryUnmapped += CounterCache.MemoryUnmappedHandler; + Physical.TextureCache.Initialize(); } /// diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index ba9cd45c67..9fcdf1ad79 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -202,7 +202,8 @@ namespace Ryujinx.Graphics.OpenGL shaderSubgroupSize: Constants.MaxSubgroupSize, storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment, 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 data) diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 33e41ab489..0faaec82a4 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -781,7 +781,26 @@ namespace Ryujinx.Graphics.Vulkan shaderSubgroupSize: (int)Capabilities.SubgroupSize, storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment, 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() @@ -865,6 +884,7 @@ namespace Ryujinx.Graphics.Vulkan private void PrintGpuInformation() { Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})"); + Logger.Notice.Print(LogClass.Gpu, $"GPU Memory: {GetTotalGPUMemory() / (1024 * 1024)} MiB"); } public void Initialize(GraphicsDebugLevel logLevel) From 5dbba07e33e83c9047dcbb701c9655edbbe89086 Mon Sep 17 00:00:00 2001 From: e2dk4r <43293320+e2dk4r@users.noreply.github.com> Date: Sat, 28 Sep 2024 11:44:23 +0300 Subject: [PATCH 2/4] 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 --- src/Ryujinx.SDL2.Common/SDL2Driver.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs index ed6d941908..9827156d03 100644 --- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs +++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -53,6 +53,7 @@ namespace Ryujinx.SDL2.Common return; } + SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); From 7d158acc3b5826a08941d6e8d50d3a3897021bcd Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 30 Sep 2024 11:41:07 -0300 Subject: [PATCH 3/4] Do not try to create a texture pool if shader does not use textures (#7379) --- src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index 98acb6f27d..1230c05805 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -743,7 +743,7 @@ namespace Ryujinx.Graphics.Gpu.Shader constantBufferUsePerStageMask &= ~(1 << index); } - if (checkTextures) + if (checkTextures && _allTextures.Length > 0) { TexturePool pool = channel.TextureManager.GetTexturePool(poolState.TexturePoolGpuVa, poolState.TexturePoolMaximumId); From a2c003501371463fd1f98d2e5a7602ae19c21d7c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 1 Oct 2024 07:30:57 -0300 Subject: [PATCH 4/4] Update audio renderer to REV13: Add support for compressor statistics and volume reset (#7372) * 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 --- .../Renderer/Dsp/Command/CompressorCommand.cs | 45 +++++++++++---- .../Dsp/Command/LimiterCommandVersion1.cs | 46 +++++++-------- .../Dsp/Command/LimiterCommandVersion2.cs | 48 ++++++++-------- .../Parameter/Effect/CompressorParameter.cs | 11 +++- .../Parameter/Effect/CompressorStatistics.cs | 38 +++++++++++++ .../ISplitterDestinationInParameter.cs | 5 ++ .../SplitterDestinationInParameterVersion1.cs | 9 ++- .../SplitterDestinationInParameterVersion2.cs | 9 ++- .../Renderer/Server/BehaviourContext.cs | 19 ++++++- .../Renderer/Server/CommandBuffer.cs | 13 ++++- .../Renderer/Server/CommandGenerator.cs | 16 +++++- .../CommandProcessingTimeEstimatorVersion5.cs | 56 ++++++++++++++----- .../Server/Effect/CompressorEffect.cs | 13 +++++ .../Server/Splitter/SplitterContext.cs | 9 ++- .../Server/Splitter/SplitterDestination.cs | 7 ++- .../Splitter/SplitterDestinationVersion1.cs | 6 +- .../Splitter/SplitterDestinationVersion2.cs | 6 +- src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs | 1 + .../Sdk/Audio/Detail/AudioDevice.cs | 42 ++++++++++++++ .../Renderer/Server/BehaviourContextTests.cs | 41 ++++++++++++++ 20 files changed, 352 insertions(+), 88 deletions(-) create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorStatistics.cs diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs index 09f415d20c..33f61e6a51 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs @@ -1,9 +1,11 @@ using Ryujinx.Audio.Renderer.Dsp.Effect; using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Parameter.Effect; using Ryujinx.Audio.Renderer.Server.Effect; using System; using System.Diagnostics; +using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Dsp.Command { @@ -21,18 +23,20 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command public CompressorParameter Parameter => _parameter; public Memory State { get; } + public Memory ResultState { get; } public ushort[] OutputBufferIndices { get; } public ushort[] InputBufferIndices { get; } public bool IsEffectEnabled { get; } private CompressorParameter _parameter; - public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId) + public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory state, Memory resultState, bool isEnabled, int nodeId) { Enabled = true; NodeId = nodeId; _parameter = parameter; State = state; + ResultState = resultState; IsEffectEnabled = isEnabled; @@ -71,9 +75,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command if (IsEffectEnabled && _parameter.IsChannelCountValid()) { - Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; - Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; - Span channelInput = stackalloc float[Parameter.ChannelCount]; + if (!ResultState.IsEmpty && _parameter.StatisticsReset) + { + ref CompressorStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.Reset(_parameter.ChannelCount); + } + + Span inputBuffers = stackalloc IntPtr[_parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[_parameter.ChannelCount]; + Span channelInput = stackalloc float[_parameter.ChannelCount]; ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage; float unknown4 = state.Unknown4; ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage; @@ -92,7 +103,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command 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 z = 1.0f; @@ -111,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command 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 { @@ -126,7 +138,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command if ((unknown4 - z) <= 0.08f) { - compressionEmaAlpha = Parameter.ReleaseCoefficient; + compressionEmaAlpha = _parameter.ReleaseCoefficient; if ((unknown4 - z) >= -0.08f) { @@ -140,18 +152,31 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command } else { - compressionEmaAlpha = Parameter.AttackCoefficient; + compressionEmaAlpha = _parameter.AttackCoefficient; } 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; } unknown4 = unknown4New; previousCompressionEmaAlpha = compressionEmaAlpha; + + if (!ResultState.IsEmpty) + { + ref CompressorStatistics statistics = ref MemoryMarshal.Cast(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; @@ -161,7 +186,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command } else { - for (int i = 0; i < Parameter.ChannelCount; i++) + for (int i = 0; i < _parameter.ChannelCount; i++) { if (InputBufferIndices[i] != OutputBufferIndices[i]) { diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs index 3ba0b5884d..06e9321997 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs @@ -38,10 +38,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command InputBufferIndices = 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]); - OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]); } } @@ -51,11 +51,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command if (IsEffectEnabled) { - if (Parameter.Status == UsageState.Invalid) + if (_parameter.Status == UsageState.Invalid) { state = new LimiterState(ref _parameter, WorkBuffer); } - else if (Parameter.Status == UsageState.New) + else if (_parameter.Status == UsageState.New) { LimiterState.UpdateParameter(ref _parameter); } @@ -66,56 +66,56 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command 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 inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; - Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span inputBuffers = stackalloc IntPtr[_parameter.ChannelCount]; + Span 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]); 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++) { 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 inputCoefficient = Parameter.ReleaseCoefficient; + float inputCoefficient = _parameter.ReleaseCoefficient; if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) { - inputCoefficient = Parameter.AttackCoefficient; + inputCoefficient = _parameter.AttackCoefficient; } float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); 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) { - outputCoefficient = Parameter.AttackCoefficient; + outputCoefficient = _parameter.AttackCoefficient; } 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; @@ -123,16 +123,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command 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 { - for (int i = 0; i < Parameter.ChannelCount; i++) + for (int i = 0; i < _parameter.ChannelCount; i++) { if (InputBufferIndices[i] != OutputBufferIndices[i]) { diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs index f6e1654dd3..ed0538c061 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs @@ -49,10 +49,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command InputBufferIndices = 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]); - OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]); } } @@ -62,11 +62,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command if (IsEffectEnabled) { - if (Parameter.Status == UsageState.Invalid) + if (_parameter.Status == UsageState.Invalid) { state = new LimiterState(ref _parameter, WorkBuffer); } - else if (Parameter.Status == UsageState.New) + else if (_parameter.Status == UsageState.New) { LimiterState.UpdateParameter(ref _parameter); } @@ -77,63 +77,63 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command 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(ResultState.Span[0].SpecificData)[0]; statistics.Reset(); } - Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; - Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span inputBuffers = stackalloc IntPtr[_parameter.ChannelCount]; + Span 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]); 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++) { 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 inputCoefficient = Parameter.ReleaseCoefficient; + float inputCoefficient = _parameter.ReleaseCoefficient; if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) { - inputCoefficient = Parameter.AttackCoefficient; + inputCoefficient = _parameter.AttackCoefficient; } float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); 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) { - outputCoefficient = Parameter.AttackCoefficient; + outputCoefficient = _parameter.AttackCoefficient; } 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; @@ -141,9 +141,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command 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) @@ -158,7 +158,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command } else { - for (int i = 0; i < Parameter.ChannelCount; i++) + for (int i = 0; i < _parameter.ChannelCount; i++) { if (InputBufferIndices[i] != OutputBufferIndices[i]) { diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs index b403f13703..c00118e49a 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs @@ -90,9 +90,16 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect public bool MakeupGainEnabled; /// - /// Reserved/padding. + /// Indicate if the compressor effect should output statistics. /// - private Array2 _reserved; + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsEnabled; + + /// + /// Indicate to the DSP that the user did a statistics reset. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsReset; /// /// Check if the is valid. diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorStatistics.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorStatistics.cs new file mode 100644 index 0000000000..65335e2d99 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorStatistics.cs @@ -0,0 +1,38 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// Effect result state for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CompressorStatistics + { + /// + /// Maximum input mean value since last reset. + /// + public float MaximumMean; + + /// + /// Minimum output gain since last reset. + /// + public float MinimumGain; + + /// + /// Last processed input sample, per channel. + /// + public Array6 LastSamples; + + /// + /// Reset the statistics. + /// + /// Number of channels to reset. + public void Reset(ushort channelCount) + { + MaximumMean = 0.0f; + MinimumGain = 1.0f; + LastSamples.AsSpan()[..channelCount].Clear(); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs index 807232f208..7ee49f11a6 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/ISplitterDestinationInParameter.cs @@ -28,6 +28,11 @@ namespace Ryujinx.Audio.Renderer.Parameter /// bool IsUsed { get; } + /// + /// Set to true to force resetting the previous mix volumes. + /// + bool ResetPrevVolume { get; } + /// /// Mix buffer volumes. /// diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs index 029c001ea9..f346efcb05 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion1.cs @@ -37,10 +37,16 @@ namespace Ryujinx.Audio.Renderer.Parameter [MarshalAs(UnmanagedType.I1)] public bool IsUsed; + /// + /// Set to true to force resetting the previous mix volumes. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ResetPrevVolume; + /// /// Reserved/padding. /// - private unsafe fixed byte _reserved[3]; + private unsafe fixed byte _reserved[2]; [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] private struct MixArray { } @@ -58,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Parameter readonly Array2 ISplitterDestinationInParameter.BiquadFilters => default; readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; + readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume; /// /// The expected constant of any input header. diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs index 312be8b707..1d867919d4 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameterVersion2.cs @@ -42,10 +42,16 @@ namespace Ryujinx.Audio.Renderer.Parameter [MarshalAs(UnmanagedType.I1)] public bool IsUsed; + /// + /// Set to true to force resetting the previous mix volumes. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ResetPrevVolume; + /// /// Reserved/padding. /// - private unsafe fixed byte _reserved[11]; + private unsafe fixed byte _reserved[10]; [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] private struct MixArray { } @@ -63,6 +69,7 @@ namespace Ryujinx.Audio.Renderer.Parameter readonly Array2 ISplitterDestinationInParameter.BiquadFilters => BiquadFilters; readonly bool ISplitterDestinationInParameter.IsUsed => IsUsed; + readonly bool ISplitterDestinationInParameter.ResetPrevVolume => ResetPrevVolume; /// /// The expected constant of any input header. diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs index 32c7de6cfb..f725eb9f3e 100644 --- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -108,10 +108,18 @@ namespace Ryujinx.Audio.Renderer.Server /// This was added in system update 17.0.0 public const int Revision12 = 12 << 24; + /// + /// REV13: + /// The compressor effect can now output statistics. + /// Splitter destinations now explicitly reset the previous mix volume, instead of doing so on first use. + /// + /// This was added in system update 18.0.0 + public const int Revision13 = 13 << 24; + /// /// Last revision supported by the implementation. /// - public const int LastRevision = Revision12; + public const int LastRevision = Revision13; /// /// Target revision magic supported by the implementation. @@ -384,6 +392,15 @@ namespace Ryujinx.Audio.Renderer.Server return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision12); } + /// + /// Check if the audio renderer should support explicit previous mix volume reset on splitter. + /// + /// True if the audio renderer support explicit previous mix volume reset on splitter + public bool IsSplitterPrevVolumeResetSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision13); + } + /// /// Get the version of the . /// diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs index 702f05462f..4c353b37e1 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs @@ -583,11 +583,20 @@ namespace Ryujinx.Audio.Renderer.Server } } - public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId) + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The compressor parameter. + /// The compressor state. + /// The DSP effect result state. + /// Set to true if the effect should be active. + /// The node id associated to this command. + public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory state, Memory effectResultState, bool isEnabled, int nodeId) { 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); diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs index d798230c1d..0b789537a7 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs @@ -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); + Memory dspResultState; + + if (effect.Parameter.StatisticsEnabled) + { + dspResultState = _effectContext.GetDspStateMemory(effectId); + } + else + { + dspResultState = Memory.Empty; + } + _commandBuffer.GenerateCompressorEffect( bufferOffset, effect.Parameter, effect.State, + dspResultState, effect.IsEnabled, nodeId); } @@ -795,7 +807,7 @@ namespace Ryujinx.Audio.Renderer.Server GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId); break; case EffectType.Compressor: - GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId); + GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId, effectId); break; default: throw new NotImplementedException($"Unsupported effect type {effect.Type}"); diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs index 06f135a886..bc9ba073d6 100644 --- a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs @@ -169,14 +169,28 @@ namespace Ryujinx.Audio.Renderer.Server { if (command.Enabled) { - return command.Parameter.ChannelCount switch + if (command.Parameter.StatisticsEnabled) { - 1 => 34431, - 2 => 44253, - 4 => 63827, - 6 => 83361, - _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), - }; + return command.Parameter.ChannelCount switch + { + 1 => 22100, + 2 => 33211, + 4 => 41587, + 6 => 58819, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + else + { + return command.Parameter.ChannelCount switch + { + 1 => 19052, + 2 => 29852, + 4 => 37904, + 6 => 55020, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } } return command.Parameter.ChannelCount switch @@ -191,14 +205,28 @@ namespace Ryujinx.Audio.Renderer.Server if (command.Enabled) { - return command.Parameter.ChannelCount switch + if (command.Parameter.StatisticsEnabled) { - 1 => 51095, - 2 => 65693, - 4 => 95383, - 6 => 124510, - _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), - }; + return command.Parameter.ChannelCount switch + { + 1 => 32518, + 2 => 49102, + 4 => 61685, + 6 => 87250, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } + else + { + return command.Parameter.ChannelCount switch + { + 1 => 27963, + 2 => 44016, + 4 => 56183, + 6 => 81862, + _ => throw new NotImplementedException($"{command.Parameter.ChannelCount}"), + }; + } } return command.Parameter.ChannelCount switch diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs index eff60e7da8..de0f44e475 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs @@ -62,6 +62,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect UpdateUsageStateForCommandGeneration(); Parameter.Status = UsageState.Enabled; + Parameter.StatisticsReset = false; + } + + public override void InitializeResultState(ref EffectResultState state) + { + ref CompressorStatistics statistics = ref MemoryMarshal.Cast(state.SpecificData)[0]; + + statistics.Reset(Parameter.ChannelCount); + } + + public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) + { + destState = srcState; } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs index a7b82a6bdc..6dddb43158 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -51,6 +51,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// public bool IsBugFixed { get; private set; } + /// + /// If set to true, the previous mix volume is explicitly resetted using the input parameter, instead of implicitly on first use. + /// + public bool IsSplitterPrevVolumeResetSupported { get; private set; } + /// /// Initialize . /// @@ -139,6 +144,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } } + IsSplitterPrevVolumeResetSupported = behaviourContext.IsSplitterPrevVolumeResetSupported(); + SplitterState.InitializeSplitters(splitters.Span); Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed()); @@ -277,7 +284,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { SplitterDestination destination = GetDestination(parameter.Id); - destination.Update(parameter); + destination.Update(parameter, IsSplitterPrevVolumeResetSupported); } return true; diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs index 36dfa5e413..1a46d41fde 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs @@ -184,15 +184,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Update the splitter destination data from user parameter. /// /// The user parameter. - public void Update(in T parameter) where T : ISplitterDestinationInParameter + /// Indicates that the audio renderer revision in use supports explicitly resetting the volume. + public void Update(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter { if (Unsafe.IsNullRef(ref _v2)) { - _v1.Update(parameter); + _v1.Update(parameter, isPrevVolumeResetSupported); } else { - _v2.Update(parameter); + _v2.Update(parameter, isPrevVolumeResetSupported); } } diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs index 5d2b8fb0fe..ce8f33685f 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs @@ -93,7 +93,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Update the from user parameter. /// /// The user parameter. - public void Update(in T parameter) where T : ISplitterDestinationInParameter + /// Indicates that the audio renderer revision in use supports explicitly resetting the volume. + public void Update(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter { Debug.Assert(Id == parameter.Id); @@ -103,7 +104,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter parameter.MixBufferVolume.CopyTo(MixBufferVolume); - if (!IsUsed && parameter.IsUsed) + bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed; + if (resetPrevVolume) { MixBufferVolume.CopyTo(PreviousMixBufferVolume); diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs index f9487909d4..5f96ef3aa5 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs @@ -98,7 +98,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Update the from user parameter. /// /// The user parameter. - public void Update(in T parameter) where T : ISplitterDestinationInParameter + /// Indicates that the audio renderer revision in use supports explicitly resetting the volume. + public void Update(in T parameter, bool isPrevVolumeResetSupported) where T : ISplitterDestinationInParameter { Debug.Assert(Id == parameter.Id); @@ -110,7 +111,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter _biquadFilters = parameter.BiquadFilters; - if (!IsUsed && parameter.IsUsed) + bool resetPrevVolume = isPrevVolumeResetSupported ? parameter.ResetPrevVolume : !IsUsed && parameter.IsUsed; + if (resetPrevVolume) { MixBufferVolume.CopyTo(PreviousMixBufferVolume); diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs index c18bfee9fc..5914a747cb 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs @@ -8,5 +8,6 @@ namespace Ryujinx.Horizon.Sdk.Audio public static Result DeviceNotFound => new(ModuleId, 1); public static Result UnsupportedRevision => new(ModuleId, 2); + public static Result NotImplemented => new(ModuleId, 513); } } diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs index f67ea72988..2d3aa7ba92 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs @@ -233,6 +233,48 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail 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) { if (disposing) diff --git a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs index 3e48a5b4ec..0b0ed7a542 100644 --- a/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs +++ b/src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs @@ -55,6 +55,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -83,6 +84,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -111,6 +113,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -139,6 +142,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -167,6 +171,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -195,6 +200,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -223,6 +229,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -251,6 +258,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -279,6 +287,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsFalse(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -307,6 +316,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsFalse(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -335,6 +345,7 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); Assert.IsFalse(behaviourContext.IsBiquadFilterParameterForSplitterEnabled()); + Assert.IsFalse(behaviourContext.IsSplitterPrevVolumeResetSupported()); Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit()); Assert.AreEqual(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion()); @@ -363,6 +374,36 @@ namespace Ryujinx.Tests.Audio.Renderer.Server Assert.IsTrue(behaviourContext.UseMultiTapBiquadFilterProcessing()); Assert.IsTrue(behaviourContext.IsNewEffectChannelMappingSupported()); 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(5, behaviourContext.GetCommandProcessingTimeEstimatorVersion());