summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/CMakeLists.txt2
-rw-r--r--src/audio_core/command_generator.cpp357
-rw-r--r--src/audio_core/command_generator.h5
-rw-r--r--src/audio_core/common.h23
-rw-r--r--src/audio_core/delay_line.cpp104
-rw-r--r--src/audio_core/delay_line.h46
-rw-r--r--src/audio_core/effect_context.cpp22
-rw-r--r--src/audio_core/effect_context.h31
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/frontend/applets/controller.h1
-rw-r--r--src/core/hle/service/am/am.cpp13
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp63
-rw-r--r--src/core/hle/service/hid/controllers/npad.h26
-rw-r--r--src/core/hle/service/ldn/errors.h13
-rw-r--r--src/core/hle/service/ldn/ldn.cpp36
-rw-r--r--src/input_common/settings.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.h2
-rw-r--r--src/video_core/shader/async_shaders.cpp1
-rw-r--r--src/yuzu/CMakeLists.txt1
-rw-r--r--src/yuzu/applets/controller.cpp9
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp106
-rw-r--r--src/yuzu/configuration/configure_input_player.h6
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp3
-rw-r--r--src/yuzu/debugger/controller.cpp2
-rw-r--r--src/yuzu/main.cpp10
-rw-r--r--src/yuzu/main.ui8
-rw-r--r--src/yuzu/yuzu.qrc5
-rw-r--r--src/yuzu_cmd/CMakeLists.txt13
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp17
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h3
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp2
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp2
33 files changed, 895 insertions, 54 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index d1d177b51..a0ae07752 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -15,6 +15,8 @@ add_library(audio_core STATIC
command_generator.cpp
command_generator.h
common.h
+ delay_line.cpp
+ delay_line.h
effect_context.cpp
effect_context.h
info_updater.cpp
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
index 5b1065520..437cc5ccd 100644
--- a/src/audio_core/command_generator.cpp
+++ b/src/audio_core/command_generator.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cmath>
+#include <numbers>
#include "audio_core/algorithm/interpolate.h"
#include "audio_core/command_generator.h"
#include "audio_core/effect_context.h"
@@ -13,6 +15,20 @@ namespace AudioCore {
namespace {
constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
+using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>;
+
+constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f};
+constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f};
+constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f};
+constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f};
+constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{
+ 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f,
+ 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f,
+ 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f};
+constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{
+ 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f,
+ 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f,
+ 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
template <std::size_t N>
void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
@@ -65,6 +81,154 @@ s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
}
}
+float Pow10(float x) {
+ if (x >= 0.0f) {
+ return 1.0f;
+ } else if (x <= -5.3f) {
+ return 0.0f;
+ }
+ return std::pow(10.0f, x);
+}
+
+float SinD(float degrees) {
+ return std::sin(degrees * std::numbers::pi_v<float> / 180.0f);
+}
+
+float CosD(float degrees) {
+ return std::cos(degrees * std::numbers::pi_v<float> / 180.0f);
+}
+
+float ToFloat(s32 sample) {
+ return static_cast<float>(sample) / 65536.f;
+}
+
+s32 ToS32(float sample) {
+ constexpr auto min = -8388608.0f;
+ constexpr auto max = 8388607.f;
+ float rescaled_sample = sample * 65536.0f;
+ if (rescaled_sample < min) {
+ rescaled_sample = min;
+ }
+ if (rescaled_sample > max) {
+ rescaled_sample = max;
+ }
+ return static_cast<s32>(rescaled_sample);
+}
+
+constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
+ 1, 1, 1, 0, 0, 0, 0, 1, 1, 1};
+
+constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
+ 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
+
+constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2,
+ 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
+
+template <std::size_t CHANNEL_COUNT>
+void ApplyReverbGeneric(I3dl2ReverbState& state,
+ const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input,
+ const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& output,
+ s32 sample_count) {
+
+ auto GetTapLookup = []() {
+ if constexpr (CHANNEL_COUNT == 1) {
+ return REVERB_TAP_INDEX_1CH;
+ } else if constexpr (CHANNEL_COUNT == 2) {
+ return REVERB_TAP_INDEX_2CH;
+ } else if constexpr (CHANNEL_COUNT == 4) {
+ return REVERB_TAP_INDEX_4CH;
+ } else if constexpr (CHANNEL_COUNT == 6) {
+ return REVERB_TAP_INDEX_6CH;
+ }
+ };
+
+ const auto& tap_index_lut = GetTapLookup();
+ for (s32 sample = 0; sample < sample_count; sample++) {
+ std::array<f32, CHANNEL_COUNT> out_samples{};
+ std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{};
+ std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{};
+ std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{};
+
+ // Mix everything into a single sample
+ s32 temp_mixed_sample = 0;
+ for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
+ temp_mixed_sample += input[i][sample];
+ }
+ const auto current_sample = ToFloat(temp_mixed_sample);
+ const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps);
+
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) {
+ const auto tapped_samp =
+ state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i];
+ out_samples[tap_index_lut[i]] += tapped_samp;
+
+ if constexpr (CHANNEL_COUNT == 6) {
+ // handle lfe
+ out_samples[5] += tapped_samp;
+ }
+ }
+
+ state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1;
+ state.early_delay_line.Tick(state.lowpass_0);
+
+ for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
+ out_samples[i] *= state.early_gain;
+ }
+
+ // Two channel seems to apply a latet gain, we require to save this
+ f32 filter{};
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ filter = state.fdn_delay_line[i].GetOutputSample();
+ const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i];
+ state.shelf_filter[i] =
+ filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i];
+ fsamp[i] = computed;
+ }
+
+ // Mixing matrix
+ mixed[0] = fsamp[1] + fsamp[2];
+ mixed[1] = -fsamp[0] - fsamp[3];
+ mixed[2] = fsamp[0] - fsamp[3];
+ mixed[3] = fsamp[1] - fsamp[2];
+
+ if constexpr (CHANNEL_COUNT == 2) {
+ for (auto& mix : mixed) {
+ mix *= (filter * state.late_gain);
+ }
+ }
+
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ const auto late = early_tap * state.late_gain;
+ osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]);
+ osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]);
+ state.fdn_delay_line[i].Tick(osamp[i]);
+ }
+
+ if constexpr (CHANNEL_COUNT == 1) {
+ output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) +
+ (out_samples[0] + osamp[0] + osamp[1]));
+ } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) {
+ for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
+ output[i][sample] =
+ ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
+ }
+ } else if constexpr (CHANNEL_COUNT == 6) {
+ const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3]));
+ for (std::size_t i = 0; i < 4; i++) {
+ output[i][sample] =
+ ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
+ }
+ output[4][sample] =
+ ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center));
+ output[5][sample] =
+ ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3]));
+ }
+ }
+}
+
} // namespace
CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
@@ -271,11 +435,10 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic
}
// Generate biquad filter
- // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
- // dsp_state.biquad_filter_state,
- // mix_buffer_count + channel, mix_buffer_count +
- // channel, worker_params.sample_count,
- // voice_info.GetInParams().node_id);
+ // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
+ // dsp_state.biquad_filter_state,
+ // mix_buffer_count + channel, mix_buffer_count + channel,
+ // worker_params.sample_count, voice_info.GetInParams().node_id);
}
}
@@ -376,21 +539,54 @@ void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
bool enabled) {
- if (!enabled) {
+ auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info);
+ const auto& params = reverb->GetParams();
+ auto& state = reverb->GetState();
+ const auto channel_count = params.channel_count;
+
+ if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) {
return;
}
- const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
- const auto channel_count = params.channel_count;
+
+ std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{};
+ std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> output{};
+
+ const auto status = params.status;
for (s32 i = 0; i < channel_count; i++) {
- // TODO(ogniK): Actually implement reverb
- /*
- if (params.input[i] != params.output[i]) {
- const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
- auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
- ApplyMix<1>(output, input, 32768, worker_params.sample_count);
- }*/
- auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
- std::memset(output, 0, worker_params.sample_count * sizeof(s32));
+ input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]);
+ output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]);
+ }
+
+ if (enabled) {
+ if (status == ParameterStatus::Initialized) {
+ InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer());
+ } else if (status == ParameterStatus::Updating) {
+ UpdateI3dl2Reverb(reverb->GetParams(), state, false);
+ }
+ }
+
+ if (enabled) {
+ switch (channel_count) {
+ case 1:
+ ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count);
+ break;
+ case 2:
+ ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count);
+ break;
+ case 4:
+ ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count);
+ break;
+ case 6:
+ ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count);
+ break;
+ }
+ } else {
+ for (s32 i = 0; i < channel_count; i++) {
+ // Only copy if the buffer input and output do not match!
+ if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) {
+ std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32));
+ }
+ }
}
}
@@ -528,6 +724,133 @@ s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u3
return sample_count;
}
+void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
+ std::vector<u8>& work_buffer) {
+ // Reset state
+ state.lowpass_0 = 0.0f;
+ state.lowpass_1 = 0.0f;
+ state.lowpass_2 = 0.0f;
+
+ state.early_delay_line.Reset();
+ state.early_tap_steps.fill(0);
+ state.early_gain = 0.0f;
+ state.late_gain = 0.0f;
+ state.early_to_late_taps = 0;
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ state.fdn_delay_line[i].Reset();
+ state.decay_delay_line0[i].Reset();
+ state.decay_delay_line1[i].Reset();
+ }
+ state.last_reverb_echo = 0.0f;
+ state.center_delay_line.Reset();
+ for (auto& coef : state.lpf_coefficients) {
+ coef.fill(0.0f);
+ }
+ state.shelf_filter.fill(0.0f);
+ state.dry_gain = 0.0f;
+
+ const auto sample_rate = info.sample_rate / 1000;
+ f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data());
+
+ s32 delay_samples{};
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ delay_samples =
+ AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]);
+ state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr);
+ work_buffer_ptr += delay_samples + 1;
+
+ delay_samples =
+ AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]);
+ state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
+ work_buffer_ptr += delay_samples + 1;
+
+ delay_samples =
+ AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]);
+ state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
+ work_buffer_ptr += delay_samples + 1;
+ }
+ delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f);
+ state.center_delay_line.Initialize(delay_samples, work_buffer_ptr);
+ work_buffer_ptr += delay_samples + 1;
+
+ delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f);
+ state.early_delay_line.Initialize(delay_samples, work_buffer_ptr);
+
+ UpdateI3dl2Reverb(info, state, true);
+}
+
+void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
+ bool should_clear) {
+
+ state.dry_gain = info.dry_gain;
+ state.shelf_filter.fill(0.0f);
+ state.lowpass_0 = 0.0f;
+ state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f);
+ state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f);
+
+ const auto sample_rate = info.sample_rate / 1000;
+ const f32 hf_gain = Pow10(info.room_hf / 2000.0f);
+ if (hf_gain >= 1.0f) {
+ state.lowpass_2 = 1.0f;
+ state.lowpass_1 = 0.0f;
+ } else {
+ const auto a = 1.0f - hf_gain;
+ const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference /
+ static_cast<f32>(info.sample_rate)));
+ const auto c = std::sqrt(b * b - 4.0f * a * a);
+
+ state.lowpass_1 = (b - c) / (2.0f * a);
+ state.lowpass_2 = 1.0f - state.lowpass_1;
+ }
+ state.early_to_late_taps = AudioCommon::CalculateDelaySamples(
+ sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay));
+
+ state.last_reverb_echo = 0.6f * info.diffusion * 0.01f;
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ const auto length =
+ FDN_MIN_DELAY_LINE_TIMES[i] +
+ (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]);
+ state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length));
+
+ const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() +
+ state.decay_delay_line0[i].GetDelay() +
+ state.decay_delay_line1[i].GetDelay();
+
+ float a = (-60.0f * static_cast<f32>(delay_sample_counts)) /
+ (info.decay_time * static_cast<f32>(info.sample_rate));
+ float b = a / info.hf_decay_ratio;
+ float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) /
+ SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate));
+ float d = Pow10((b - a) / 40.0f);
+ float e = Pow10((b + a) / 40.0f) * 0.7071f;
+
+ state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d);
+ state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d);
+ state.lpf_coefficients[2][i] = (c - d) / (c + d);
+
+ state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo);
+ state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo);
+ }
+
+ if (should_clear) {
+ for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
+ state.fdn_delay_line[i].Clear();
+ state.decay_delay_line0[i].Clear();
+ state.decay_delay_line1[i].Clear();
+ }
+ state.early_delay_line.Clear();
+ state.center_delay_line.Clear();
+ }
+
+ const auto max_early_delay = state.early_delay_line.GetMaxDelay();
+ const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f);
+ for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) {
+ const auto length = AudioCommon::CalculateDelaySamples(
+ sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]);
+ state.early_tap_steps[tap] = std::min(length, max_early_delay);
+ }
+}
+
void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
s32 channel, s32 node_id) {
const auto last = static_cast<s32>(last_volume * 32768.0f);
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
index b937350b1..2ebb755b0 100644
--- a/src/audio_core/command_generator.h
+++ b/src/audio_core/command_generator.h
@@ -21,6 +21,8 @@ class ServerMixInfo;
class EffectContext;
class EffectBase;
struct AuxInfoDSP;
+struct I3dl2ReverbParams;
+struct I3dl2ReverbState;
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
class CommandGenerator {
@@ -80,6 +82,9 @@ private:
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
u32 sample_count, u32 read_offset, u32 read_count);
+ void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
+ std::vector<u8>& work_buffer);
+ void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
// DSP Code
s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
s32 channel, std::size_t mix_offset);
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
index ec59a3ba9..fe546c55d 100644
--- a/src/audio_core/common.h
+++ b/src/audio_core/common.h
@@ -33,6 +33,29 @@ constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this
// and our const ends up being 0x3f04, the 4 bytes are most
// likely the sample history
constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
+constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f;
+constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f;
+constexpr std::size_t I3DL2REVERB_TAPS = 20;
+constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4;
+using Fractional = s32;
+
+template <typename T>
+constexpr Fractional ToFractional(T x) {
+ return static_cast<Fractional>(x * static_cast<T>(0x4000));
+}
+
+constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) {
+ return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14);
+}
+
+constexpr s32 FractionalToFixed(Fractional x) {
+ const auto s = x & (1 << 13);
+ return static_cast<s32>(x >> 14) + s;
+}
+
+constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) {
+ return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time)));
+}
static constexpr u32 VersionFromRevision(u32_le rev) {
// "REV7" -> 7
diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp
new file mode 100644
index 000000000..f4e4dd8d2
--- /dev/null
+++ b/src/audio_core/delay_line.cpp
@@ -0,0 +1,104 @@
+#include <cstring>
+#include "audio_core/delay_line.h"
+
+namespace AudioCore {
+DelayLineBase::DelayLineBase() = default;
+DelayLineBase::~DelayLineBase() = default;
+
+void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) {
+ buffer = src_buffer;
+ buffer_end = buffer + max_delay_;
+ max_delay = max_delay_;
+ output = buffer;
+ SetDelay(max_delay_);
+ Clear();
+}
+
+void DelayLineBase::SetDelay(s32 new_delay) {
+ if (max_delay < new_delay) {
+ return;
+ }
+ delay = new_delay;
+ input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1));
+}
+
+s32 DelayLineBase::GetDelay() const {
+ return delay;
+}
+
+s32 DelayLineBase::GetMaxDelay() const {
+ return max_delay;
+}
+
+f32 DelayLineBase::TapOut(s32 last_sample) {
+ const float* ptr = input - (last_sample + 1);
+ if (ptr < buffer) {
+ ptr += (max_delay + 1);
+ }
+
+ return *ptr;
+}
+
+f32 DelayLineBase::Tick(f32 sample) {
+ *(input++) = sample;
+ const auto out_sample = *(output++);
+
+ if (buffer_end < input) {
+ input = buffer;
+ }
+
+ if (buffer_end < output) {
+ output = buffer;
+ }
+
+ return out_sample;
+}
+
+float* DelayLineBase::GetInput() {
+ return input;
+}
+
+const float* DelayLineBase::GetInput() const {
+ return input;
+}
+
+f32 DelayLineBase::GetOutputSample() const {
+ return *output;
+}
+
+void DelayLineBase::Clear() {
+ std::memset(buffer, 0, sizeof(float) * max_delay);
+}
+
+void DelayLineBase::Reset() {
+ buffer = nullptr;
+ buffer_end = nullptr;
+ max_delay = 0;
+ input = nullptr;
+ output = nullptr;
+ delay = 0;
+}
+
+DelayLineAllPass::DelayLineAllPass() = default;
+DelayLineAllPass::~DelayLineAllPass() = default;
+
+void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) {
+ DelayLineBase::Initialize(delay_, src_buffer);
+ SetCoefficient(coeffcient_);
+}
+
+void DelayLineAllPass::SetCoefficient(float coeffcient_) {
+ coefficient = coeffcient_;
+}
+
+f32 DelayLineAllPass::Tick(f32 sample) {
+ const auto temp = sample - coefficient * *output;
+ return coefficient * temp + DelayLineBase::Tick(temp);
+}
+
+void DelayLineAllPass::Reset() {
+ coefficient = 0.0f;
+ DelayLineBase::Reset();
+}
+
+} // namespace AudioCore
diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h
new file mode 100644
index 000000000..cafddd432
--- /dev/null
+++ b/src/audio_core/delay_line.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "common/common_types.h"
+
+namespace AudioCore {
+
+class DelayLineBase {
+public:
+ DelayLineBase();
+ ~DelayLineBase();
+
+ void Initialize(s32 max_delay_, float* src_buffer);
+ void SetDelay(s32 new_delay);
+ s32 GetDelay() const;
+ s32 GetMaxDelay() const;
+ f32 TapOut(s32 last_sample);
+ f32 Tick(f32 sample);
+ float* GetInput();
+ const float* GetInput() const;
+ f32 GetOutputSample() const;
+ void Clear();
+ void Reset();
+
+protected:
+ float* buffer{nullptr};
+ float* buffer_end{nullptr};
+ s32 max_delay{};
+ float* input{nullptr};
+ float* output{nullptr};
+ s32 delay{};
+};
+
+class DelayLineAllPass final : public DelayLineBase {
+public:
+ DelayLineAllPass();
+ ~DelayLineAllPass();
+
+ void Initialize(u32 delay, float coeffcient_, f32* src_buffer);
+ void SetCoefficient(float coeffcient_);
+ f32 Tick(f32 sample);
+ void Reset();
+
+private:
+ float coefficient{};
+};
+} // namespace AudioCore
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp
index f770b9608..89e4573c7 100644
--- a/src/audio_core/effect_context.cpp
+++ b/src/audio_core/effect_context.cpp
@@ -90,6 +90,14 @@ s32 EffectBase::GetProcessingOrder() const {
return processing_order;
}
+std::vector<u8>& EffectBase::GetWorkBuffer() {
+ return work_buffer;
+}
+
+const std::vector<u8>& EffectBase::GetWorkBuffer() const {
+ return work_buffer;
+}
+
EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {}
EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
@@ -117,6 +125,12 @@ void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
usage = UsageState::Initialized;
params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
+ if (!skipped) {
+ auto& cur_work_buffer = GetWorkBuffer();
+ // Has two buffers internally
+ cur_work_buffer.resize(in_params.buffer_size * 2);
+ std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0);
+ }
}
}
@@ -129,6 +143,14 @@ void EffectI3dl2Reverb::UpdateForCommandGeneration() {
GetParams().status = ParameterStatus::Updated;
}
+I3dl2ReverbState& EffectI3dl2Reverb::GetState() {
+ return state;
+}
+
+const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const {
+ return state;
+}
+
EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}
EffectBiquadFilter::~EffectBiquadFilter() = default;
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
index c5e0b398c..5e0655dd7 100644
--- a/src/audio_core/effect_context.h
+++ b/src/audio_core/effect_context.h
@@ -8,6 +8,7 @@
#include <memory>
#include <vector>
#include "audio_core/common.h"
+#include "audio_core/delay_line.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -194,6 +195,8 @@ public:
[[nodiscard]] bool IsEnabled() const;
[[nodiscard]] s32 GetMixID() const;
[[nodiscard]] s32 GetProcessingOrder() const;
+ [[nodiscard]] std::vector<u8>& GetWorkBuffer();
+ [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const;
protected:
UsageState usage{UsageState::Invalid};
@@ -201,6 +204,7 @@ protected:
s32 mix_id{};
s32 processing_order{};
bool enabled = false;
+ std::vector<u8> work_buffer{};
};
template <typename T>
@@ -212,7 +216,7 @@ public:
return internal_params;
}
- const I3dl2ReverbParams& GetParams() const {
+ const T& GetParams() const {
return internal_params;
}
@@ -229,6 +233,27 @@ public:
void UpdateForCommandGeneration() override;
};
+struct I3dl2ReverbState {
+ f32 lowpass_0{};
+ f32 lowpass_1{};
+ f32 lowpass_2{};
+
+ DelayLineBase early_delay_line{};
+ std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{};
+ f32 early_gain{};
+ f32 late_gain{};
+
+ u32 early_to_late_taps{};
+ std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{};
+ std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{};
+ std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{};
+ f32 last_reverb_echo{};
+ DelayLineBase center_delay_line{};
+ std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{};
+ std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{};
+ f32 dry_gain{};
+};
+
class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
public:
explicit EffectI3dl2Reverb();
@@ -237,8 +262,12 @@ public:
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
+ I3dl2ReverbState& GetState();
+ const I3dl2ReverbState& GetState() const;
+
private:
bool skipped = false;
+ I3dl2ReverbState state{};
};
class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 1662ec63d..28196d26a 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -266,6 +266,7 @@ add_library(core STATIC
hle/service/am/applets/software_keyboard.h
hle/service/am/applets/web_browser.cpp
hle/service/am/applets/web_browser.h
+ hle/service/am/applets/web_types.h
hle/service/am/idle.cpp
hle/service/am/idle.h
hle/service/am/omm.cpp
@@ -400,6 +401,7 @@ add_library(core STATIC
hle/service/hid/controllers/xpad.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
+ hle/service/ldn/errors.h
hle/service/ldn/ldn.cpp
hle/service/ldn/ldn.h
hle/service/ldr/ldr.cpp
diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h
index dff71d8d9..b0626a0f9 100644
--- a/src/core/frontend/applets/controller.h
+++ b/src/core/frontend/applets/controller.h
@@ -31,6 +31,7 @@ struct ControllerParameters {
bool allow_dual_joycons{};
bool allow_left_joycon{};
bool allow_right_joycon{};
+ bool allow_gamecube_controller{};
};
class ControllerApplet {
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index bb77c2569..8e1fe9438 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1047,20 +1047,21 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
const u64 offset{rp.Pop<u64>()};
const std::vector<u8> data{ctx.ReadBuffer()};
+ const std::size_t size{std::min(data.size(), backing.GetSize() - offset)};
- LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size());
+ LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
- if (data.size() > backing.GetSize() - offset) {
+ if (offset > backing.GetSize()) {
LOG_ERROR(Service_AM,
"offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
- backing.GetSize(), data.size(), offset);
+ backing.GetSize(), size, offset);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
return;
}
- std::memcpy(backing.GetData().data() + offset, data.data(), data.size());
+ std::memcpy(backing.GetData().data() + offset, data.data(), size);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -1070,11 +1071,11 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 offset{rp.Pop<u64>()};
- const std::size_t size{ctx.GetWriteBufferSize()};
+ const std::size_t size{std::min(ctx.GetWriteBufferSize(), backing.GetSize() - offset)};
LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
- if (size > backing.GetSize() - offset) {
+ if (offset > backing.GetSize()) {
LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
backing.GetSize(), size, offset);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index dbf198345..70b9f3824 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -21,6 +21,7 @@
namespace Service::HID {
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
+constexpr s32 HID_TRIGGER_MAX = 0x7fff;
[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
constexpr std::size_t NPAD_OFFSET = 0x9A00;
constexpr u32 BATTERY_FULL = 2;
@@ -48,6 +49,8 @@ Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad(
return NPadControllerType::JoyRight;
case Settings::ControllerType::Handheld:
return NPadControllerType::Handheld;
+ case Settings::ControllerType::GameCube:
+ return NPadControllerType::GameCube;
default:
UNREACHABLE();
return NPadControllerType::ProController;
@@ -67,6 +70,8 @@ Settings::ControllerType Controller_NPad::MapNPadToSettingsType(
return Settings::ControllerType::RightJoycon;
case NPadControllerType::Handheld:
return Settings::ControllerType::Handheld;
+ case NPadControllerType::GameCube:
+ return Settings::ControllerType::GameCube;
default:
UNREACHABLE();
return Settings::ControllerType::ProController;
@@ -209,6 +214,13 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
controller.assignment_mode = NpadAssignments::Single;
controller.footer_type = AppletFooterUiType::JoyRightHorizontal;
break;
+ case NPadControllerType::GameCube:
+ controller.style_set.gamecube.Assign(1);
+ // The GC Controller behaves like a wired Pro Controller
+ controller.device_type.fullkey.Assign(1);
+ controller.system_properties.is_vertical.Assign(1);
+ controller.system_properties.use_plus.Assign(1);
+ break;
case NPadControllerType::Pokeball:
controller.style_set.palma.Assign(1);
controller.device_type.palma.Assign(1);
@@ -259,6 +271,7 @@ void Controller_NPad::OnInit() {
style.joycon_right.Assign(1);
style.joycon_dual.Assign(1);
style.fullkey.Assign(1);
+ style.gamecube.Assign(1);
style.palma.Assign(1);
}
@@ -339,6 +352,7 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
auto& pad_state = npad_pad_states[controller_idx].pad_states;
auto& lstick_entry = npad_pad_states[controller_idx].l_stick;
auto& rstick_entry = npad_pad_states[controller_idx].r_stick;
+ auto& trigger_entry = npad_trigger_states[controller_idx];
const auto& button_state = buttons[controller_idx];
const auto& analog_state = sticks[controller_idx];
const auto [stick_l_x_f, stick_l_y_f] =
@@ -404,6 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
}
+
+ if (controller_type == NPadControllerType::GameCube) {
+ trigger_entry.l_analog = static_cast<s32>(
+ button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0);
+ trigger_entry.r_analog = static_cast<s32>(
+ button_state[ZR - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0);
+ pad_state.zl.Assign(false);
+ pad_state.zr.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ }
}
void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data,
@@ -418,6 +443,11 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
&npad.joy_left_states, &npad.joy_right_states, &npad.palma_states,
&npad.system_ext_states};
+ // There is the posibility to have more controllers with analog triggers
+ const std::array<TriggerGeneric*, 1> controller_triggers{
+ &npad.gc_trigger_states,
+ };
+
for (auto* main_controller : controller_npads) {
main_controller->common.entry_count = 16;
main_controller->common.total_entry_count = 17;
@@ -435,6 +465,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
cur_entry.timestamp2 = cur_entry.timestamp;
}
+ for (auto* analog_trigger : controller_triggers) {
+ analog_trigger->entry_count = 16;
+ analog_trigger->total_entry_count = 17;
+
+ const auto& last_entry = analog_trigger->trigger[analog_trigger->last_entry_index];
+
+ analog_trigger->timestamp = core_timing.GetCPUTicks();
+ analog_trigger->last_entry_index = (analog_trigger->last_entry_index + 1) % 17;
+
+ auto& cur_entry = analog_trigger->trigger[analog_trigger->last_entry_index];
+
+ cur_entry.timestamp = last_entry.timestamp + 1;
+ cur_entry.timestamp2 = cur_entry.timestamp;
+ }
+
const auto& controller_type = connected_controllers[i].type;
if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
@@ -444,6 +489,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
RequestPadStateUpdate(npad_index);
auto& pad_state = npad_pad_states[npad_index];
+ auto& trigger_state = npad_trigger_states[npad_index];
auto& main_controller =
npad.fullkey_states.npad[npad.fullkey_states.common.last_entry_index];
@@ -456,6 +502,8 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index];
auto& libnx_entry =
npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index];
+ auto& trigger_entry =
+ npad.gc_trigger_states.trigger[npad.gc_trigger_states.last_entry_index];
libnx_entry.connection_status.raw = 0;
libnx_entry.connection_status.is_connected.Assign(1);
@@ -524,6 +572,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
libnx_entry.connection_status.is_right_connected.Assign(1);
break;
+ case NPadControllerType::GameCube:
+ main_controller.connection_status.raw = 0;
+ main_controller.connection_status.is_connected.Assign(1);
+ main_controller.connection_status.is_wired.Assign(1);
+ main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
+ main_controller.pad.l_stick = pad_state.l_stick;
+ main_controller.pad.r_stick = pad_state.r_stick;
+ trigger_entry.l_analog = trigger_state.l_analog;
+ trigger_entry.r_analog = trigger_state.r_analog;
+
+ libnx_entry.connection_status.is_wired.Assign(1);
+ break;
case NPadControllerType::Pokeball:
pokeball_entry.connection_status.raw = 0;
pokeball_entry.connection_status.is_connected.Assign(1);
@@ -674,6 +734,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
right_sixaxis_entry.orientation = motion_devices[1].orientation;
}
break;
+ case NPadControllerType::GameCube:
case NPadControllerType::Pokeball:
break;
}
@@ -1135,6 +1196,8 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
return style.joycon_left;
case NPadControllerType::JoyRight:
return style.joycon_right;
+ case NPadControllerType::GameCube:
+ return style.gamecube;
case NPadControllerType::Pokeball:
return style.palma;
default:
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 48bab988c..bc2e6779d 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -51,6 +51,7 @@ public:
JoyDual,
JoyLeft,
JoyRight,
+ GameCube,
Pokeball,
};
@@ -60,6 +61,7 @@ public:
JoyconDual = 5,
JoyconLeft = 6,
JoyconRight = 7,
+ GameCube = 8,
Pokeball = 9,
MaxNpadType = 10,
};
@@ -389,6 +391,25 @@ private:
};
static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
+ struct TriggerState {
+ s64_le timestamp{};
+ s64_le timestamp2{};
+ s32_le l_analog{};
+ s32_le r_analog{};
+ };
+ static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size");
+
+ struct TriggerGeneric {
+ INSERT_PADDING_BYTES(0x4);
+ s64_le timestamp;
+ INSERT_PADDING_BYTES(0x4);
+ s64_le total_entry_count;
+ s64_le last_entry_index;
+ s64_le entry_count;
+ std::array<TriggerState, 17> trigger{};
+ };
+ static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size");
+
struct NPadSystemProperties {
union {
s64_le raw{};
@@ -509,7 +530,9 @@ private:
AppletFooterUiType footer_type;
// nfc_states needs to be checked switchbrew does not match with HW
NfcXcdHandle nfc_states;
- INSERT_PADDING_BYTES(0xdef);
+ INSERT_PADDING_BYTES(0x8); // Mutex
+ TriggerGeneric gc_trigger_states;
+ INSERT_PADDING_BYTES(0xc1f);
};
static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
@@ -560,6 +583,7 @@ private:
f32 sixaxis_fusion_parameter2{};
bool sixaxis_at_rest{true};
std::array<ControllerPad, 10> npad_pad_states{};
+ std::array<TriggerState, 10> npad_trigger_states{};
bool is_in_lr_assignment_mode{false};
Core::System& system;
};
diff --git a/src/core/hle/service/ldn/errors.h b/src/core/hle/service/ldn/errors.h
new file mode 100644
index 000000000..a718c5c66
--- /dev/null
+++ b/src/core/hle/service/ldn/errors.h
@@ -0,0 +1,13 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::LDN {
+
+constexpr ResultCode ERROR_DISABLED{ErrorModule::LDN, 22};
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index ee908f399..c630d93cd 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -6,6 +6,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/result.h"
+#include "core/hle/service/ldn/errors.h"
#include "core/hle/service/ldn/ldn.h"
#include "core/hle/service/sm/sm.h"
@@ -103,7 +104,7 @@ public:
: ServiceFramework{system_, "IUserLocalCommunicationService"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "GetState"},
+ {0, &IUserLocalCommunicationService::GetState, "GetState"},
{1, nullptr, "GetNetworkInfo"},
{2, nullptr, "GetIpv4Address"},
{3, nullptr, "GetDisconnectReason"},
@@ -138,13 +139,38 @@ public:
RegisterHandlers(functions);
}
- void Initialize2(Kernel::HLERequestContext& ctx) {
+ void GetState(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_LDN, "(STUBBED) called");
- // Result success seem make this services start network and continue.
- // If we just pass result error then it will stop and maybe try again and again.
+
+ IPC::ResponseBuilder rb{ctx, 3};
+
+ // Indicate a network error, as we do not actually emulate LDN
+ rb.Push(static_cast<u32>(State::Error));
+
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Initialize2(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_LDN, "called");
+
+ is_initialized = true;
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_UNKNOWN);
+ rb.Push(RESULT_SUCCESS);
}
+
+private:
+ enum class State {
+ None,
+ Initialized,
+ AccessPointOpened,
+ AccessPointCreated,
+ StationOpened,
+ StationConnected,
+ Error,
+ };
+
+ bool is_initialized{};
};
class LDNS final : public ServiceFramework<LDNS> {
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
index 75486554b..a59f5d461 100644
--- a/src/input_common/settings.h
+++ b/src/input_common/settings.h
@@ -340,6 +340,7 @@ enum class ControllerType {
LeftJoycon,
RightJoycon,
Handheld,
+ GameCube,
};
struct PlayerInput {
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
index ee274ac59..a8bf7bda8 100644
--- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
@@ -17,21 +17,21 @@ ResourcePool::~ResourcePool() = default;
size_t ResourcePool::CommitResource() {
// Refresh semaphore to query updated results
master_semaphore.Refresh();
-
- const auto search = [this](size_t begin, size_t end) -> std::optional<size_t> {
+ const u64 gpu_tick = master_semaphore.KnownGpuTick();
+ const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional<size_t> {
for (size_t iterator = begin; iterator < end; ++iterator) {
- if (master_semaphore.IsFree(ticks[iterator])) {
+ if (gpu_tick >= ticks[iterator]) {
ticks[iterator] = master_semaphore.CurrentTick();
return iterator;
}
}
- return {};
+ return std::nullopt;
};
// Try to find a free resource from the hinted position to the end.
- auto found = search(free_iterator, ticks.size());
+ std::optional<size_t> found = search(hint_iterator, ticks.size());
if (!found) {
// Search from beginning to the hinted position.
- found = search(0, free_iterator);
+ found = search(0, hint_iterator);
if (!found) {
// Both searches failed, the pool is full; handle it.
const size_t free_resource = ManageOverflow();
@@ -41,7 +41,7 @@ size_t ResourcePool::CommitResource() {
}
}
// Free iterator is hinted to the resource after the one that's been commited.
- free_iterator = (*found + 1) % ticks.size();
+ hint_iterator = (*found + 1) % ticks.size();
return *found;
}
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h
index a018c7ec2..9d0bb3b4d 100644
--- a/src/video_core/renderer_vulkan/vk_resource_pool.h
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.h
@@ -36,7 +36,7 @@ private:
MasterSemaphore& master_semaphore;
size_t grow_step = 0; ///< Number of new resources created after an overflow
- size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found
+ size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found
std::vector<u64> ticks; ///< Ticks for each resource
};
diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp
index 3b40db9bc..02adcf9c7 100644
--- a/src/video_core/shader/async_shaders.cpp
+++ b/src/video_core/shader/async_shaders.cpp
@@ -64,6 +64,7 @@ void AsyncShaders::FreeWorkers() {
void AsyncShaders::KillWorkers() {
is_thread_exiting.store(true);
+ cv.notify_all();
for (auto& thread : worker_threads) {
thread.detach();
}
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index fb9967c8f..b025ced1c 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -151,6 +151,7 @@ add_executable(yuzu
util/util.h
compatdb.cpp
compatdb.h
+ yuzu.qrc
yuzu.rc
)
diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp
index c680fd2c2..b92cd6886 100644
--- a/src/yuzu/applets/controller.cpp
+++ b/src/yuzu/applets/controller.cpp
@@ -67,6 +67,8 @@ bool IsControllerCompatible(Settings::ControllerType controller_type,
return parameters.allow_right_joycon;
case Settings::ControllerType::Handheld:
return parameters.enable_single_mode && parameters.allow_handheld;
+ case Settings::ControllerType::GameCube:
+ return parameters.allow_gamecube_controller;
default:
return false;
}
@@ -370,7 +372,7 @@ void QtControllerSelectorDialog::SetSupportedControllers() {
QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
}
- if (parameters.allow_pro_controller) {
+ if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) {
ui->controllerSupported5->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
} else {
@@ -420,6 +422,10 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index
Settings::ControllerType::Handheld);
emulated_controllers[player_index]->addItem(tr("Handheld"));
}
+
+ pairs.emplace_back(emulated_controllers[player_index]->count(),
+ Settings::ControllerType::GameCube);
+ emulated_controllers[player_index]->addItem(tr("GameCube Controller"));
}
Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex(
@@ -461,6 +467,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
player_index)) {
case Settings::ControllerType::ProController:
+ case Settings::ControllerType::GameCube:
return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
case Settings::ControllerType::DualJoyconDetached:
return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index c9d19c948..21d0d3449 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -467,10 +467,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
UpdateControllerIcon();
UpdateControllerAvailableButtons();
+ UpdateControllerEnabledButtons();
+ UpdateControllerButtonNames();
UpdateMotionButtons();
connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
UpdateControllerIcon();
UpdateControllerAvailableButtons();
+ UpdateControllerEnabledButtons();
+ UpdateControllerButtonNames();
UpdateMotionButtons();
});
@@ -558,9 +562,6 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
&ConfigureInputPlayer::SaveProfile);
LoadConfiguration();
-
- // TODO(wwylele): enable this when we actually emulate it
- ui->buttonHome->setEnabled(false);
ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked());
}
@@ -924,6 +925,12 @@ void ConfigureInputPlayer::SetConnectableControllers() {
Settings::ControllerType::Handheld);
ui->comboControllerType->addItem(tr("Handheld"));
}
+
+ if (enable_all || npad_style_set.gamecube == 1) {
+ index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
+ Settings::ControllerType::GameCube);
+ ui->comboControllerType->addItem(tr("GameCube Controller"));
+ }
};
Core::System& system{Core::System::GetInstance()};
@@ -1014,7 +1021,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
// List of all the widgets that will be hidden by any of the following layouts that need
// "unhidden" after the controller type changes
- const std::array<QWidget*, 9> layout_show = {
+ const std::array<QWidget*, 11> layout_show = {
ui->buttonShoulderButtonsSLSR,
ui->horizontalSpacerShoulderButtonsWidget,
ui->horizontalSpacerShoulderButtonsWidget2,
@@ -1024,6 +1031,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
ui->buttonShoulderButtonsRight,
ui->buttonMiscButtonsPlusHome,
ui->bottomRight,
+ ui->buttonMiscButtonsMinusGroup,
+ ui->buttonMiscButtonsScreenshotGroup,
};
for (auto* widget : layout_show) {
@@ -1056,6 +1065,14 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
ui->bottomLeft,
};
break;
+ case Settings::ControllerType::GameCube:
+ layout_hidden = {
+ ui->buttonShoulderButtonsSLSR,
+ ui->horizontalSpacerShoulderButtonsWidget2,
+ ui->buttonMiscButtonsMinusGroup,
+ ui->buttonMiscButtonsScreenshotGroup,
+ };
+ break;
}
for (auto* widget : layout_hidden) {
@@ -1063,6 +1080,52 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
}
}
+void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
+ auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ if (debug) {
+ layout = Settings::ControllerType::ProController;
+ }
+
+ // List of all the widgets that will be disabled by any of the following layouts that need
+ // "enabled" after the controller type changes
+ const std::array<QWidget*, 4> layout_enable = {
+ ui->buttonHome,
+ ui->buttonLStickPressedGroup,
+ ui->groupRStickPressed,
+ ui->buttonShoulderButtonsButtonLGroup,
+ };
+
+ for (auto* widget : layout_enable) {
+ widget->setEnabled(true);
+ }
+
+ std::vector<QWidget*> layout_disable;
+ switch (layout) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::DualJoyconDetached:
+ case Settings::ControllerType::Handheld:
+ case Settings::ControllerType::LeftJoycon:
+ case Settings::ControllerType::RightJoycon:
+ // TODO(wwylele): enable this when we actually emulate it
+ layout_disable = {
+ ui->buttonHome,
+ };
+ break;
+ case Settings::ControllerType::GameCube:
+ layout_disable = {
+ ui->buttonHome,
+ ui->buttonLStickPressedGroup,
+ ui->groupRStickPressed,
+ ui->buttonShoulderButtonsButtonLGroup,
+ };
+ break;
+ }
+
+ for (auto* widget : layout_disable) {
+ widget->setEnabled(false);
+ }
+}
+
void ConfigureInputPlayer::UpdateMotionButtons() {
if (debug) {
// Motion isn't used with the debug controller, hide both groupboxes.
@@ -1085,6 +1148,11 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
ui->buttonMotionLeftGroup->hide();
ui->buttonMotionRightGroup->show();
break;
+ case Settings::ControllerType::GameCube:
+ // Hide both "Motion 1/2".
+ ui->buttonMotionLeftGroup->hide();
+ ui->buttonMotionRightGroup->hide();
+ break;
case Settings::ControllerType::DualJoyconDetached:
default:
// Show both "Motion 1/2".
@@ -1094,6 +1162,36 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
}
}
+void ConfigureInputPlayer::UpdateControllerButtonNames() {
+ auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
+ if (debug) {
+ layout = Settings::ControllerType::ProController;
+ }
+
+ switch (layout) {
+ case Settings::ControllerType::ProController:
+ case Settings::ControllerType::DualJoyconDetached:
+ case Settings::ControllerType::Handheld:
+ case Settings::ControllerType::LeftJoycon:
+ case Settings::ControllerType::RightJoycon:
+ ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus"));
+ ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL"));
+ ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR"));
+ ui->buttonShoulderButtonsRGroup->setTitle(tr("R"));
+ ui->LStick->setTitle(tr("Left Stick"));
+ ui->RStick->setTitle(tr("Right Stick"));
+ break;
+ case Settings::ControllerType::GameCube:
+ ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause"));
+ ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L"));
+ ui->buttonShoulderButtonsZRGroup->setTitle(tr("R"));
+ ui->buttonShoulderButtonsRGroup->setTitle(tr("Z"));
+ ui->LStick->setTitle(tr("Control Stick"));
+ ui->RStick->setTitle(tr("C-Stick"));
+ break;
+ }
+}
+
void ConfigureInputPlayer::UpdateMappingWithDefaults() {
if (ui->comboDevices->currentIndex() == 0) {
return;
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index da2b89136..efe953fbc 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -143,9 +143,15 @@ private:
/// Hides and disables controller settings based on the current controller type.
void UpdateControllerAvailableButtons();
+ /// Disables controller settings based on the current controller type.
+ void UpdateControllerEnabledButtons();
+
/// Shows or hides motion groupboxes based on the current controller type.
void UpdateMotionButtons();
+ /// Alters the button names based on the current controller type.
+ void UpdateControllerButtonNames();
+
/// Gets the default controller mapping for this device and auto configures the input to match.
void UpdateMappingWithDefaults();
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 0e8a964d2..61ba91cef 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -227,6 +227,9 @@ void PlayerControlPreview::paintEvent(QPaintEvent* event) {
case Settings::ControllerType::RightJoycon:
DrawRightController(p, center);
break;
+ case Settings::ControllerType::GameCube:
+ DrawGCController(p, center);
+ break;
case Settings::ControllerType::ProController:
default:
DrawProController(p, center);
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp
index 85724a8f3..2731d948d 100644
--- a/src/yuzu/debugger/controller.cpp
+++ b/src/yuzu/debugger/controller.cpp
@@ -42,7 +42,7 @@ void ControllerDialog::refreshConfiguration() {
QAction* ControllerDialog::toggleViewAction() {
if (toggle_view_action == nullptr) {
- toggle_view_action = new QAction(windowTitle(), this);
+ toggle_view_action = new QAction(tr("&Controller P1"), this);
toggle_view_action->setCheckable(true);
toggle_view_action->setChecked(isVisible());
connect(toggle_view_action, &QAction::toggled, this, &ControllerDialog::setVisible);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 28a52a56c..0ba7c07cc 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -2770,7 +2770,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
.arg(errors));
}
- QProgressDialog prog;
+ QProgressDialog prog(this);
prog.setRange(0, 0);
prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your "
"system's performance."));
@@ -2952,7 +2952,7 @@ void GMainWindow::filterBarSetChecked(bool state) {
}
void GMainWindow::UpdateUITheme() {
- const QString default_icons = QStringLiteral(":/icons/default");
+ const QString default_icons = QStringLiteral("default");
const QString& current_theme = UISettings::values.theme;
const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);
QStringList theme_paths(default_theme_paths);
@@ -2968,7 +2968,6 @@ void GMainWindow::UpdateUITheme() {
qApp->setStyleSheet({});
setStyleSheet({});
}
- theme_paths.append(default_icons);
QIcon::setThemeName(default_icons);
} else {
const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
@@ -2980,10 +2979,7 @@ void GMainWindow::UpdateUITheme() {
} else {
LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
}
-
- const QString theme_name = QStringLiteral(":/icons/") + current_theme;
- theme_paths.append({default_icons, theme_name});
- QIcon::setThemeName(theme_name);
+ QIcon::setThemeName(current_theme);
}
QIcon::setThemeSearchPaths(theme_paths);
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index e2ad5baf6..048870687 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -14,8 +14,8 @@
<string>yuzu</string>
</property>
<property name="windowIcon">
- <iconset>
- <normaloff>../dist/yuzu.ico</normaloff>../dist/yuzu.ico</iconset>
+ <iconset resource="yuzu.qrc">
+ <normaloff>:/img/yuzu.ico</normaloff>:/img/yuzu.ico</iconset>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
@@ -303,6 +303,8 @@
</property>
</action>
</widget>
- <resources/>
+ <resources>
+ <include location="yuzu.qrc"/>
+ </resources>
<connections/>
</ui>
diff --git a/src/yuzu/yuzu.qrc b/src/yuzu/yuzu.qrc
new file mode 100644
index 000000000..5733cac98
--- /dev/null
+++ b/src/yuzu/yuzu.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/img">
+ <file alias="yuzu.ico">../../dist/yuzu.ico</file>
+ </qresource>
+</RCC>
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 0b3f2cb54..8461f8896 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -1,5 +1,15 @@
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
+function(create_resource file output filename)
+ # Read hex data from file
+ file(READ ${file} filedata HEX)
+ # Convert hex data for C compatibility
+ string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata})
+ # Write data to output file
+ set(RESOURCES_DIR "${PROJECT_BINARY_DIR}/dist" PARENT_SCOPE)
+ file(WRITE "${PROJECT_BINARY_DIR}/dist/${output}" "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n")
+endfunction()
+
add_executable(yuzu-cmd
config.cpp
config.h
@@ -24,6 +34,9 @@ if (MSVC)
endif()
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads)
+create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
+target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR})
+
target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include)
if(UNIX AND NOT APPLE)
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 39841aa28..7e391ab89 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -12,6 +12,7 @@
#include "input_common/mouse/mouse_input.h"
#include "input_common/sdl/sdl.h"
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
+#include "yuzu_cmd/yuzu_icon.h"
EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_)
: input_subsystem{input_subsystem_} {
@@ -194,6 +195,22 @@ void EmuWindow_SDL2::WaitEvent() {
}
}
+void EmuWindow_SDL2::SetWindowIcon() {
+ SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size);
+ if (yuzu_icon_stream == nullptr) {
+ LOG_WARNING(Frontend, "Failed to create yuzu icon stream.");
+ return;
+ }
+ SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1);
+ if (window_icon == nullptr) {
+ LOG_WARNING(Frontend, "Failed to read BMP from stream.");
+ return;
+ }
+ // The icon is attached to the window pointer
+ SDL_SetWindowIcon(render_window, window_icon);
+ SDL_FreeSurface(window_icon);
+}
+
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index a93141240..51a12a6a9 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -32,6 +32,9 @@ public:
/// Wait for the next event on the main thread.
void WaitEvent();
+ // Sets the window icon from yuzu.bmp
+ void SetWindowIcon();
+
protected:
/// Called by WaitEvent when a key is pressed or released.
void OnKeyEvent(int key, u8 state);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index deddea9ee..a02485c14 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -107,6 +107,8 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
+ SetWindowIcon();
+
if (fullscreen) {
Fullscreen();
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
index 3ba657c00..6f9b00461 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -35,6 +35,8 @@ EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsyste
std::exit(EXIT_FAILURE);
}
+ SetWindowIcon();
+
switch (wm.subsystem) {
#ifdef SDL_VIDEO_DRIVER_WINDOWS
case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: