Compare commits

...
Sign in to create a new pull request.

10 commits

Author SHA1 Message Date
a3ef2cc183
[audio_core/hid] Audio REV12+15 support + HID fixes (#2719)
This fixes newer updates / games.

Implements partial audio rev15, rev13, rev12 and HID issues on SDK20+ games.

Credits to LotP (Ryubing) and Zephyron (Citron) for their research and implementation.

Co-authored-by: Zephyron <zephyron@citron-emu.org>
Co-authored-by: Shinmegumi <shinmegumi@eden-emu.dev>
Reviewed-on: #2719
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: unknown <sahyno1996@gmail.com>
Co-committed-by: unknown <sahyno1996@gmail.com>
2025-10-12 17:03:14 +02:00
1e1b8ad33f
[common] Properly skip Custom Web Applet if YUZU_USE_QT_WEB_ENGINE is not defined (#2717)
This restores the Offline Web Applet LLE setting as default and properly skip Custom Web Applet if YUZU_USE_QT_WEB_ENGINE is not defined preventing crashes.

Reviewed-on: #2717
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-10-11 14:45:14 +02:00
91493fa39b
[vk] Fast UBO: fix tracking (#2712)
Fixes or mitigates memory errors in TOTK and possibly other games as well.

Credit: Ribbit
Reviewed-on: #2712
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-10-11 06:34:21 +02:00
973a65c4c5
[qt_common] fix typo (#2715)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: #2715
2025-10-11 04:31:14 +02:00
8a017951aa
[qt_common] fix building with Qt 6.10 (#2713)
Qt old style include variables are deprecated in Qt, see <https://github.com/qt/qtbase/blob/v6.10.0/cmake/QtModuleConfig.cmake.in#L84>, and Qt 6.10 stopped exporting them after <ad7b94e163>.

Signed-off-by: Marcin Serwin <marcin@serwin.dev>

Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: #2713
Co-authored-by: Marcin Serwin <marcin@serwin.dev>
Co-committed-by: Marcin Serwin <marcin@serwin.dev>
2025-10-10 22:33:15 +02:00
776958c79d
[vk] Introduce Ring Buffers for Uniform Buffer (#2698)
Create 3 ring buffers which rotates between buffers each frame to avoid GPU/CPU conflicts
BindMappedUniformBuffer first tries to allocate from the ring buffer and falls back to staging pool only if allocation is too large.
Note to testers:- please test the performance since it is primarily a performance optimization and also look for visual bugs.

Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Reviewed-on: #2698
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: Shinmegumi <shinmegumi@eden-emu.dev>
Co-committed-by: Shinmegumi <shinmegumi@eden-emu.dev>
2025-10-10 19:24:20 +02:00
3656253262
[acc] do not consider system profile as orphaned (#2708)
Profile 00000000000000000000000000000000 is apparently needed for acnh,
etc

Signed-off-by: crueter <crueter@eden-emu.dev>

Reviewed-on: #2708
2025-10-10 05:59:31 +02:00
b6241e4148
revert [vk] StreamBuffer Changes (#2684) (#2707)
revert [vk] StreamBuffer Changes (#2684)

Streambuffer changes did broke stuff in other games that got out of our scope of testing, we're going to study this changes in the future for better graphic stability.

Co-authored-by: Ribbit <ribbit@placeholder.com>
Reviewed-on: #2684
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: Ribbit <ribbit@eden-emu.dev>
Co-committed-by: Ribbit <ribbit@eden-emu.dev>
Reviewed-on: #2707
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Co-committed-by: CamilleLaVey <camillelavey99@gmail.com>
2025-10-10 01:55:43 +02:00
bfffafe68b
[common] Change web offline applet default setting to HLE (#2705)
This prevents some games from ignoring the disable web applet setting.

Reviewed-on: #2705
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-10-10 01:36:55 +02:00
3c6ef765af
revert [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695) (#2706)
revert [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695)

Well, stuff showed up after testing phase, that showed us this change break SMO and some mods after being merged directly into master, we will keep stuying why happens this and add a better handling later.

Co-authored-by: Ribbit <ribbit@placeholder.com>
Reviewed-on: #2695
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: Ribbit <ribbit@eden-emu.dev>
Co-committed-by: Ribbit <ribbit@eden-emu.dev>

Reviewed-on: #2706
Co-authored-by: CamilleLaVey <camillelavey99@gmail.com>
Co-committed-by: CamilleLaVey <camillelavey99@gmail.com>
2025-10-09 21:37:27 +02:00
40 changed files with 880 additions and 193 deletions

View file

@ -16,7 +16,7 @@
#include <ranges>
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 =

View file

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

View file

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

View file

@ -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<const VoiceInfo::InParameter> in_params{
reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(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<u32>(sizeof(VoiceInfo::InParameter));
for (u32 i = 0; i < voice_count; i++) {
const auto& in_param{in_params[i]};
VoiceInfo::InParameter local_in{};
std::array<VoiceInfo::BiquadFilterParameter2, MaxBiquadFilters> float_biquads{};
if (!use_v2) {
const auto* in_param_ptr = reinterpret_cast<const VoiceInfo::InParameter*>(
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<f32, 3> b;
std::array<f32, 2> 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<VoiceInfo::WaveBufferInternal, MaxWaveBuffers> wavebuffers;
std::array<u32, MaxChannels> 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<const VoiceInParameterV2*>(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<s32>(vin->priority);
local_in.sort_order = static_cast<s32>(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<s16>(std::clamp(src.b[0] * 16384.0f, -32768.0f, 32767.0f));
dst.b[1] = static_cast<s16>(std::clamp(src.b[1] * 16384.0f, -32768.0f, 32767.0f));
dst.b[2] = static_cast<s16>(std::clamp(src.b[2] * 16384.0f, -32768.0f, 32767.0f));
dst.a[0] = static_cast<s16>(std::clamp(src.a[0] * 16384.0f, -32768.0f, 32767.0f));
dst.a[1] = static_cast<s16>(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<u16>(vin->wave_buffer_index);
local_in.src_data_address = static_cast<CpuAddr>(vin->src_data_address);
local_in.src_data_size = vin->src_data_size;
local_in.mix_id = static_cast<u32>(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<VoiceState*, MaxChannels> 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<u32>(sizeof(VoiceInfo::InParameter))};
auto consumed_input_size{voice_count * in_stride};
auto consumed_output_size{voice_count * static_cast<u32>(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<const MixInfo::InDirtyParameter*>(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<u32>(sizeof(MixInfo::InDirtyParameter));
input += sizeof(MixInfo::InDirtyParameter);
consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
mix_count * sizeof(MixInfo::InParameter));
} else {
mix_count = mix_context.GetCount();
consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
}
input_mix_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
consumed_input_size += input_mix_size;
if (mix_buffer_count == 0) {
return Service::Audio::ResultInvalidUpdateInfo;
}

View file

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

View file

@ -51,6 +51,40 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
state.s3 = Common::BitCast<s64>(s[3]);
}
/**
* Biquad filter float implementation with native float coefficients.
*/
void ApplyBiquadFilterFloat2(std::span<s32> output, std::span<const s32> input,
std::array<f32, 3>& b, std::array<f32, 2>& a,
VoiceState::BiquadFilterState& state, const u32 sample_count) {
constexpr f64 min{std::numeric_limits<s32>::min()};
constexpr f64 max{std::numeric_limits<s32>::max()};
std::array<f64, 3> b_double{static_cast<f64>(b[0]), static_cast<f64>(b[1]),
static_cast<f64>(b[2])};
std::array<f64, 2> a_double{static_cast<f64>(a[0]), static_cast<f64>(a[1])};
std::array<f64, 4> s{Common::BitCast<f64>(state.s0), Common::BitCast<f64>(state.s1),
Common::BitCast<f64>(state.s2), Common::BitCast<f64>(state.s3)};
for (u32 i = 0; i < sample_count; i++) {
f64 in_sample{static_cast<f64>(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<s32>(std::clamp(sample, min, max));
s[1] = s[0];
s[0] = in_sample;
s[3] = s[2];
s[2] = sample;
}
state.s0 = Common::BitCast<s64>(s[0]);
state.s1 = Common::BitCast<s64>(s[1]);
state.s2 = Common::BitCast<s64>(s[2]);
state.s3 = Common::BitCast<s64>(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);

View file

@ -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<s32> output, std::span<const s32> input,
std::array<s16, 3>& b, std::array<s16, 2>& 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<s32> output, std::span<const s32> input,
std::array<f32, 3>& b, std::array<f32, 2>& a,
VoiceState::BiquadFilterState& state, const u32 sample_count);
} // namespace AudioCore::Renderer

View file

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

View file

@ -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<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
/// Biquad parameters (REV15+ native float)
std::array<VoiceInfo::BiquadFilterParameter2, MaxBiquadFilters> biquads_float;
/// Biquad states, updated each call
std::array<CpuAddr, MaxBiquadFilters> states;
/// If each biquad needs initialisation
std::array<bool, MaxBiquadFilters> needs_init;
/// Number of active biquads
u8 filter_tap_count;
/// If true, use native float coefficients (REV15+)
bool use_float_coefficients;
};
} // namespace AudioCore::Renderer

View file

@ -35,12 +35,16 @@ SplitterDestinationData& SplitterContext::GetData(const u32 index) {
void SplitterContext::Setup(std::span<SplitterInfo> 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<const SplitterDestinationData::InParameter*>(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<const SplitterDestinationData::InParameter*>(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<const SplitterDestinationData::InParameterVersion2a*>(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<float>(1 << 14);
out.numerator[0] = static_cast<float>(legacy.b[0]) * scale;
out.numerator[1] = static_cast<float>(legacy.b[1]) * scale;
out.numerator[2] = static_cast<float>(legacy.b[2]) * scale;
out.denominator[0] = static_cast<float>(legacy.a[0]) * scale;
out.denominator[1] = static_cast<float>(legacy.a[1]) * scale;
}
offset += static_cast<u32>(sizeof(SplitterDestinationData::InParameterVersion2a));
} else {
// Version 2b: struct contains extra biquad filter fields with float coeffs
const auto* data_header_v2b =
reinterpret_cast<const SplitterDestinationData::InParameterVersion2b*>(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<u32>(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;

View file

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

View file

@ -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::BiquadFilterParameter2>
SplitterDestinationData::GetBiquadFilters() {
return biquad_filters;
}
std::span<const SplitterDestinationData::BiquadFilterParameter2>
SplitterDestinationData::GetBiquadFilters() const {
return biquad_filters;
}
} // namespace AudioCore::Renderer

View file

@ -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<f32, 3> numerator; // b0, b1, b2
/* 0x10 */ std::array<f32, 2> 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<s16, 3> b; // numerator
/* 0x08 */ std::array<s16, 2> 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<f32, MaxMixBuffers> 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<f32, MaxMixBuffers> mix_volumes;
/* 0x68 */ u32 mix_id;
/* 0x6C */ std::array<SplitterDestinationData::BiquadFilterParameterLegacy, MaxBiquadFilters>
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<f32, MaxMixBuffers> mix_volumes;
/* 0x68 */ u32 mix_id;
/* 0x6C */ std::array<SplitterDestinationData::BiquadFilterParameter2, MaxBiquadFilters>
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<BiquadFilterParameter2> GetBiquadFilters();
/**
* Get const biquad filter parameters for this destination (REV15+ or mapped from REV12).
*
* @return Const span of biquad filter parameters.
*/
std::span<const BiquadFilterParameter2> GetBiquadFilters() const;
private:
/// Id of this destination
const s32 id;
@ -124,6 +197,8 @@ private:
std::array<f32, MaxMixBuffers> mix_volumes{0.0f};
/// Previous mix volumes
std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f};
/// Biquad filter parameters (REV15+ or mapped from REV12)
std::array<BiquadFilterParameter2, MaxBiquadFilters> biquad_filters{};
/// Next destination in the mix chain
SplitterDestinationData* next{};
/// Is this destination in use?

View file

@ -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<f32, 3> numerator; // b0, b1, b2
/* 0x10 */ std::array<f32, 2> 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<BiquadFilterParameter2, MaxBiquadFilters> 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<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
/* 0x158 */ std::array<s32, MaxChannels> 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<BiquadFilterParameter, MaxBiquadFilters> biquads{};
/// Float biquad filters for REV15+ (native float coefficients)
std::array<BiquadFilterParameter2, MaxBiquadFilters> biquads_float{};
/// Use float biquad coefficients (REV15+)
bool use_float_biquads{};
/// Number of active wavebuffers
u32 wave_buffer_count{};
/// Current playing wavebuffer index

View file

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

View file

@ -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<Traits>::ReadBlockUnsafe(DAddr address, void* dest_poin
});
}
#ifdef YUZU_DEBUG
template <typename Traits>
bool DeviceMemoryManager<Traits>::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<u8*>(dest_pointer) + copy_amount;
});
return success;
}
#endif
template <typename Traits>
void DeviceMemoryManager<Traits>::WriteBlockUnsafe(DAddr address, const void* src_pointer,
size_t size) {

View file

@ -509,6 +509,9 @@ std::vector<std::string> 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";

View file

@ -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<u8>& 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<u8> 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<u8>& 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<u8> 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<const char*, 7> 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> 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() {

View file

@ -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<SharedPointer<ILibraryAppletAccessor>> 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<ILibraryAppletAccessor> 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<SharedPointer<IStorage>> out_storage, s64 size) {
LOG_DEBUG(Service_AM, "called, size={}", size);

View file

@ -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<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
LibraryAppletMode library_applet_mode);
Result CreateLibraryAppletEx(
Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
LibraryAppletMode library_applet_mode, u64 thread_id);
Result CreateStorage(Out<SharedPointer<IStorage>> out_storage, s64 size);
Result CreateTransferMemoryStorage(
Out<SharedPointer<IStorage>> out_storage, bool is_writable, s64 size,

View file

@ -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<Result> 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<BufferAttr_HipcAutoSelect> out_buffer_58,
InBuffer<BufferAttr_HipcMapAlias> 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<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_NS, "(STUBBED) called");
@ -539,4 +573,9 @@ Result IApplicationManagerInterface::Unknown4023(Out<u64> out_result) {
R_SUCCEED();
}
Result IApplicationManagerInterface::Unknown4053() {
LOG_WARNING(Service_NS, "(STUBBED) called.");
R_SUCCEED();
}
} // namespace Service::NS

View file

@ -55,6 +55,12 @@ public:
Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result Unknown4023(Out<u64> out_result);
Result Unknown4053();
Result RequestDownloadApplicationControlDataInBackground(u64 unk,
u64 application_id);
Result Unknown1706(OutBuffer<BufferAttr_HipcAutoSelect> out_buffer_58,
InBuffer<BufferAttr_HipcMapAlias> in_buffer_8);
private:
KernelHelpers::ServiceContext service_context;

View file

@ -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<u8, 0x8> 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<char, 0x200> application_name;
std::array<char, 0x100> developer_name;
};
static_assert(sizeof(ApplicationDisplayData) == 0x300, "ApplicationDisplayData has incorrect size.");
} // namespace Service::NS

View file

@ -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<BufferAttr_HipcMapAlias> out_buffer, Out<u64> 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

View file

@ -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<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> out_actual_size,
ApplicationControlSource application_control_source,
@ -25,6 +27,10 @@ public:
u32 supported_languages);
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
ApplicationLanguage application_language);
Result GetApplicationDisplayData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u64> out_size, u64 language_code,
u64 application_id);
};
} // namespace Service::NS

View file

@ -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<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> 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

View file

@ -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<bool> out_has_application_record, u64 program_id);
Result IsDataCorruptedResult(Out<bool> out_is_data_corrupted_result, Result result);
Result ListApplicationRecord(
OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> out_count,
s32 entry_offset);
};
} // namespace Service::NS

View file

@ -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"},

View file

@ -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<u32>(NpadJoyHoldType::Horizontal)};
u32 is_valid{1};
};
static_assert(sizeof(NpadCondition) == 0x10, "NpadCondition is an invalid size");
// This is nn::hid::NpadSystemProperties
struct NPadSystemProperties {
union {

View file

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

View file

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

View file

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

View file

@ -386,8 +386,7 @@ void BufferCache<P>::BindHostComputeBuffers() {
template <class P>
void BufferCache<P>::SetUniformBuffersState(const std::array<u32, NUM_STAGES>& 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<P>::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<u8> 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<P>::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;
}

View file

@ -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<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>;

View file

@ -337,6 +337,11 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
uint8_pass = std::make_unique<Uint8Pass>(device, scheduler, descriptor_pool, staging_pool,
compute_pass_descriptor_queue);
}
const u32 ubo_align = static_cast<u32>(
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<QuadArrayIndexBuffer>(device_, memory_allocator_,
scheduler_, staging_pool_);
quad_strip_index_buffer = std::make_shared<QuadStripIndexBuffer>(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<u8> BufferCacheRuntime::UniformRing::Alloc(u32 bytes, u32& out_offset) {
const u64 aligned = Common::AlignUp(head, static_cast<u64>(align));
u64 end = aligned + bytes;
if (end > size) {
return {}; // Fallback to staging pool
}
out_offset = static_cast<u32>(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<Buffer>& slot_buffers) noe
for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
it->ResetUsageTracking();
}
uniform_ring.BeginFrame();
}
void BufferCacheRuntime::Finish() {

View file

@ -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<Buffer>& bindings);
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
[[maybe_unused]] u32 binding_index, u32 size) {
std::span<u8> 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<u32>(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<vk::Buffer, NUM_FRAMES> buffers{};
std::array<u8*, NUM_FRAMES> 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<u8> Alloc(u32 bytes, u32& out_offset);
};
UniformRing uniform_ring;
const Device& device;
MemoryAllocator& memory_allocator;
Scheduler& scheduler;

View file

@ -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<size_t>((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<VkDeviceSize>(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<size_t>(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<VkDeviceSize>(offset),

View file

@ -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<u8> stream_pointer;
VkDeviceSize stream_buffer_size;

View file

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

View file

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