diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h index 91d6991416..4585be45ef 100644 --- a/src/audio_core/common/feature_support.h +++ b/src/audio_core/common/feature_support.h @@ -16,7 +16,7 @@ #include namespace AudioCore { -constexpr u32 CurrentRevision = 16; +constexpr u32 CurrentRevision = 15; enum class SupportTags { CommandProcessingTimeEstimatorVersion4, @@ -47,6 +47,10 @@ enum class SupportTags { DelayChannelMappingChange, ReverbChannelMappingChange, I3dl2ReverbChannelMappingChange, + SplitterPrevVolumeReset, + SplitterBiquadFilterParameter, + SplitterDestinationV2b, + VoiceInParameterV2, // Not a real tag, just here to get the count. Size @@ -91,6 +95,10 @@ constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) { {SupportTags::DelayChannelMappingChange, 11}, {SupportTags::ReverbChannelMappingChange, 11}, {SupportTags::I3dl2ReverbChannelMappingChange, 11}, + {SupportTags::SplitterBiquadFilterParameter, 12}, + {SupportTags::SplitterPrevVolumeReset, 13}, + {SupportTags::SplitterDestinationV2b, 15}, + {SupportTags::VoiceInParameterV2, 15}, }}; const auto& feature = diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp index d0df1f29de..63b80c503a 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp @@ -193,4 +193,20 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); } +bool BehaviorInfo::IsSplitterPrevVolumeResetSupported() const { + return CheckFeatureSupported(SupportTags::SplitterPrevVolumeReset, user_revision); +} + +bool BehaviorInfo::IsSplitterDestinationV2bSupported() const { + return CheckFeatureSupported(SupportTags::SplitterDestinationV2b, user_revision); +} + +bool BehaviorInfo::IsVoiceInParameterV2Supported() const { + return CheckFeatureSupported(SupportTags::VoiceInParameterV2, user_revision); +} + +bool BehaviorInfo::IsBiquadFilterParameterForSplitterEnabled() const { + return CheckFeatureSupported(SupportTags::SplitterBiquadFilterParameter, user_revision); +} + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h index a4958857a4..757f163c5a 100644 --- a/src/audio_core/renderer/behavior/behavior_info.h +++ b/src/audio_core/renderer/behavior/behavior_info.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 @@ -361,6 +364,38 @@ public: */ bool IsI3dl2ReverbChannelMappingChanged() const; + /** + * Check if explicit previous mix volume reset is supported for splitters. + * This allows splitters to explicitly reset their previous mix volumes instead of + * doing so implicitly on first use. + * + * @return True if supported, otherwise false. + */ + bool IsSplitterPrevVolumeResetSupported() const; + + /** + * Check if splitter destination v2b parameter format is supported (revision 15+). + * This uses the extended parameter format with biquad filter fields. + * + * @return True if supported, otherwise false. + */ + bool IsSplitterDestinationV2bSupported() const; + + /** + * Check if voice input parameter v2 format is supported (revision 15+). + * This uses the extended parameter format with float biquad filters. + * + * @return True if supported, otherwise false. + */ + bool IsVoiceInParameterV2Supported() const; + + /** + * Check if splitter destinations can carry biquad filter parameters (revision 12+). + * + * @return True if supported, otherwise false. + */ + bool IsBiquadFilterParameterForSplitterEnabled() const; + /// Host version u32 process_revision; /// User version diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index 20f6cda3a2..39bbc91ded 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -64,8 +64,6 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, behaviour.IsMemoryForceMappingEnabled()); const auto voice_count{voice_context.GetCount()}; - std::span in_params{ - reinterpret_cast(input), voice_count}; std::span out_params{reinterpret_cast(output), voice_count}; @@ -76,8 +74,104 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, u32 new_voice_count{0}; + // Two input formats exist: legacy (0x170) and v2 with float biquad (0x188). + const bool use_v2 = behaviour.IsVoiceInParameterV2Supported(); + const u32 in_stride = use_v2 ? 0x188u : static_cast(sizeof(VoiceInfo::InParameter)); + for (u32 i = 0; i < voice_count; i++) { - const auto& in_param{in_params[i]}; + VoiceInfo::InParameter local_in{}; + std::array float_biquads{}; + + if (!use_v2) { + const auto* in_param_ptr = reinterpret_cast( + input + i * sizeof(VoiceInfo::InParameter)); + local_in = *in_param_ptr; + } else { + struct VoiceInParameterV2 { + u32 id; + u32 node_id; + bool is_new; + bool in_use; + PlayState play_state; + SampleFormat sample_format; + u32 sample_rate; + u32 priority; + u32 sort_order; + u32 channel_count; + f32 pitch; + f32 volume; + // Two BiquadFilterParameter2 (0x18 each) -> ignored/converted + struct BiquadV2 { + bool enable; + u8 r1; + u8 r2; + u8 r3; + std::array b; + std::array a; + } biquads[2]; + u32 wave_buffer_count; + u32 wave_buffer_index; + u32 reserved1; + u64 src_data_address; + u64 src_data_size; + s32 mix_id; + u32 splitter_id; + std::array wavebuffers; + std::array channel_resource_ids; + bool clear_voice_drop; + u8 flush_wave_buffer_count; + u16 reserved2; + VoiceInfo::Flags flags; + SrcQuality src_quality; + u32 external_ctx; + u32 external_ctx_size; + u32 reserved3[2]; + }; + const auto* vin = reinterpret_cast(input + i * in_stride); + local_in.id = vin->id; + local_in.node_id = vin->node_id; + local_in.is_new = vin->is_new; + local_in.in_use = vin->in_use; + local_in.play_state = vin->play_state; + local_in.sample_format = vin->sample_format; + local_in.sample_rate = vin->sample_rate; + local_in.priority = static_cast(vin->priority); + local_in.sort_order = static_cast(vin->sort_order); + local_in.channel_count = vin->channel_count; + local_in.pitch = vin->pitch; + local_in.volume = vin->volume; + + // For REV15+, we keep float coefficients separate and only convert for compatibility + for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) { + const auto& src = vin->biquads[filter_idx]; + auto& dst = local_in.biquads[filter_idx]; + dst.enabled = src.enable; + // Convert float coefficients to fixed-point Q2.14 for legacy path + dst.b[0] = static_cast(std::clamp(src.b[0] * 16384.0f, -32768.0f, 32767.0f)); + dst.b[1] = static_cast(std::clamp(src.b[1] * 16384.0f, -32768.0f, 32767.0f)); + dst.b[2] = static_cast(std::clamp(src.b[2] * 16384.0f, -32768.0f, 32767.0f)); + dst.a[0] = static_cast(std::clamp(src.a[0] * 16384.0f, -32768.0f, 32767.0f)); + dst.a[1] = static_cast(std::clamp(src.a[1] * 16384.0f, -32768.0f, 32767.0f)); + + // Also store the native float version + float_biquads[filter_idx].enabled = src.enable; + float_biquads[filter_idx].numerator = src.b; + float_biquads[filter_idx].denominator = src.a; + } + local_in.wave_buffer_count = vin->wave_buffer_count; + local_in.wave_buffer_index = static_cast(vin->wave_buffer_index); + local_in.src_data_address = static_cast(vin->src_data_address); + local_in.src_data_size = vin->src_data_size; + local_in.mix_id = static_cast(vin->mix_id); + local_in.splitter_id = vin->splitter_id; + local_in.wave_buffer_internal = vin->wavebuffers; + local_in.channel_resource_ids = vin->channel_resource_ids; + local_in.clear_voice_drop = vin->clear_voice_drop; + local_in.flush_buffer_count = vin->flush_wave_buffer_count; + local_in.flags = vin->flags; + local_in.src_quality = vin->src_quality; + } + const auto& in_param = local_in; std::array voice_states{}; if (!in_param.in_use) { @@ -101,6 +195,14 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, BehaviorInfo::ErrorInfo update_error{}; voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour); + // For REV15+, store the native float biquad coefficients + if (use_v2) { + voice_info.use_float_biquads = true; + voice_info.biquads_float = float_biquads; + } else { + voice_info.use_float_biquads = false; + } + if (!update_error.error_code.IsSuccess()) { behaviour.AppendError(update_error); } @@ -121,7 +223,7 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, new_voice_count += in_param.channel_count; } - auto consumed_input_size{voice_count * static_cast(sizeof(VoiceInfo::InParameter))}; + auto consumed_input_size{voice_count * in_stride}; auto consumed_output_size{voice_count * static_cast(sizeof(VoiceInfo::OutStatus))}; if (consumed_input_size != in_header->voices_size) { LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}", @@ -257,18 +359,31 @@ Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_co EffectContext& effect_context, SplitterContext& splitter_context) { s32 mix_count{0}; u32 consumed_input_size{0}; + u32 input_mix_size{0}; if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { auto in_dirty_params{reinterpret_cast(input)}; mix_count = in_dirty_params->count; + + // Validate against expected header size to ensure structure is correct + if (mix_count < 0 || mix_count > 0x100) { + LOG_ERROR( + Service_Audio, + "Invalid mix count from dirty parameter: count={}, magic=0x{:X}, expected_size={}", + mix_count, in_dirty_params->magic, in_header->mix_size); + return Service::Audio::ResultInvalidUpdateInfo; + } + + consumed_input_size += static_cast(sizeof(MixInfo::InDirtyParameter)); input += sizeof(MixInfo::InDirtyParameter); - consumed_input_size = static_cast(sizeof(MixInfo::InDirtyParameter) + - mix_count * sizeof(MixInfo::InParameter)); } else { mix_count = mix_context.GetCount(); - consumed_input_size = static_cast(mix_count * sizeof(MixInfo::InParameter)); } + input_mix_size = static_cast(mix_count * sizeof(MixInfo::InParameter)); + consumed_input_size += input_mix_size; + + if (mix_buffer_count == 0) { return Service::Audio::ResultInvalidUpdateInfo; } diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp index d680e0c74b..a0a574fc64 100644 --- a/src/audio_core/renderer/command/command_buffer.cpp +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -237,6 +237,13 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& vo cmd.biquad = voice_info.biquads[biquad_index]; + if (voice_info.use_float_biquads) { + cmd.biquad_float = voice_info.biquads_float[biquad_index]; + cmd.use_float_coefficients = true; + } else { + cmd.use_float_coefficients = false; + } + cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); @@ -263,6 +270,9 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas cmd.biquad.b = parameter.b; cmd.biquad.a = parameter.a; + // Effects use legacy fixed-point format + cmd.use_float_coefficients = false; + cmd.state = memory_pool->Translate(CpuAddr(state), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); @@ -658,6 +668,13 @@ void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, Voice cmd.output = buffer_count + channel; cmd.biquads = voice_info.biquads; + if (voice_info.use_float_biquads) { + cmd.biquads_float = voice_info.biquads_float; + cmd.use_float_coefficients = true; + } else { + cmd.use_float_coefficients = false; + } + cmd.states[0] = memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp index 539e0c6370..4ad3184079 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp @@ -51,6 +51,40 @@ void ApplyBiquadFilterFloat(std::span output, std::span input, state.s3 = Common::BitCast(s[3]); } +/** + * Biquad filter float implementation with native float coefficients. + */ +void ApplyBiquadFilterFloat2(std::span output, std::span input, + std::array& b, std::array& a, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr f64 min{std::numeric_limits::min()}; + constexpr f64 max{std::numeric_limits::max()}; + + std::array b_double{static_cast(b[0]), static_cast(b[1]), + static_cast(b[2])}; + std::array a_double{static_cast(a[0]), static_cast(a[1])}; + std::array s{Common::BitCast(state.s0), Common::BitCast(state.s1), + Common::BitCast(state.s2), Common::BitCast(state.s3)}; + + for (u32 i = 0; i < sample_count; i++) { + f64 in_sample{static_cast(input[i])}; + auto sample{in_sample * b_double[0] + s[0] * b_double[1] + s[1] * b_double[2] + + s[2] * a_double[0] + s[3] * a_double[1]}; + + output[i] = static_cast(std::clamp(sample, min, max)); + + s[1] = s[0]; + s[0] = in_sample; + s[3] = s[2]; + s[2] = sample; + } + + state.s0 = Common::BitCast(s[0]); + state.s1 = Common::BitCast(s[1]); + state.s2 = Common::BitCast(s[2]); + state.s3 = Common::BitCast(s[3]); +} + /** * Biquad filter s32 implementation. * @@ -98,8 +132,14 @@ void BiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& pro processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; if (use_float_processing) { - ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, - processor.sample_count); + // REV15+: Use native float coefficients if available + if (use_float_coefficients) { + ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquad_float.numerator, + biquad_float.denominator, *state_, processor.sample_count); + } else { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } } else { ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, processor.sample_count); diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h index 0e903930a6..0e1893e536 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.h +++ b/src/audio_core/renderer/command/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 @@ -50,12 +53,16 @@ struct BiquadFilterCommand : ICommand { s16 output; /// Input parameters for biquad VoiceInfo::BiquadFilterParameter biquad; + /// Input parameters for biquad (REV15+ native float) + VoiceInfo::BiquadFilterParameter2 biquad_float; /// Biquad state, updated each call CpuAddr state; /// If true, reset the state bool needs_init; /// If true, use float processing rather than int bool use_float_processing; + /// If true, use native float coefficients (REV15+) + bool use_float_coefficients; }; /** @@ -72,4 +79,18 @@ void ApplyBiquadFilterFloat(std::span output, std::span input, std::array& b, std::array& a, VoiceState::BiquadFilterState& state, const u32 sample_count); +/** + * Biquad filter float implementation with native float coefficients (SDK REV15+). + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients (float). + * @param a - Feedback coefficients (float). + * @param state - State to track previous samples. + * @param sample_count - Number of samples to process. + */ +void ApplyBiquadFilterFloat2(std::span output, std::span input, + std::array& b, std::array& a, + VoiceState::BiquadFilterState& state, const u32 sample_count); + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp index 208bbeaf29..2dfbcf6145 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/multi_tap_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 @@ -33,8 +36,14 @@ void MultiTapBiquadFilterCommand::Process(const AudioRenderer::CommandListProces *state = {}; } - ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, - processor.sample_count); + // REV15+: Use native float coefficients if available + if (use_float_coefficients) { + ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquads_float[i].numerator, + biquads_float[i].denominator, *state, processor.sample_count); + } else { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, + processor.sample_count); + } } } diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h index 50fce80b0c..35fa76f62d 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h +++ b/src/audio_core/renderer/command/effect/multi_tap_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 @@ -49,12 +52,16 @@ struct MultiTapBiquadFilterCommand : ICommand { s16 output; /// Biquad parameters std::array biquads; + /// Biquad parameters (REV15+ native float) + std::array biquads_float; /// Biquad states, updated each call std::array states; /// If each biquad needs initialisation std::array needs_init; /// Number of active biquads u8 filter_tap_count; + /// If true, use native float coefficients (REV15+) + bool use_float_coefficients; }; } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp index fb118e981e..1ba5daebe9 100644 --- a/src/audio_core/renderer/splitter/splitter_context.cpp +++ b/src/audio_core/renderer/splitter/splitter_context.cpp @@ -35,12 +35,16 @@ SplitterDestinationData& SplitterContext::GetData(const u32 index) { void SplitterContext::Setup(std::span splitter_infos_, const u32 splitter_info_count_, SplitterDestinationData* splitter_destinations_, - const u32 destination_count_, const bool splitter_bug_fixed_) { + const u32 destination_count_, const bool splitter_bug_fixed_, + const BehaviorInfo& behavior) { splitter_infos = splitter_infos_; info_count = splitter_info_count_; splitter_destinations = splitter_destinations_; destinations_count = destination_count_; splitter_bug_fixed = splitter_bug_fixed_; + splitter_prev_volume_reset_supported = behavior.IsSplitterPrevVolumeResetSupported(); + splitter_biquad_param_supported = behavior.IsBiquadFilterParameterForSplitterEnabled(); + splitter_float_coeff_supported = behavior.IsSplitterDestinationV2bSupported(); } bool SplitterContext::UsingSplitter() const { @@ -84,7 +88,7 @@ bool SplitterContext::Initialize(const BehaviorInfo& behavior, } Setup(splitter_infos, params.splitter_infos, splitter_destinations, - params.splitter_destinations, behavior.IsSplitterBugFixed()); + params.splitter_destinations, behavior.IsSplitterBugFixed(), behavior); } return true; } @@ -137,19 +141,104 @@ u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_ u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { for (u32 i = 0; i < count; i++) { - auto data_header{ - reinterpret_cast(input + offset)}; + // Version selection based on feature flags: + // - REV12: integer biquad params (Version2a) + // - REV15: float coeff/biquad v2b + // - older: no biquad fields + if (!splitter_biquad_param_supported) { + const auto* data_header = + reinterpret_cast(input + offset); - if (data_header->magic != GetSplitterSendDataMagic()) { - continue; + if (data_header->magic != GetSplitterSendDataMagic()) { + continue; + } + if (data_header->id < 0 || data_header->id > destinations_count) { + continue; + } + + auto modified_params = *data_header; + if (!splitter_prev_volume_reset_supported) { + modified_params.reset_prev_volume = false; + } + splitter_destinations[data_header->id].Update(modified_params); + offset += sizeof(SplitterDestinationData::InParameter); + } else if (!splitter_float_coeff_supported) { + // Version 2a: struct contains legacy fixed-point biquad filter fields (REV12+) + const auto* data_header_v2a = + reinterpret_cast(input + + offset); + + if (data_header_v2a->magic != GetSplitterSendDataMagic()) { + continue; + } + if (data_header_v2a->id < 0 || data_header_v2a->id > destinations_count) { + continue; + } + + // Map common fields to the base format + SplitterDestinationData::InParameter mapped{}; + mapped.magic = data_header_v2a->magic; + mapped.id = data_header_v2a->id; + mapped.mix_volumes = data_header_v2a->mix_volumes; + mapped.mix_id = data_header_v2a->mix_id; + mapped.in_use = data_header_v2a->in_use; + mapped.reset_prev_volume = + splitter_prev_volume_reset_supported ? data_header_v2a->reset_prev_volume : false; + + auto& destination = splitter_destinations[data_header_v2a->id]; + destination.Update(mapped); + + // Convert legacy fixed-point biquad params into float representation + auto biquad_filters = destination.GetBiquadFilters(); + for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) { + const auto& legacy = data_header_v2a->biquad_filters[filter_idx]; + auto& out = biquad_filters[filter_idx]; + out.enabled = legacy.enabled; + // s16 fixed-point scale: use Q14 like voices (b and a are s16, 1.0 ~= 1<<14) + constexpr float scale = 1.0f / static_cast(1 << 14); + out.numerator[0] = static_cast(legacy.b[0]) * scale; + out.numerator[1] = static_cast(legacy.b[1]) * scale; + out.numerator[2] = static_cast(legacy.b[2]) * scale; + out.denominator[0] = static_cast(legacy.a[0]) * scale; + out.denominator[1] = static_cast(legacy.a[1]) * scale; + } + + offset += static_cast(sizeof(SplitterDestinationData::InParameterVersion2a)); + } else { + // Version 2b: struct contains extra biquad filter fields with float coeffs + const auto* data_header_v2b = + reinterpret_cast(input + + offset); + + if (data_header_v2b->magic != GetSplitterSendDataMagic()) { + continue; + } + if (data_header_v2b->id < 0 || data_header_v2b->id > destinations_count) { + continue; + } + + // Map common fields to the old format + SplitterDestinationData::InParameter mapped{}; + mapped.magic = data_header_v2b->magic; + mapped.id = data_header_v2b->id; + mapped.mix_volumes = data_header_v2b->mix_volumes; + mapped.mix_id = data_header_v2b->mix_id; + mapped.in_use = data_header_v2b->in_use; + mapped.reset_prev_volume = + splitter_prev_volume_reset_supported ? data_header_v2b->reset_prev_volume : false; + + // Store biquad filters from V2b (REV15+) + auto& destination = splitter_destinations[data_header_v2b->id]; + destination.Update(mapped); + + // Copy biquad filter parameters + auto biquad_filters = destination.GetBiquadFilters(); + for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) { + biquad_filters[filter_idx] = data_header_v2b->biquad_filters[filter_idx]; + } + + offset += static_cast(sizeof(SplitterDestinationData::InParameterVersion2b)); } - - if (data_header->id < 0 || data_header->id > destinations_count) { - continue; - } - - splitter_destinations[data_header->id].Update(*data_header); - offset += sizeof(SplitterDestinationData::InParameter); } return offset; diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h index 1c0b846719..c71ce3d7c4 100644 --- a/src/audio_core/renderer/splitter/splitter_context.h +++ b/src/audio_core/renderer/splitter/splitter_context.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 @@ -168,10 +171,11 @@ private: * @param splitter_destinations - Workbuffer for splitter destinations. * @param destination_count - Number of destinations in the workbuffer. * @param splitter_bug_fixed - Is the splitter bug fixed? + * @param behavior - Behavior info for feature support. */ void Setup(std::span splitter_infos, u32 splitter_info_count, SplitterDestinationData* splitter_destinations, u32 destination_count, - bool splitter_bug_fixed); + bool splitter_bug_fixed, const BehaviorInfo& behavior); /// Workbuffer for splitters std::span splitter_infos{}; @@ -183,6 +187,12 @@ private: s32 destinations_count{}; /// Is the splitter bug fixed? bool splitter_bug_fixed{}; + /// Is explicit previous mix volume reset supported? + bool splitter_prev_volume_reset_supported{}; + /// Is biquad filter parameter for splitter (REV12) supported? + bool splitter_biquad_param_supported{}; + /// Is float coefficient/biquad filter v2b parameter supported? + bool splitter_float_coeff_supported{}; }; } // namespace Renderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp index 5ec37e48e1..028cee9853 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.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 @@ -84,4 +87,14 @@ void SplitterDestinationData::SetNext(SplitterDestinationData* next_) { next = next_; } +std::span +SplitterDestinationData::GetBiquadFilters() { + return biquad_filters; +} + +std::span +SplitterDestinationData::GetBiquadFilters() const { + return biquad_filters; +} + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h index 90edfc667c..6bfe5e0e34 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.h +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.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 @@ -16,16 +19,72 @@ namespace AudioCore::Renderer { */ class SplitterDestinationData { public: + /** + * Biquad filter parameter with float coefficients (SDK REV15+). + * Defined here to avoid circular dependency with VoiceInfo. + */ + struct BiquadFilterParameter2 { + /* 0x00 */ bool enabled; + /* 0x01 */ u8 reserved1; + /* 0x02 */ u8 reserved2; + /* 0x03 */ u8 reserved3; + /* 0x04 */ std::array numerator; // b0, b1, b2 + /* 0x10 */ std::array denominator; // a1, a2 (a0 = 1) + }; + static_assert(sizeof(BiquadFilterParameter2) == 0x18, + "BiquadFilterParameter2 has the wrong size!"); + + /** + * Legacy biquad filter parameter with fixed-point coefficients (SDK REV12+ for splitters). + * Matches the old voice biquad format. + */ + struct BiquadFilterParameterLegacy { + /* 0x00 */ bool enabled; + /* 0x02 */ std::array b; // numerator + /* 0x08 */ std::array a; // denominator (a0 = 1) + }; + static_assert(sizeof(BiquadFilterParameterLegacy) == 0xC, + "BiquadFilterParameterLegacy has the wrong size!"); + struct InParameter { /* 0x00 */ u32 magic; // 'SNDD' /* 0x04 */ s32 id; /* 0x08 */ std::array mix_volumes; /* 0x68 */ u32 mix_id; /* 0x6C */ bool in_use; + /* 0x6D */ bool reset_prev_volume; }; static_assert(sizeof(InParameter) == 0x70, "SplitterDestinationData::InParameter has the wrong size!"); + struct InParameterVersion2a { + /* 0x00 */ u32 magic; // 'SNDD' + /* 0x04 */ s32 id; + /* 0x08 */ std::array mix_volumes; + /* 0x68 */ u32 mix_id; + /* 0x6C */ std::array + biquad_filters; + /* 0x84 */ bool in_use; + /* 0x85 */ bool reset_prev_volume; // only effective if supported + /* 0x86 */ u8 reserved[10]; + }; + static_assert(sizeof(InParameterVersion2a) == 0x90, + "SplitterDestinationData::InParameterVersion2a has the wrong size!"); + + struct InParameterVersion2b { + /* 0x00 */ u32 magic; // 'SNDD' + /* 0x04 */ s32 id; + /* 0x08 */ std::array mix_volumes; + /* 0x68 */ u32 mix_id; + /* 0x6C */ std::array + biquad_filters; + /* 0x9C */ bool in_use; + /* 0x9D */ bool reset_prev_volume; + /* 0x9E */ u8 reserved[10]; + }; + static_assert(sizeof(InParameterVersion2b) == 0xA8, + "SplitterDestinationData::InParameterVersion2b has the wrong size!"); + SplitterDestinationData(s32 id); /** @@ -78,7 +137,7 @@ public: f32 GetMixVolumePrev(u32 index) const; /** - * Get the previous mix volumes for all mix buffers in this destination. + * Get the previous mix volumes for all mix buffers. * * @return Span of previous mix buffer volumes. */ @@ -115,6 +174,20 @@ public: */ void SetNext(SplitterDestinationData* next); + /** + * Get biquad filter parameters for this destination (REV15+ or mapped from REV12). + * + * @return Span of biquad filter parameters. + */ + std::span GetBiquadFilters(); + + /** + * Get const biquad filter parameters for this destination (REV15+ or mapped from REV12). + * + * @return Const span of biquad filter parameters. + */ + std::span GetBiquadFilters() const; + private: /// Id of this destination const s32 id; @@ -124,6 +197,8 @@ private: std::array mix_volumes{0.0f}; /// Previous mix volumes std::array prev_mix_volumes{0.0f}; + /// Biquad filter parameters (REV15+ or mapped from REV12) + std::array biquad_filters{}; /// Next destination in the mix chain SplitterDestinationData* next{}; /// Is this destination in use? diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h index 14a687dcb7..595150f232 100644 --- a/src/audio_core/renderer/voice/voice_info.h +++ b/src/audio_core/renderer/voice/voice_info.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 @@ -135,6 +138,17 @@ public: static_assert(sizeof(BiquadFilterParameter) == 0xC, "VoiceInfo::BiquadFilterParameter has the wrong size!"); + struct BiquadFilterParameter2 { + /* 0x00 */ bool enabled; + /* 0x01 */ u8 reserved1; + /* 0x02 */ u8 reserved2; + /* 0x03 */ u8 reserved3; + /* 0x04 */ std::array numerator; // b0, b1, b2 + /* 0x10 */ std::array denominator; // a1, a2 (a0 = 1) + }; + static_assert(sizeof(BiquadFilterParameter2) == 0x18, + "VoiceInfo::BiquadFilterParameter2 has the wrong size!"); + struct InParameter { /* 0x000 */ u32 id; /* 0x004 */ u32 node_id; @@ -168,6 +182,43 @@ public: }; static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!"); + struct InParameter2 { + /* 0x000 */ u32 id; + /* 0x004 */ u32 node_id; + /* 0x008 */ bool is_new; + /* 0x009 */ bool in_use; + /* 0x00A */ PlayState play_state; + /* 0x00B */ SampleFormat sample_format; + /* 0x00C */ u32 sample_rate; + /* 0x010 */ s32 priority; + /* 0x014 */ s32 sort_order; + /* 0x018 */ u32 channel_count; + /* 0x01C */ f32 pitch; + /* 0x020 */ f32 volume; + /* 0x024 */ std::array biquads; + /* 0x054 */ u32 wave_buffer_count; + /* 0x058 */ u32 wave_buffer_index; + /* 0x05C */ u32 reserved1; + /* 0x060 */ CpuAddr src_data_address; + /* 0x068 */ u64 src_data_size; + /* 0x070 */ u32 mix_id; + /* 0x074 */ u32 splitter_id; + /* 0x078 */ std::array wave_buffer_internal; + /* 0x158 */ std::array channel_resource_ids; + /* 0x170 */ bool clear_voice_drop; + /* 0x171 */ u8 flush_buffer_count; + /* 0x172 */ u16 reserved2; + /* 0x174 */ Flags flags; + /* 0x175 */ u8 reserved3; + /* 0x176 */ SrcQuality src_quality; + /* 0x177 */ u8 reserved4; + /* 0x178 */ u32 external_context; + /* 0x17C */ u32 external_context_size; + /* 0x180 */ u32 reserved5; + /* 0x184 */ u32 reserved6; + }; + static_assert(sizeof(InParameter2) == 0x188, "VoiceInfo::InParameter2 has the wrong size!"); + struct OutStatus { /* 0x00 */ u64 played_sample_count; /* 0x08 */ u32 wave_buffers_consumed; @@ -349,6 +400,10 @@ public: f32 prev_volume{}; /// Biquad filters for generating filter commands on this voice std::array biquads{}; + /// Float biquad filters for REV15+ (native float coefficients) + std::array biquads_float{}; + /// Use float biquad coefficients (REV15+) + bool use_float_biquads{}; /// Number of active wavebuffers u32 wave_buffer_count{}; /// Current playing wavebuffer index diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 192c6e5c01..6dcf7bb228 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -112,9 +109,6 @@ public: void ReadBlock(DAddr address, void* dest_pointer, size_t size); void ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size); -#ifdef YUZU_DEBUG - bool ReadBlockFastChecked(DAddr address, void* dest_pointer, size_t size); -#endif void WriteBlock(DAddr address, const void* src_pointer, size_t size); void WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size); diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 3629579c09..52dff5df9a 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -470,29 +467,6 @@ void DeviceMemoryManager::ReadBlockUnsafe(DAddr address, void* dest_poin }); } -#ifdef YUZU_DEBUG -template -bool DeviceMemoryManager::ReadBlockFastChecked(DAddr address, void* dest_pointer, - size_t size) { - bool success = true; - WalkBlock( - address, size, - [&](size_t copy_amount, DAddr current_vaddr) { - LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", current_vaddr, - size); - std::memset(dest_pointer, 0, copy_amount); - success = false; - }, - [&](size_t copy_amount, const u8* const src_ptr) { - std::memcpy(dest_pointer, src_ptr, copy_amount); - }, - [&](const std::size_t copy_amount) { - dest_pointer = static_cast(dest_pointer) + copy_amount; - }); - return success; -} -#endif - template void DeviceMemoryManager::WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size) { diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 4a892f7c65..12ea5f7aa1 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -509,6 +509,9 @@ std::vector ProfileManager::FindOrphanedProfiles() good_uuids.emplace_back(uuid_string); } + // used for acnh, etc + good_uuids.emplace_back("00000000000000000000000000000000"); + // TODO: fetch save_id programmatically const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "user/save/0000000000000000"; diff --git a/src/core/hle/service/am/frontend/applet_web_browser.cpp b/src/core/hle/service/am/frontend/applet_web_browser.cpp index e35a1daa1e..53fe867b9e 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser.cpp +++ b/src/core/hle/service/am/frontend/applet_web_browser.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -68,42 +71,6 @@ std::string ResolveURL(const std::string& url) { return url.substr(0, index) + "lp1" + url.substr(index + 1); } -WebArgInputTLVMap ReadWebArgs(const std::vector& web_arg, WebArgHeader& web_arg_header) { - std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader)); - - if (web_arg.size() == sizeof(WebArgHeader)) { - return {}; - } - - WebArgInputTLVMap input_tlv_map; - - u64 current_offset = sizeof(WebArgHeader); - - for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) { - if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) { - return input_tlv_map; - } - - WebArgInputTLV input_tlv; - std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV)); - - current_offset += sizeof(WebArgInputTLV); - - if (web_arg.size() < current_offset + input_tlv.arg_data_size) { - return input_tlv_map; - } - - std::vector data(input_tlv.arg_data_size); - std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size); - - current_offset += input_tlv.arg_data_size; - - input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data)); - } - - return input_tlv_map; -} - FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, FileSys::ContentRecordType nca_type) { if (nca_type == FileSys::ContentRecordType::Data) { @@ -144,6 +111,43 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, } } +#ifdef YUZU_USE_QT_WEB_ENGINE +WebArgInputTLVMap ReadWebArgs(const std::vector& web_arg, WebArgHeader& web_arg_header) { + std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader)); + + if (web_arg.size() == sizeof(WebArgHeader)) { + return {}; + } + + WebArgInputTLVMap input_tlv_map; + + u64 current_offset = sizeof(WebArgHeader); + + for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) { + if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) { + return input_tlv_map; + } + + WebArgInputTLV input_tlv; + std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV)); + + current_offset += sizeof(WebArgInputTLV); + + if (web_arg.size() < current_offset + input_tlv.arg_data_size) { + return input_tlv_map; + } + + std::vector data(input_tlv.arg_data_size); + std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size); + + current_offset += input_tlv.arg_data_size; + + input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data)); + } + + return input_tlv_map; +} + void ExtractSharedFonts(Core::System& system) { static constexpr std::array DECRYPTED_SHARED_FONTS{ "FontStandard.ttf", @@ -221,6 +225,7 @@ void ExtractSharedFonts(Core::System& system) { FileSys::VfsRawCopy(decrypted_font, out_file); } } +#endif } // namespace @@ -232,6 +237,7 @@ WebBrowser::WebBrowser(Core::System& system_, std::shared_ptr applet_, WebBrowser::~WebBrowser() = default; void WebBrowser::Initialize() { +#ifdef YUZU_USE_QT_WEB_ENGINE FrontendApplet::Initialize(); LOG_INFO(Service_AM, "Initializing Web Browser Applet."); @@ -284,6 +290,7 @@ void WebBrowser::Initialize() { ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); break; } +#endif } Result WebBrowser::GetStatus() const { @@ -295,6 +302,7 @@ void WebBrowser::ExecuteInteractive() { } void WebBrowser::Execute() { +#ifdef YUZU_USE_QT_WEB_ENGINE switch (web_arg_header.shim_kind) { case ShimKind::Shop: ExecuteShop(); @@ -322,6 +330,10 @@ void WebBrowser::Execute() { WebBrowserExit(WebExitReason::EndButtonPressed); break; } +#else + LOG_INFO(Service_AM, "Web Browser Applet disabled, skipping."); + WebBrowserExit(WebExitReason::EndButtonPressed); +#endif } void WebBrowser::ExtractOfflineRomFS() { diff --git a/src/core/hle/service/am/service/library_applet_creator.cpp b/src/core/hle/service/am/service/library_applet_creator.cpp index 54790838e0..e38729e70a 100644 --- a/src/core/hle/service/am/service/library_applet_creator.cpp +++ b/src/core/hle/service/am/service/library_applet_creator.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -175,6 +178,7 @@ ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_, std::shared_ {0, D<&ILibraryAppletCreator::CreateLibraryApplet>, "CreateLibraryApplet"}, {1, nullptr, "TerminateAllLibraryApplets"}, {2, nullptr, "AreAnyLibraryAppletsLeft"}, + {3, D<&ILibraryAppletCreator::CreateLibraryAppletEx>, "CreateLibraryAppletEx"}, {10, D<&ILibraryAppletCreator::CreateStorage>, "CreateStorage"}, {11, D<&ILibraryAppletCreator::CreateTransferMemoryStorage>, "CreateTransferMemoryStorage"}, {12, D<&ILibraryAppletCreator::CreateHandleStorage>, "CreateHandleStorage"}, @@ -210,6 +214,32 @@ Result ILibraryAppletCreator::CreateLibraryApplet( R_SUCCEED(); } +Result ILibraryAppletCreator::CreateLibraryAppletEx( + Out> out_library_applet_accessor, AppletId applet_id, + LibraryAppletMode library_applet_mode, u64 thread_id) { + LOG_DEBUG(Service_AM, "called with applet_id={} applet_mode={} thread_id={}", applet_id, + library_applet_mode, thread_id); + + std::shared_ptr library_applet; + if (ShouldCreateGuestApplet(applet_id)) { + library_applet = + CreateGuestApplet(system, m_window_system, m_applet, applet_id, library_applet_mode); + } + if (!library_applet) { + library_applet = + CreateFrontendApplet(system, m_window_system, m_applet, applet_id, library_applet_mode); + } + if (!library_applet) { + LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); + R_THROW(ResultUnknown); + } + + // Applet is created, can now be launched. + m_applet->library_applet_launchable_event.Signal(); + *out_library_applet_accessor = library_applet; + R_SUCCEED(); +} + Result ILibraryAppletCreator::CreateStorage(Out> out_storage, s64 size) { LOG_DEBUG(Service_AM, "called, size={}", size); diff --git a/src/core/hle/service/am/service/library_applet_creator.h b/src/core/hle/service/am/service/library_applet_creator.h index a10a769828..34e7586e8f 100644 --- a/src/core/hle/service/am/service/library_applet_creator.h +++ b/src/core/hle/service/am/service/library_applet_creator.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -24,6 +27,9 @@ private: Result CreateLibraryApplet( Out> out_library_applet_accessor, AppletId applet_id, LibraryAppletMode library_applet_mode); + Result CreateLibraryAppletEx( + Out> out_library_applet_accessor, AppletId applet_id, + LibraryAppletMode library_applet_mode, u64 thread_id); Result CreateStorage(Out> out_storage, s64 size); Result CreateTransferMemoryStorage( Out> out_storage, bool is_writable, s64 size, diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index 60ecd5c2b9..461f134bfd 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -128,6 +128,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {406, nullptr, "GetApplicationControlProperty"}, {407, nullptr, "ListApplicationTitle"}, {408, nullptr, "ListApplicationIcon"}, + {419, D<&IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground>, "RequestDownloadApplicationControlDataInBackground"}, {502, nullptr, "RequestCheckGameCardRegistration"}, {503, nullptr, "RequestGameCardRegistrationGoldPoint"}, {504, nullptr, "RequestRegisterGameCard"}, @@ -210,6 +211,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {1703, nullptr, "GetApplicationViewDownloadErrorContext"}, {1704, D<&IApplicationManagerInterface::GetApplicationViewWithPromotionInfo>, "GetApplicationViewWithPromotionInfo"}, {1705, nullptr, "IsPatchAutoDeletableApplication"}, + {1706, D<&IApplicationManagerInterface::Unknown1706>, "Unknown1706"}, {1800, nullptr, "IsNotificationSetupCompleted"}, {1801, nullptr, "GetLastNotificationInfoCount"}, {1802, nullptr, "ListLastNotificationInfo"}, @@ -309,6 +311,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {4022, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4022"}, {4023, D<&IApplicationManagerInterface::Unknown4023>, "Unknown4023"}, {4088, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4088"}, + {4053, D<&IApplicationManagerInterface::Unknown4053>, "Unknown4053"}, {9999, nullptr, "GetApplicationCertificate"}, }; // clang-format on @@ -526,6 +529,37 @@ Result IApplicationManagerInterface::GetApplicationTerminateResult(Out o R_SUCCEED(); } +Result IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground( + u64 unk, u64 application_id) { + LOG_WARNING(Service_NS, "(STUBBED), app={:016X} unk={}", application_id, unk); + R_SUCCEED(); +} + +Result IApplicationManagerInterface::Unknown1706( + OutBuffer out_buffer_58, + InBuffer in_buffer_8) { + LOG_WARNING(Service_NS, "(STUBBED) Unknown1706 called: out_size={} in_size={}", + out_buffer_58.size(), in_buffer_8.size()); + + if (out_buffer_58.size() < 0x58 || in_buffer_8.size() < 0x8) { + R_THROW(ResultUnknown); + } + + u64 application_id = 0; + std::memcpy(&application_id, in_buffer_8.data(), sizeof(u64)); + + ApplicationView view{}; + view.application_id = application_id; + view.unk = 0x70000; + view.flags = 0x401f17; + + std::memset(out_buffer_58.data(), 0, out_buffer_58.size()); + std::memcpy(out_buffer_58.data(), &view, sizeof(ApplicationView)); + + + R_SUCCEED(); +} + Result IApplicationManagerInterface::Unknown4022( OutCopyHandle out_event) { LOG_WARNING(Service_NS, "(STUBBED) called"); @@ -539,4 +573,9 @@ Result IApplicationManagerInterface::Unknown4023(Out out_result) { R_SUCCEED(); } +Result IApplicationManagerInterface::Unknown4053() { + LOG_WARNING(Service_NS, "(STUBBED) called."); + R_SUCCEED(); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h index 251f93ee06..e181960396 100644 --- a/src/core/hle/service/ns/application_manager_interface.h +++ b/src/core/hle/service/ns/application_manager_interface.h @@ -55,6 +55,12 @@ public: Result GetApplicationTerminateResult(Out out_result, u64 application_id); Result Unknown4022(OutCopyHandle out_event); Result Unknown4023(Out out_result); + Result Unknown4053(); + + Result RequestDownloadApplicationControlDataInBackground(u64 unk, + u64 application_id); + Result Unknown1706(OutBuffer out_buffer_58, + InBuffer in_buffer_8); private: KernelHelpers::ServiceContext service_context; diff --git a/src/core/hle/service/ns/ns_types.h b/src/core/hle/service/ns/ns_types.h index 2dd664c4e9..a4eec3a5d8 100644 --- a/src/core/hle/service/ns/ns_types.h +++ b/src/core/hle/service/ns/ns_types.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 @@ -81,10 +84,11 @@ static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size." /// NsApplicationViewWithPromotionInfo struct ApplicationViewWithPromotionInfo { - ApplicationView view; ///< \ref NsApplicationView - PromotionInfo promotion; ///< \ref NsPromotionInfo + ApplicationView view; ///< \ref NsApplicationView + PromotionInfo promotion; ///< \ref NsPromotionInfo + std::array padding{}; ///< Extra padding for newer HOS versions }; -static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x70, +static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x78, "ApplicationViewWithPromotionInfo has incorrect size."); struct ApplicationOccupiedSizeEntity { @@ -113,4 +117,10 @@ struct Uid { }; static_assert(sizeof(Uid) == 0x10, "Uid has incorrect size."); +struct ApplicationDisplayData { + std::array application_name; + std::array developer_name; +}; +static_assert(sizeof(ApplicationDisplayData) == 0x300, "ApplicationDisplayData has incorrect size."); + } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp index 9b2ca94a4f..c72aa08870 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,6 +10,7 @@ #include "core/file_sys/vfs/vfs.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ns/language.h" +#include "core/hle/service/ns/ns_types.h" #include "core/hle/service/ns/ns_results.h" #include "core/hle/service/ns/read_only_application_control_data_interface.h" #include "core/hle/service/set/settings_server.h" @@ -23,6 +27,7 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa {2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"}, {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"}, {4, nullptr, "SelectApplicationDesiredLanguage"}, + {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationDisplayData>, "GetApplicationDisplayData"}, }; // clang-format on @@ -119,4 +124,33 @@ Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLan R_SUCCEED(); } +Result IReadOnlyApplicationControlDataInterface::GetApplicationDisplayData( + OutBuffer out_buffer, Out out_size, u64 language_code, + u64 application_id) { + LOG_INFO(Service_NS, "called with application_id={:016X}, language_code={:016X}", + application_id, language_code); + + constexpr u64 payload_size = sizeof(ApplicationDisplayData); + + if (out_buffer.size() < payload_size) { + LOG_ERROR(Service_NS, "output buffer is too small! (actual={}, expected_min={})", + out_buffer.size(), payload_size); + R_THROW(ResultUnknown); + } + + const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), + system.GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + + ApplicationDisplayData display_data{}; + + std::memset(display_data.application_name.data(), 0, display_data.application_name.size()); + std::memset(display_data.developer_name.data(), 0, display_data.developer_name.size()); + + std::memcpy(out_buffer.data(), &display_data, payload_size); + *out_size = payload_size; + + R_SUCCEED(); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.h b/src/core/hle/service/ns/read_only_application_control_data_interface.h index ac099435ab..9ac355014d 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.h +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.h @@ -1,12 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/ns/ns_types.h" -#include "core/hle/service/service.h" namespace Service::NS { @@ -16,7 +19,6 @@ public: explicit IReadOnlyApplicationControlDataInterface(Core::System& system_); ~IReadOnlyApplicationControlDataInterface() override; -public: Result GetApplicationControlData(OutBuffer out_buffer, Out out_actual_size, ApplicationControlSource application_control_source, @@ -25,6 +27,10 @@ public: u32 supported_languages); Result ConvertApplicationLanguageToLanguageCode(Out out_language_code, ApplicationLanguage application_language); + + Result GetApplicationDisplayData(OutBuffer out_buffer, + Out out_size, u64 language_code, + u64 application_id); }; } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_record_interface.cpp b/src/core/hle/service/ns/read_only_application_record_interface.cpp index 816a1e1dc8..18f9e41d8c 100644 --- a/src/core/hle/service/ns/read_only_application_record_interface.cpp +++ b/src/core/hle/service/ns/read_only_application_record_interface.cpp @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ns/read_only_application_record_interface.h" +#include "core/hle/service/ns/ns_types.h" +#include "core/hle/service/ns/application_manager_interface.h" namespace Service::NS { @@ -13,6 +18,8 @@ IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::S {1, nullptr, "NotifyApplicationFailure"}, {2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>, "IsDataCorruptedResult"}, + {3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>, + "ListApplicationRecord"}, }; // clang-format on @@ -35,4 +42,14 @@ Result IReadOnlyApplicationRecordInterface::IsDataCorruptedResult( R_SUCCEED(); } +Result IReadOnlyApplicationRecordInterface::ListApplicationRecord( + OutArray out_records, Out out_count, + s32 entry_offset) { + LOG_DEBUG(Service_NS, "delegating to IApplicationManagerInterface::ListApplicationRecord, offset={} limit={}", + entry_offset, out_records.size()); + + R_RETURN(IApplicationManagerInterface(system).ListApplicationRecord(out_records, out_count, + entry_offset)); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_record_interface.h b/src/core/hle/service/ns/read_only_application_record_interface.h index d06e8f5e66..fc43c768ba 100644 --- a/src/core/hle/service/ns/read_only_application_record_interface.h +++ b/src/core/hle/service/ns/read_only_application_record_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,6 +8,7 @@ #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" +#include "core/hle/service/ns/ns_types.h" namespace Service::NS { @@ -17,6 +21,9 @@ public: private: Result HasApplicationRecord(Out out_has_application_record, u64 program_id); Result IsDataCorruptedResult(Out out_is_data_corrupted_result, Result result); + Result ListApplicationRecord( + OutArray out_records, Out out_count, + s32 entry_offset); }; } // namespace Service::NS diff --git a/src/core/hle/service/pctl/parental_control_service.cpp b/src/core/hle/service/pctl/parental_control_service.cpp index 82c65ac1fd..1310be64b1 100644 --- a/src/core/hle/service/pctl/parental_control_service.cpp +++ b/src/core/hle/service/pctl/parental_control_service.cpp @@ -38,6 +38,7 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili {1016, nullptr, "ConfirmShowNewsPermission"}, {1017, D<&IParentalControlService::EndFreeCommunication>, "EndFreeCommunication"}, {1018, D<&IParentalControlService::IsFreeCommunicationAvailable>, "IsFreeCommunicationAvailable"}, + {1019, D<&IParentalControlService::ConfirmLaunchApplicationPermission>, "ConfirmLaunchApplicationPermission"}, {1031, D<&IParentalControlService::IsRestrictionEnabled>, "IsRestrictionEnabled"}, {1032, D<&IParentalControlService::GetSafetyLevel>, "GetSafetyLevel"}, {1033, nullptr, "SetSafetyLevel"}, diff --git a/src/hid_core/resources/npad/npad_types.h b/src/hid_core/resources/npad/npad_types.h index 92700d69a6..68b803c3d3 100644 --- a/src/hid_core/resources/npad/npad_types.h +++ b/src/hid_core/resources/npad/npad_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -159,6 +162,17 @@ struct NpadGcTriggerState { }; static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); +// This is nn::hid::NpadCondition (global controller condition structure) +struct NpadCondition { + u32 _00{}; + u32 is_initialized{1}; + u32 hold_type{static_cast(NpadJoyHoldType::Horizontal)}; + u32 is_valid{1}; +}; +static_assert(sizeof(NpadCondition) == 0x10, "NpadCondition is an invalid size"); + + + // This is nn::hid::NpadSystemProperties struct NPadSystemProperties { union { diff --git a/src/hid_core/resources/ring_lifo.h b/src/hid_core/resources/ring_lifo.h index 0816784e08..55ba2f0e61 100644 --- a/src/hid_core/resources/ring_lifo.h +++ b/src/hid_core/resources/ring_lifo.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -44,8 +47,7 @@ struct Lifo { buffer_count++; } buffer_tail = GetNextEntryIndex(); - const auto& previous_entry = ReadPreviousEntry(); - entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1; + entries[buffer_tail].sampling_number = new_state.sampling_number << 1; entries[buffer_tail].state = new_state; } }; diff --git a/src/hid_core/resources/shared_memory_format.h b/src/hid_core/resources/shared_memory_format.h index 49755c8dc3..b9deeec302 100644 --- a/src/hid_core/resources/shared_memory_format.h +++ b/src/hid_core/resources/shared_memory_format.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -201,7 +204,9 @@ static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20, // This is nn::hid::detail::SharedMemoryFormat struct SharedMemoryFormat { - void Initialize() {} + void Initialize() { + npad_condition = NpadCondition{}; + } DebugPadSharedMemoryFormat debug_pad; TouchScreenSharedMemoryFormat touch_screen; @@ -218,7 +223,9 @@ struct SharedMemoryFormat { ConsoleSixAxisSensorSharedMemoryFormat console; INSERT_PADDING_BYTES(0x19E0); MouseSharedMemoryFormat debug_mouse; - INSERT_PADDING_BYTES(0x2000); + INSERT_PADDING_BYTES(0x200); + NpadCondition npad_condition; + INSERT_PADDING_BYTES(0x1DF0); }; static_assert(offsetof(SharedMemoryFormat, debug_pad) == 0x0, "debug_pad has wrong offset"); static_assert(offsetof(SharedMemoryFormat, touch_screen) == 0x400, "touch_screen has wrong offset"); @@ -236,6 +243,8 @@ static_assert(offsetof(SharedMemoryFormat, npad) == 0x9A00, "npad has wrong offs static_assert(offsetof(SharedMemoryFormat, gesture) == 0x3BA00, "gesture has wrong offset"); static_assert(offsetof(SharedMemoryFormat, console) == 0x3C200, "console has wrong offset"); static_assert(offsetof(SharedMemoryFormat, debug_mouse) == 0x3DC00, "debug_mouse has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, npad_condition) == 0x3E200, + "npad_condition has wrong offset"); static_assert(sizeof(SharedMemoryFormat) == 0x40000, "SharedMemoryFormat is an invalid size"); } // namespace Service::HID diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index aa931f113e..2c47a37526 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -45,6 +45,10 @@ if (NOT APPLE AND ENABLE_OPENGL) target_compile_definitions(qt_common PUBLIC HAS_OPENGL) endif() -if (NOT WIN32) - target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) +if (UNIX AND NOT APPLE) + if (TARGET Qt6::GuiPrivate) + target_link_libraries(qt_common PRIVATE Qt6::GuiPrivate) + else() + target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + endif() endif() diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 388c8034c5..9eaa322c9c 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -386,8 +386,7 @@ void BufferCache

::BindHostComputeBuffers() { template void BufferCache

::SetUniformBuffersState(const std::array& mask, const UniformBufferSizes* sizes) { - const bool mask_changed = channel_state->enabled_uniform_buffer_masks != mask; - if (mask_changed) { + if (channel_state->enabled_uniform_buffer_masks != mask) { channel_state->fast_bound_uniform_buffers.fill(0); if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { channel_state->dirty_uniform_buffers.fill(~u32{0}); @@ -818,18 +817,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan const std::span span = runtime.BindMappedUniformBuffer(stage, binding_index, size); -#ifdef YUZU_DEBUG - ASSERT(binding_index < NUM_GRAPHICS_UNIFORM_BUFFERS); - ASSERT(span.size() >= size && "UBO stream span too small"); - if (!device_memory.ReadBlockFastChecked(device_addr, span.data(), size)) { - LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", device_addr, size); - channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); - ASSERT(false); - return; - } -#else device_memory.ReadBlockUnsafe(device_addr, span.data(), size); -#endif return; } // Classic cached path @@ -838,8 +826,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } // Skip binding if it's not needed and if the bound buffer is not the fast version // This exists to avoid instances where the fast buffer is bound and a GPU write happens - const bool was_fast_bound = HasFastUniformBufferBound(stage, binding_index); - needs_bind |= was_fast_bound; + needs_bind |= HasFastUniformBufferBound(stage, binding_index); if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { needs_bind |= channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; } diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 09631ffd83..1b551931a4 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -53,6 +53,7 @@ constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; constexpr u32 NUM_STORAGE_BUFFERS = 16; constexpr u32 NUM_TEXTURE_BUFFERS = 32; constexpr u32 NUM_STAGES = 5; + static_assert(NUM_GRAPHICS_UNIFORM_BUFFERS <= 32, "fast bitmask must fit u32"); using UniformBufferSizes = std::array, NUM_STAGES>; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 55565e3d79..0f0d27c6f7 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -337,6 +337,11 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m uint8_pass = std::make_unique(device, scheduler, descriptor_pool, staging_pool, compute_pass_descriptor_queue); } + const u32 ubo_align = static_cast( + device.GetUniformBufferAlignment() //check if the device has it + ); + // add the ability to change the size in settings in future + uniform_ring.Init(device, memory_allocator, 8 * 1024 * 1024 /* 8 MiB */, ubo_align ? ubo_align : 256); quad_array_index_buffer = std::make_shared(device_, memory_allocator_, scheduler_, staging_pool_); quad_strip_index_buffer = std::make_shared(device_, memory_allocator_, @@ -355,6 +360,42 @@ void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) { staging_pool.FreeDeferred(ref); } +void BufferCacheRuntime::UniformRing::Init(const Device& device, + MemoryAllocator& alloc, + u64 bytes, u32 alignment) { + for (size_t i = 0; i < NUM_FRAMES; ++i) { + VkBufferCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = bytes, + .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + buffers[i] = alloc.CreateBuffer(ci, MemoryUsage::Upload); + mapped[i] = buffers[i].Mapped().data(); + } + size = bytes; + align = alignment ? alignment : 256; + head = 0; + current_frame = 0; +} + +std::span BufferCacheRuntime::UniformRing::Alloc(u32 bytes, u32& out_offset) { + const u64 aligned = Common::AlignUp(head, static_cast(align)); + u64 end = aligned + bytes; + + if (end > size) { + return {}; // Fallback to staging pool + } + + out_offset = static_cast(aligned); + head = end; + return {mapped[current_frame] + out_offset, bytes}; +} + u64 BufferCacheRuntime::GetDeviceLocalMemory() const { return device.GetDeviceLocalMemory(); } @@ -375,6 +416,7 @@ void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noe for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { it->ResetUsageTracking(); } + uniform_ring.BeginFrame(); } void BufferCacheRuntime::Finish() { diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index efe960258c..86bce01596 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -124,8 +127,15 @@ public: void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); - std::span BindMappedUniformBuffer([[maybe_unused]] size_t stage, - [[maybe_unused]] u32 binding_index, u32 size) { + std::span BindMappedUniformBuffer([[maybe_unused]] size_t /*stage*/, + [[maybe_unused]] u32 /*binding_index*/, + u32 size) { + u32 offset = 0; + if (auto span = uniform_ring.Alloc(size, offset); !span.empty()) { + BindBuffer(*uniform_ring.buffers[uniform_ring.current_frame], offset, size); + return span; + } + // Fallback for giant requests const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload); BindBuffer(ref.buffer, static_cast(ref.offset), size); return ref.mapped_span; @@ -153,6 +163,24 @@ private: void ReserveNullBuffer(); vk::Buffer CreateNullBuffer(); + struct UniformRing { + static constexpr size_t NUM_FRAMES = 3; + std::array buffers{}; + std::array mapped{}; + u64 size = 0; + u64 head = 0; + u32 align = 256; + size_t current_frame = 0; + + void Init(const Device& device, MemoryAllocator& alloc, u64 bytes, u32 alignment); + void BeginFrame() { + current_frame = (current_frame + 1) % NUM_FRAMES; + head = 0; + } + std::span Alloc(u32 bytes, u32& out_offset); + }; + UniformRing uniform_ring; + const Device& device; MemoryAllocator& memory_allocator; Scheduler& scheduler; diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 0fbe707b04..08513d1534 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -25,48 +25,35 @@ namespace { using namespace Common::Literals; -// Minimum alignment we want to enforce for the streaming ring -constexpr VkDeviceSize MIN_STREAM_ALIGNMENT = 256; +// Maximum potential alignment of a Vulkan buffer +constexpr VkDeviceSize MAX_ALIGNMENT = 256; // Stream buffer size in bytes constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; -size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) { +size_t GetStreamBufferSize(const Device& device) { VkDeviceSize size{0}; if (device.HasDebuggingToolAttached()) { - bool found_heap = false; - ForEachDeviceLocalHostVisibleHeap(device, [&size, &found_heap](size_t /*index*/, VkMemoryHeap& heap) { + ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) { size = (std::max)(size, heap.size); - found_heap = true; }); - // If no suitable heap was found fall back to the default cap to avoid creating a zero-sized stream buffer. - if (!found_heap) { - size = MAX_STREAM_BUFFER_SIZE; - } else if (size <= 256_MiB) { - // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be - // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue - // as the heap will be much larger. + // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be + // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue + // as the heap will be much larger. + if (size <= 256_MiB) { size = size * 40 / 100; } } else { size = MAX_STREAM_BUFFER_SIZE; } - - // Clamp to the configured maximum, align up for safety, and ensure a sane minimum so - // region_size (stream_buffer_size / NUM_SYNCS) never becomes zero. - const VkDeviceSize aligned = - (std::min)(Common::AlignUp(size, alignment), MAX_STREAM_BUFFER_SIZE); - const VkDeviceSize min_size = alignment * StagingBufferPool::NUM_SYNCS; - return static_cast((std::max)(aligned, min_size)); + return (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); } } // Anonymous namespace StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_) : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, - stream_alignment{std::max(device_.GetUniformBufferAlignment(), - MIN_STREAM_ALIGNMENT)}, - stream_buffer_size{GetStreamBufferSize(device_, stream_alignment)}, - region_size{stream_buffer_size / StagingBufferPool::NUM_SYNCS} { + stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size / + StagingBufferPool::NUM_SYNCS} { VkBufferCreateInfo stream_ci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, @@ -119,54 +106,31 @@ void StagingBufferPool::TickFrame() { } StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { - const size_t alignment = static_cast(stream_alignment); - const size_t aligned_size = Common::AlignUp(size, alignment); - const bool wraps = iterator + size >= stream_buffer_size; - const size_t new_iterator = - wraps ? aligned_size : Common::AlignUp(iterator + size, alignment); - const size_t begin_region = wraps ? 0 : Region(iterator); - const size_t last_byte = new_iterator == 0 ? 0 : new_iterator - 1; - const size_t end_region = (std::min)(Region(last_byte) + 1, NUM_SYNCS); - const size_t guard_begin = (std::min)(Region(free_iterator) + 1, NUM_SYNCS); - - if (!wraps) { - if (guard_begin < end_region && AreRegionsActive(guard_begin, end_region)) { - // Avoid waiting for the previous usages to be free - return GetStagingBuffer(size, MemoryUsage::Upload); - } - } else if (guard_begin < NUM_SYNCS && AreRegionsActive(guard_begin, NUM_SYNCS)) { + if (AreRegionsActive(Region(free_iterator) + 1, + (std::min)(Region(iterator + size) + 1, NUM_SYNCS))) { // Avoid waiting for the previous usages to be free return GetStagingBuffer(size, MemoryUsage::Upload); } - const u64 current_tick = scheduler.CurrentTick(); std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator), current_tick); used_iterator = iterator; + free_iterator = (std::max)(free_iterator, iterator + size); - if (wraps) { + if (iterator + size >= stream_buffer_size) { std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, current_tick); used_iterator = 0; iterator = 0; - free_iterator = aligned_size; - const size_t head_last_byte = aligned_size == 0 ? 0 : aligned_size - 1; - const size_t head_end_region = (std::min)(Region(head_last_byte) + 1, NUM_SYNCS); - if (AreRegionsActive(0, head_end_region)) { + free_iterator = size; + + if (AreRegionsActive(0, Region(size) + 1)) { // Avoid waiting for the previous usages to be free return GetStagingBuffer(size, MemoryUsage::Upload); } } - - std::fill(sync_ticks.begin() + begin_region, sync_ticks.begin() + end_region, current_tick); - - const size_t offset = wraps ? 0 : iterator; - iterator = new_iterator; - - if (!wraps) { - free_iterator = (std::max)(free_iterator, offset + aligned_size); - } - + const size_t offset = iterator; + iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT); return StagingBufferRef{ .buffer = *stream_buffer, .offset = static_cast(offset), diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index 5c40ca069f..f63a203272 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -1,6 +1,3 @@ -// 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-3.0-or-later @@ -105,7 +102,6 @@ private: MemoryAllocator& memory_allocator; Scheduler& scheduler; - VkDeviceSize stream_alignment; vk::Buffer stream_buffer; std::span stream_pointer; VkDeviceSize stream_buffer_size; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index c03f7a3abf..a8e3953716 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -393,16 +393,8 @@ target_link_libraries(yuzu PRIVATE common core input_common frontend_common netw target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) -if (NOT WIN32) - target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) -endif() - if (UNIX AND NOT APPLE) target_link_libraries(yuzu PRIVATE Qt6::DBus) - - if (TARGET Qt6::GuiPrivate) - target_link_libraries(yuzu PRIVATE Qt6::GuiPrivate) - endif() endif() target_compile_definitions(yuzu PRIVATE diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 18f629f639..b825348760 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -83,8 +83,7 @@ void ConfigureDebug::SetConfiguration() { #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); #else - ui->disable_web_applet->setEnabled(false); - ui->disable_web_applet->setText(tr("Web applet not compiled")); + ui->disable_web_applet->setVisible(false); #endif }