From 06b83a58a6d60063cd5fad934aa7f3a75f0ce040 Mon Sep 17 00:00:00 2001 From: Maufeat Date: Sat, 13 Dec 2025 19:44:41 +0100 Subject: [PATCH] [audio] correct biquad filter v2 parameters (#3142) We had the same struct for v1 and v2 - this was tested only with MP4, should output correct sounds now and boot it. Co-authored-by: MaranBr Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3142 Reviewed-by: MaranBr Reviewed-by: crueter Co-authored-by: Maufeat Co-committed-by: Maufeat --- .../renderer/command/command_buffer.cpp | 56 ++++++++++++++++--- .../renderer/command/command_generator.cpp | 54 ++++++++++++++---- .../renderer/effect/biquad_filter.cpp | 23 ++++++-- .../renderer/effect/biquad_filter.h | 13 +++-- .../renderer_opengl/maxwell_to_gl.h | 10 +++- .../renderer_vulkan/maxwell_to_vk.cpp | 11 ++-- .../renderer_vulkan/vk_rasterizer.cpp | 3 - 7 files changed, 129 insertions(+), 41 deletions(-) diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp index a0a574fc64..804aba438d 100644 --- a/src/audio_core/renderer/command/command_buffer.cpp +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -21,6 +21,15 @@ namespace AudioCore::Renderer { +namespace { +constexpr f32 BiquadParameterFixedScaleQ14 = 16384.0f; // 1 << 14 + +[[nodiscard]] inline s16 ToQ14Clamped(f32 v) { + const f32 scaled = std::clamp(v * BiquadParameterFixedScaleQ14, -32768.0f, 32767.0f); + return static_cast(scaled); +} +} // namespace + template T& CommandBuffer::GenerateStart(const s32 node_id) { if (size + sizeof(T) >= command_list.size_bytes()) { @@ -259,18 +268,42 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas const bool use_float_processing) { auto& cmd{GenerateStart(node_id)}; - const auto& parameter{ - *reinterpret_cast(effect_info.GetParameter())}; const auto state{reinterpret_cast( effect_info.GetStateBuffer() + channel * sizeof(VoiceState::BiquadFilterState))}; - cmd.input = buffer_offset + parameter.inputs[channel]; - cmd.output = buffer_offset + parameter.outputs[channel]; + if (behavior->IsEffectInfoVersion2Supported()) { + const auto& p{ + *reinterpret_cast(effect_info.GetParameter())}; - cmd.biquad.b = parameter.b; - cmd.biquad.a = parameter.a; + if (!IsChannelCountValid(p.channel_count) || channel < 0 || channel >= p.channel_count) { + return; + } + + cmd.input = buffer_offset + p.inputs[channel]; + cmd.output = buffer_offset + p.outputs[channel]; + + // Convert float coefficients to Q2.14 fixed-point as expected by the legacy DSP path. + cmd.biquad.b[0] = ToQ14Clamped(p.b[0]); + cmd.biquad.b[1] = ToQ14Clamped(p.b[1]); + cmd.biquad.b[2] = ToQ14Clamped(p.b[2]); + cmd.biquad.a[0] = ToQ14Clamped(p.a[0]); + cmd.biquad.a[1] = ToQ14Clamped(p.a[1]); + } else { + const auto& p{ + *reinterpret_cast(effect_info.GetParameter())}; - // Effects use legacy fixed-point format + if (!IsChannelCountValid(p.channel_count) || channel < 0 || channel >= p.channel_count) { + return; + } + + cmd.input = buffer_offset + p.inputs[channel]; + cmd.output = buffer_offset + p.outputs[channel]; + + cmd.biquad.b = p.b; + cmd.biquad.a = p.a; + } + + // Effects always use the fixed-point coefficient path on the DSP. cmd.use_float_coefficients = false; cmd.state = memory_pool->Translate(CpuAddr(state), @@ -595,6 +628,15 @@ void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBa const s16 buffer_offset, const s8 channel) { auto& cmd{GenerateStart(node_id)}; + if (behavior->IsEffectInfoVersion2Supported()) { + const auto& parameter_v2{ + *reinterpret_cast(effect_info.GetParameter())}; + cmd.input_index = buffer_offset + parameter_v2.inputs[channel]; + cmd.output_index = buffer_offset + parameter_v2.outputs[channel]; + GenerateEnd(cmd); + return; + } + const auto& parameter{ *reinterpret_cast(effect_info.GetParameter())}; cmd.input_index = buffer_offset + parameter.inputs[channel]; diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp index f97db5899e..bec6abbaf9 100644 --- a/src/audio_core/renderer/command/command_generator.cpp +++ b/src/audio_core/renderer/command/command_generator.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -361,12 +364,37 @@ void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBas void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, EffectInfoBase& effect_info, const s32 node_id) { - const auto& parameter{ - *reinterpret_cast(effect_info.GetParameter())}; + EffectInfoBase::ParameterState param_state{}; + s8 channel_count = 0; + + if (render_context.behavior->IsEffectInfoVersion2Supported()) { + const auto* parameter = + reinterpret_cast(effect_info.GetParameter()); + if (!parameter) { + LOG_ERROR(Service_Audio, "Biquad filter parameter is null"); + return; + } + param_state = parameter->state; + channel_count = parameter->channel_count; + } else { + const auto* parameter = + reinterpret_cast(effect_info.GetParameter()); + if (!parameter) { + LOG_ERROR(Service_Audio, "Biquad filter parameter is null"); + return; + } + param_state = parameter->state; + channel_count = parameter->channel_count; + } + + if (channel_count <= 0) { + return; + } + if (effect_info.IsEnabled()) { bool needs_init{false}; - switch (parameter.state) { + switch (param_state) { case EffectInfoBase::ParameterState::Initialized: needs_init = true; break; @@ -375,22 +403,26 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) { needs_init = false; } else { - needs_init = parameter.state == EffectInfoBase::ParameterState::Updating; + needs_init = param_state == EffectInfoBase::ParameterState::Updating; } break; default: - LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", - static_cast(parameter.state)); + LOG_ERROR(Service_Audio, + "Invalid biquad parameter state {}, treating as uninitialized", + static_cast(param_state)); + needs_init = true; break; } - for (s8 channel = 0; channel < parameter.channel_count; channel++) { - command_buffer.GenerateBiquadFilterCommand( - node_id, effect_info, buffer_offset, channel, needs_init, - render_context.behavior->UseBiquadFilterFloatProcessing()); + const bool use_float_processing = + render_context.behavior->UseBiquadFilterFloatProcessing(); + + for (s8 channel = 0; channel < channel_count; channel++) { + command_buffer.GenerateBiquadFilterCommand(node_id, effect_info, buffer_offset, channel, + needs_init, use_float_processing); } } else { - for (s8 channel = 0; channel < parameter.channel_count; channel++) { + for (s8 channel = 0; channel < channel_count; channel++) { command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, channel); } diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp index 08161d8400..d3e1bfc08c 100644 --- a/src/audio_core/renderer/effect/biquad_filter.cpp +++ b/src/audio_core/renderer/effect/biquad_filter.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -34,14 +37,22 @@ void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, } void BiquadFilterInfo::UpdateForCommandGeneration() { - if (enabled) { - usage_state = UsageState::Enabled; + usage_state = enabled ? UsageState::Enabled : UsageState::Disabled; + + auto* params_v1 = reinterpret_cast(parameter.data()); + auto* params_v2 = reinterpret_cast(parameter.data()); + + const auto raw_state_v1 = static_cast(params_v1->state); + const auto raw_state_v2 = static_cast(params_v2->state); + + if (raw_state_v1 <= static_cast(ParameterState::Updated)) { + params_v1->state = ParameterState::Updated; + } else if (raw_state_v2 <= static_cast(ParameterState::Updated)) { + params_v2->state = ParameterState::Updated; } else { - usage_state = UsageState::Disabled; + params_v1->state = ParameterState::Updated; + params_v2->state = ParameterState::Updated; } - - auto params{reinterpret_cast(parameter.data())}; - params->state = ParameterState::Updated; } void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {} diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h index 5a22899abd..e268cfa11a 100644 --- a/src/audio_core/renderer/effect/biquad_filter.h +++ b/src/audio_core/renderer/effect/biquad_filter.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -27,10 +30,12 @@ public: struct ParameterVersion2 { /* 0x00 */ std::array inputs; /* 0x06 */ std::array outputs; - /* 0x0C */ std::array b; - /* 0x12 */ std::array a; - /* 0x16 */ s8 channel_count; - /* 0x17 */ ParameterState state; + /* 0x0C */ u32 padding; + /* 0x10 */ std::array b; + /* 0x1C */ std::array a; + /* 0x24 */ s8 channel_count; + /* 0x25 */ ParameterState state; + /* 0x26 */ u16 reserved; }; static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), "BiquadFilterInfo::ParameterVersion2 has the wrong size!"); diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 5ea9e23780..ffeb6cb720 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -304,7 +307,7 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { case Tegra::Texture::WrapMode::Border: return GL_CLAMP_TO_BORDER; case Tegra::Texture::WrapMode::Clamp: - return GL_CLAMP; + return GL_CLAMP_TO_EDGE; case Tegra::Texture::WrapMode::MirrorOnceClampToEdge: return GL_MIRROR_CLAMP_TO_EDGE; case Tegra::Texture::WrapMode::MirrorOnceBorder: @@ -319,9 +322,10 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { } else { return GL_MIRROR_CLAMP_TO_EDGE; } + default: + UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", wrap_mode); + return GL_REPEAT; } - UNIMPLEMENTED_MSG("Unimplemented texture wrap mode={}", wrap_mode); - return GL_REPEAT; } inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) { diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index a7a878f18c..c993e19ad4 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -50,7 +50,8 @@ VkSamplerMipmapMode MipmapMode(Tegra::Texture::TextureMipmapFilter mipmap_filter return {}; } -VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wrap_mode, +VkSamplerAddressMode WrapMode(const Device& device, + Tegra::Texture::WrapMode wrap_mode, Tegra::Texture::TextureFilter filter) { switch (wrap_mode) { case Tegra::Texture::WrapMode::Wrap: @@ -62,12 +63,6 @@ VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wra case Tegra::Texture::WrapMode::Border: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; case Tegra::Texture::WrapMode::Clamp: - if (device.GetDriverID() == VK_DRIVER_ID_NVIDIA_PROPRIETARY) { - // Nvidia's Vulkan driver defaults to GL_CLAMP on invalid enumerations, we can hack this - // by sending an invalid enumeration. - return static_cast(0xcafe); - } - // TODO(Rodrigo): Emulate GL_CLAMP properly on other vendors switch (filter) { case Tegra::Texture::TextureFilter::Nearest: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; @@ -81,6 +76,8 @@ VkSamplerAddressMode WrapMode(const Device& device, Tegra::Texture::WrapMode wra case Tegra::Texture::WrapMode::MirrorOnceBorder: UNIMPLEMENTED(); return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; + case Tegra::Texture::WrapMode::MirrorOnceClampOGL: + return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; default: UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", wrap_mode); return {}; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index da101fc4b7..18e74c49e7 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -240,13 +240,10 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { UpdateDynamicStates(); HandleTransformFeedback(); - query_cache.NotifySegment(true); query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable); draw_func(); - - query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount, false); } void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {