summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dist/qt_themes/colorful_dark/icons/index.theme2
-rw-r--r--dist/qt_themes/colorful_midnight_blue/icons/index.theme2
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qss4
-rw-r--r--dist/yuzu.bmpbin0 -> 262282 bytes
-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/sdl/sdl_impl.cpp7
-rw-r--r--src/input_common/settings.h1
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp31
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h4
-rw-r--r--src/video_core/renderer_opengl/util_shaders.cpp13
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp83
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h18
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp12
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h3
-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/renderer_vulkan/vk_state_tracker.cpp34
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.h5
-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/config.cpp9
-rw-r--r--src/yuzu/configuration/configure_filesystem.cpp10
-rw-r--r--src/yuzu/configuration/configure_filesystem.h1
-rw-r--r--src/yuzu/configuration/configure_filesystem.ui35
-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/config.cpp3
-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
53 files changed, 1048 insertions, 180 deletions
diff --git a/dist/qt_themes/colorful_dark/icons/index.theme b/dist/qt_themes/colorful_dark/icons/index.theme
index 94d5ae8aa..19dc0369a 100644
--- a/dist/qt_themes/colorful_dark/icons/index.theme
+++ b/dist/qt_themes/colorful_dark/icons/index.theme
@@ -1,7 +1,7 @@
1[Icon Theme] 1[Icon Theme]
2Name=colorful_dark 2Name=colorful_dark
3Comment=Colorful theme (Dark style) 3Comment=Colorful theme (Dark style)
4Inherits=default 4Inherits=colorful
5Directories=16x16 5Directories=16x16
6 6
7[16x16] 7[16x16]
diff --git a/dist/qt_themes/colorful_midnight_blue/icons/index.theme b/dist/qt_themes/colorful_midnight_blue/icons/index.theme
index e23bfe6f9..dcb2c50d6 100644
--- a/dist/qt_themes/colorful_midnight_blue/icons/index.theme
+++ b/dist/qt_themes/colorful_midnight_blue/icons/index.theme
@@ -1,7 +1,7 @@
1[Icon Theme] 1[Icon Theme]
2Name=colorful_midnight_blue 2Name=colorful_midnight_blue
3Comment=Colorful theme (Midnight Blue style) 3Comment=Colorful theme (Midnight Blue style)
4Inherits=default 4Inherits=colorful
5Directories=16x16 5Directories=16x16
6 6
7[16x16] 7[16x16]
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
index 70e540b06..a64037455 100644
--- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
@@ -1257,10 +1257,6 @@ QComboBox::item:alternate {
1257 background: #19232D; 1257 background: #19232D;
1258} 1258}
1259 1259
1260QComboBox::item:checked {
1261 font-weight: bold;
1262}
1263
1264QComboBox::item:selected { 1260QComboBox::item:selected {
1265 border: 0px solid transparent; 1261 border: 0px solid transparent;
1266} 1262}
diff --git a/dist/yuzu.bmp b/dist/yuzu.bmp
new file mode 100644
index 000000000..66f2f696f
--- /dev/null
+++ b/dist/yuzu.bmp
Binary files differ
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
15 command_generator.cpp 15 command_generator.cpp
16 command_generator.h 16 command_generator.h
17 common.h 17 common.h
18 delay_line.cpp
19 delay_line.h
18 effect_context.cpp 20 effect_context.cpp
19 effect_context.h 21 effect_context.h
20 info_updater.cpp 22 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 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <cmath>
6#include <numbers>
5#include "audio_core/algorithm/interpolate.h" 7#include "audio_core/algorithm/interpolate.h"
6#include "audio_core/command_generator.h" 8#include "audio_core/command_generator.h"
7#include "audio_core/effect_context.h" 9#include "audio_core/effect_context.h"
@@ -13,6 +15,20 @@ namespace AudioCore {
13namespace { 15namespace {
14constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; 16constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
15constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; 17constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
18using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>;
19
20constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f};
21constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f};
22constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f};
23constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f};
24constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{
25 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f,
26 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f,
27 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f};
28constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{
29 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f,
30 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f,
31 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
16 32
17template <std::size_t N> 33template <std::size_t N>
18void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { 34void 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) {
65 } 81 }
66} 82}
67 83
84float Pow10(float x) {
85 if (x >= 0.0f) {
86 return 1.0f;
87 } else if (x <= -5.3f) {
88 return 0.0f;
89 }
90 return std::pow(10.0f, x);
91}
92
93float SinD(float degrees) {
94 return std::sin(degrees * std::numbers::pi_v<float> / 180.0f);
95}
96
97float CosD(float degrees) {
98 return std::cos(degrees * std::numbers::pi_v<float> / 180.0f);
99}
100
101float ToFloat(s32 sample) {
102 return static_cast<float>(sample) / 65536.f;
103}
104
105s32 ToS32(float sample) {
106 constexpr auto min = -8388608.0f;
107 constexpr auto max = 8388607.f;
108 float rescaled_sample = sample * 65536.0f;
109 if (rescaled_sample < min) {
110 rescaled_sample = min;
111 }
112 if (rescaled_sample > max) {
113 rescaled_sample = max;
114 }
115 return static_cast<s32>(rescaled_sample);
116}
117
118constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
119 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
120
121constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
122 1, 1, 1, 0, 0, 0, 0, 1, 1, 1};
123
124constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
125 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
126
127constexpr std::array<std::size_t, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2,
128 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
129
130template <std::size_t CHANNEL_COUNT>
131void ApplyReverbGeneric(I3dl2ReverbState& state,
132 const std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT>& input,
133 const std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT>& output,
134 s32 sample_count) {
135
136 auto GetTapLookup = []() {
137 if constexpr (CHANNEL_COUNT == 1) {
138 return REVERB_TAP_INDEX_1CH;
139 } else if constexpr (CHANNEL_COUNT == 2) {
140 return REVERB_TAP_INDEX_2CH;
141 } else if constexpr (CHANNEL_COUNT == 4) {
142 return REVERB_TAP_INDEX_4CH;
143 } else if constexpr (CHANNEL_COUNT == 6) {
144 return REVERB_TAP_INDEX_6CH;
145 }
146 };
147
148 const auto& tap_index_lut = GetTapLookup();
149 for (s32 sample = 0; sample < sample_count; sample++) {
150 std::array<f32, CHANNEL_COUNT> out_samples{};
151 std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{};
152 std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{};
153 std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{};
154
155 // Mix everything into a single sample
156 s32 temp_mixed_sample = 0;
157 for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
158 temp_mixed_sample += input[i][sample];
159 }
160 const auto current_sample = ToFloat(temp_mixed_sample);
161 const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps);
162
163 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) {
164 const auto tapped_samp =
165 state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i];
166 out_samples[tap_index_lut[i]] += tapped_samp;
167
168 if constexpr (CHANNEL_COUNT == 6) {
169 // handle lfe
170 out_samples[5] += tapped_samp;
171 }
172 }
173
174 state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1;
175 state.early_delay_line.Tick(state.lowpass_0);
176
177 for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
178 out_samples[i] *= state.early_gain;
179 }
180
181 // Two channel seems to apply a latet gain, we require to save this
182 f32 filter{};
183 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
184 filter = state.fdn_delay_line[i].GetOutputSample();
185 const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i];
186 state.shelf_filter[i] =
187 filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i];
188 fsamp[i] = computed;
189 }
190
191 // Mixing matrix
192 mixed[0] = fsamp[1] + fsamp[2];
193 mixed[1] = -fsamp[0] - fsamp[3];
194 mixed[2] = fsamp[0] - fsamp[3];
195 mixed[3] = fsamp[1] - fsamp[2];
196
197 if constexpr (CHANNEL_COUNT == 2) {
198 for (auto& mix : mixed) {
199 mix *= (filter * state.late_gain);
200 }
201 }
202
203 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
204 const auto late = early_tap * state.late_gain;
205 osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]);
206 osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]);
207 state.fdn_delay_line[i].Tick(osamp[i]);
208 }
209
210 if constexpr (CHANNEL_COUNT == 1) {
211 output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) +
212 (out_samples[0] + osamp[0] + osamp[1]));
213 } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) {
214 for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
215 output[i][sample] =
216 ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
217 }
218 } else if constexpr (CHANNEL_COUNT == 6) {
219 const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3]));
220 for (std::size_t i = 0; i < 4; i++) {
221 output[i][sample] =
222 ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
223 }
224 output[4][sample] =
225 ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center));
226 output[5][sample] =
227 ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3]));
228 }
229 }
230}
231
68} // namespace 232} // namespace
69 233
70CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, 234CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
@@ -271,11 +435,10 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic
271 } 435 }
272 436
273 // Generate biquad filter 437 // Generate biquad filter
274 // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, 438 // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
275 // dsp_state.biquad_filter_state, 439 // dsp_state.biquad_filter_state,
276 // mix_buffer_count + channel, mix_buffer_count + 440 // mix_buffer_count + channel, mix_buffer_count + channel,
277 // channel, worker_params.sample_count, 441 // worker_params.sample_count, voice_info.GetInParams().node_id);
278 // voice_info.GetInParams().node_id);
279 } 442 }
280} 443}
281 444
@@ -376,21 +539,54 @@ void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
376 539
377void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, 540void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
378 bool enabled) { 541 bool enabled) {
379 if (!enabled) { 542 auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info);
543 const auto& params = reverb->GetParams();
544 auto& state = reverb->GetState();
545 const auto channel_count = params.channel_count;
546
547 if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) {
380 return; 548 return;
381 } 549 }
382 const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams(); 550
383 const auto channel_count = params.channel_count; 551 std::array<const s32*, AudioCommon::MAX_CHANNEL_COUNT> input{};
552 std::array<s32*, AudioCommon::MAX_CHANNEL_COUNT> output{};
553
554 const auto status = params.status;
384 for (s32 i = 0; i < channel_count; i++) { 555 for (s32 i = 0; i < channel_count; i++) {
385 // TODO(ogniK): Actually implement reverb 556 input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]);
386 /* 557 output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]);
387 if (params.input[i] != params.output[i]) { 558 }
388 const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]); 559
389 auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); 560 if (enabled) {
390 ApplyMix<1>(output, input, 32768, worker_params.sample_count); 561 if (status == ParameterStatus::Initialized) {
391 }*/ 562 InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer());
392 auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); 563 } else if (status == ParameterStatus::Updating) {
393 std::memset(output, 0, worker_params.sample_count * sizeof(s32)); 564 UpdateI3dl2Reverb(reverb->GetParams(), state, false);
565 }
566 }
567
568 if (enabled) {
569 switch (channel_count) {
570 case 1:
571 ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count);
572 break;
573 case 2:
574 ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count);
575 break;
576 case 4:
577 ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count);
578 break;
579 case 6:
580 ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count);
581 break;
582 }
583 } else {
584 for (s32 i = 0; i < channel_count; i++) {
585 // Only copy if the buffer input and output do not match!
586 if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) {
587 std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32));
588 }
589 }
394 } 590 }
395} 591}
396 592
@@ -528,6 +724,133 @@ s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u3
528 return sample_count; 724 return sample_count;
529} 725}
530 726
727void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
728 std::vector<u8>& work_buffer) {
729 // Reset state
730 state.lowpass_0 = 0.0f;
731 state.lowpass_1 = 0.0f;
732 state.lowpass_2 = 0.0f;
733
734 state.early_delay_line.Reset();
735 state.early_tap_steps.fill(0);
736 state.early_gain = 0.0f;
737 state.late_gain = 0.0f;
738 state.early_to_late_taps = 0;
739 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
740 state.fdn_delay_line[i].Reset();
741 state.decay_delay_line0[i].Reset();
742 state.decay_delay_line1[i].Reset();
743 }
744 state.last_reverb_echo = 0.0f;
745 state.center_delay_line.Reset();
746 for (auto& coef : state.lpf_coefficients) {
747 coef.fill(0.0f);
748 }
749 state.shelf_filter.fill(0.0f);
750 state.dry_gain = 0.0f;
751
752 const auto sample_rate = info.sample_rate / 1000;
753 f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data());
754
755 s32 delay_samples{};
756 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
757 delay_samples =
758 AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]);
759 state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr);
760 work_buffer_ptr += delay_samples + 1;
761
762 delay_samples =
763 AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]);
764 state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
765 work_buffer_ptr += delay_samples + 1;
766
767 delay_samples =
768 AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]);
769 state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
770 work_buffer_ptr += delay_samples + 1;
771 }
772 delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f);
773 state.center_delay_line.Initialize(delay_samples, work_buffer_ptr);
774 work_buffer_ptr += delay_samples + 1;
775
776 delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f);
777 state.early_delay_line.Initialize(delay_samples, work_buffer_ptr);
778
779 UpdateI3dl2Reverb(info, state, true);
780}
781
782void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
783 bool should_clear) {
784
785 state.dry_gain = info.dry_gain;
786 state.shelf_filter.fill(0.0f);
787 state.lowpass_0 = 0.0f;
788 state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f);
789 state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f);
790
791 const auto sample_rate = info.sample_rate / 1000;
792 const f32 hf_gain = Pow10(info.room_hf / 2000.0f);
793 if (hf_gain >= 1.0f) {
794 state.lowpass_2 = 1.0f;
795 state.lowpass_1 = 0.0f;
796 } else {
797 const auto a = 1.0f - hf_gain;
798 const auto b = 2.0f * (1.0f - hf_gain * CosD(256.0f * info.hf_reference /
799 static_cast<f32>(info.sample_rate)));
800 const auto c = std::sqrt(b * b - 4.0f * a * a);
801
802 state.lowpass_1 = (b - c) / (2.0f * a);
803 state.lowpass_2 = 1.0f - state.lowpass_1;
804 }
805 state.early_to_late_taps = AudioCommon::CalculateDelaySamples(
806 sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay));
807
808 state.last_reverb_echo = 0.6f * info.diffusion * 0.01f;
809 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
810 const auto length =
811 FDN_MIN_DELAY_LINE_TIMES[i] +
812 (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]);
813 state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length));
814
815 const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() +
816 state.decay_delay_line0[i].GetDelay() +
817 state.decay_delay_line1[i].GetDelay();
818
819 float a = (-60.0f * static_cast<f32>(delay_sample_counts)) /
820 (info.decay_time * static_cast<f32>(info.sample_rate));
821 float b = a / info.hf_decay_ratio;
822 float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) /
823 SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate));
824 float d = Pow10((b - a) / 40.0f);
825 float e = Pow10((b + a) / 40.0f) * 0.7071f;
826
827 state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d);
828 state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d);
829 state.lpf_coefficients[2][i] = (c - d) / (c + d);
830
831 state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo);
832 state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo);
833 }
834
835 if (should_clear) {
836 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
837 state.fdn_delay_line[i].Clear();
838 state.decay_delay_line0[i].Clear();
839 state.decay_delay_line1[i].Clear();
840 }
841 state.early_delay_line.Clear();
842 state.center_delay_line.Clear();
843 }
844
845 const auto max_early_delay = state.early_delay_line.GetMaxDelay();
846 const auto reflection_time = 1000.0f * (0.0098f * info.reverb_delay + 0.02f);
847 for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) {
848 const auto length = AudioCommon::CalculateDelaySamples(
849 sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]);
850 state.early_tap_steps[tap] = std::min(length, max_early_delay);
851 }
852}
853
531void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, 854void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
532 s32 channel, s32 node_id) { 855 s32 channel, s32 node_id) {
533 const auto last = static_cast<s32>(last_volume * 32768.0f); 856 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;
21class EffectContext; 21class EffectContext;
22class EffectBase; 22class EffectBase;
23struct AuxInfoDSP; 23struct AuxInfoDSP;
24struct I3dl2ReverbParams;
25struct I3dl2ReverbState;
24using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; 26using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
25 27
26class CommandGenerator { 28class CommandGenerator {
@@ -80,6 +82,9 @@ private:
80 s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data, 82 s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
81 u32 sample_count, u32 read_offset, u32 read_count); 83 u32 sample_count, u32 read_offset, u32 read_count);
82 84
85 void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
86 std::vector<u8>& work_buffer);
87 void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
83 // DSP Code 88 // DSP Code
84 s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, 89 s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
85 s32 channel, std::size_t mix_offset); 90 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
33// and our const ends up being 0x3f04, the 4 bytes are most 33// and our const ends up being 0x3f04, the 4 bytes are most
34// likely the sample history 34// likely the sample history
35constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; 35constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
36constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f;
37constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f;
38constexpr std::size_t I3DL2REVERB_TAPS = 20;
39constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4;
40using Fractional = s32;
41
42template <typename T>
43constexpr Fractional ToFractional(T x) {
44 return static_cast<Fractional>(x * static_cast<T>(0x4000));
45}
46
47constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) {
48 return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14);
49}
50
51constexpr s32 FractionalToFixed(Fractional x) {
52 const auto s = x & (1 << 13);
53 return static_cast<s32>(x >> 14) + s;
54}
55
56constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) {
57 return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time)));
58}
36 59
37static constexpr u32 VersionFromRevision(u32_le rev) { 60static constexpr u32 VersionFromRevision(u32_le rev) {
38 // "REV7" -> 7 61 // "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 @@
1#include <cstring>
2#include "audio_core/delay_line.h"
3
4namespace AudioCore {
5DelayLineBase::DelayLineBase() = default;
6DelayLineBase::~DelayLineBase() = default;
7
8void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) {
9 buffer = src_buffer;
10 buffer_end = buffer + max_delay_;
11 max_delay = max_delay_;
12 output = buffer;
13 SetDelay(max_delay_);
14 Clear();
15}
16
17void DelayLineBase::SetDelay(s32 new_delay) {
18 if (max_delay < new_delay) {
19 return;
20 }
21 delay = new_delay;
22 input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1));
23}
24
25s32 DelayLineBase::GetDelay() const {
26 return delay;
27}
28
29s32 DelayLineBase::GetMaxDelay() const {
30 return max_delay;
31}
32
33f32 DelayLineBase::TapOut(s32 last_sample) {
34 const float* ptr = input - (last_sample + 1);
35 if (ptr < buffer) {
36 ptr += (max_delay + 1);
37 }
38
39 return *ptr;
40}
41
42f32 DelayLineBase::Tick(f32 sample) {
43 *(input++) = sample;
44 const auto out_sample = *(output++);
45
46 if (buffer_end < input) {
47 input = buffer;
48 }
49
50 if (buffer_end < output) {
51 output = buffer;
52 }
53
54 return out_sample;
55}
56
57float* DelayLineBase::GetInput() {
58 return input;
59}
60
61const float* DelayLineBase::GetInput() const {
62 return input;
63}
64
65f32 DelayLineBase::GetOutputSample() const {
66 return *output;
67}
68
69void DelayLineBase::Clear() {
70 std::memset(buffer, 0, sizeof(float) * max_delay);
71}
72
73void DelayLineBase::Reset() {
74 buffer = nullptr;
75 buffer_end = nullptr;
76 max_delay = 0;
77 input = nullptr;
78 output = nullptr;
79 delay = 0;
80}
81
82DelayLineAllPass::DelayLineAllPass() = default;
83DelayLineAllPass::~DelayLineAllPass() = default;
84
85void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) {
86 DelayLineBase::Initialize(delay_, src_buffer);
87 SetCoefficient(coeffcient_);
88}
89
90void DelayLineAllPass::SetCoefficient(float coeffcient_) {
91 coefficient = coeffcient_;
92}
93
94f32 DelayLineAllPass::Tick(f32 sample) {
95 const auto temp = sample - coefficient * *output;
96 return coefficient * temp + DelayLineBase::Tick(temp);
97}
98
99void DelayLineAllPass::Reset() {
100 coefficient = 0.0f;
101 DelayLineBase::Reset();
102}
103
104} // 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 @@
1#pragma once
2
3#include "common/common_types.h"
4
5namespace AudioCore {
6
7class DelayLineBase {
8public:
9 DelayLineBase();
10 ~DelayLineBase();
11
12 void Initialize(s32 max_delay_, float* src_buffer);
13 void SetDelay(s32 new_delay);
14 s32 GetDelay() const;
15 s32 GetMaxDelay() const;
16 f32 TapOut(s32 last_sample);
17 f32 Tick(f32 sample);
18 float* GetInput();
19 const float* GetInput() const;
20 f32 GetOutputSample() const;
21 void Clear();
22 void Reset();
23
24protected:
25 float* buffer{nullptr};
26 float* buffer_end{nullptr};
27 s32 max_delay{};
28 float* input{nullptr};
29 float* output{nullptr};
30 s32 delay{};
31};
32
33class DelayLineAllPass final : public DelayLineBase {
34public:
35 DelayLineAllPass();
36 ~DelayLineAllPass();
37
38 void Initialize(u32 delay, float coeffcient_, f32* src_buffer);
39 void SetCoefficient(float coeffcient_);
40 f32 Tick(f32 sample);
41 void Reset();
42
43private:
44 float coefficient{};
45};
46} // 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 {
90 return processing_order; 90 return processing_order;
91} 91}
92 92
93std::vector<u8>& EffectBase::GetWorkBuffer() {
94 return work_buffer;
95}
96
97const std::vector<u8>& EffectBase::GetWorkBuffer() const {
98 return work_buffer;
99}
100
93EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} 101EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {}
94EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; 102EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
95 103
@@ -117,6 +125,12 @@ void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
117 usage = UsageState::Initialized; 125 usage = UsageState::Initialized;
118 params.status = ParameterStatus::Initialized; 126 params.status = ParameterStatus::Initialized;
119 skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; 127 skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
128 if (!skipped) {
129 auto& cur_work_buffer = GetWorkBuffer();
130 // Has two buffers internally
131 cur_work_buffer.resize(in_params.buffer_size * 2);
132 std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0);
133 }
120 } 134 }
121} 135}
122 136
@@ -129,6 +143,14 @@ void EffectI3dl2Reverb::UpdateForCommandGeneration() {
129 GetParams().status = ParameterStatus::Updated; 143 GetParams().status = ParameterStatus::Updated;
130} 144}
131 145
146I3dl2ReverbState& EffectI3dl2Reverb::GetState() {
147 return state;
148}
149
150const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const {
151 return state;
152}
153
132EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} 154EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}
133EffectBiquadFilter::~EffectBiquadFilter() = default; 155EffectBiquadFilter::~EffectBiquadFilter() = default;
134 156
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 @@
8#include <memory> 8#include <memory>
9#include <vector> 9#include <vector>
10#include "audio_core/common.h" 10#include "audio_core/common.h"
11#include "audio_core/delay_line.h"
11#include "common/common_funcs.h" 12#include "common/common_funcs.h"
12#include "common/common_types.h" 13#include "common/common_types.h"
13#include "common/swap.h" 14#include "common/swap.h"
@@ -194,6 +195,8 @@ public:
194 [[nodiscard]] bool IsEnabled() const; 195 [[nodiscard]] bool IsEnabled() const;
195 [[nodiscard]] s32 GetMixID() const; 196 [[nodiscard]] s32 GetMixID() const;
196 [[nodiscard]] s32 GetProcessingOrder() const; 197 [[nodiscard]] s32 GetProcessingOrder() const;
198 [[nodiscard]] std::vector<u8>& GetWorkBuffer();
199 [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const;
197 200
198protected: 201protected:
199 UsageState usage{UsageState::Invalid}; 202 UsageState usage{UsageState::Invalid};
@@ -201,6 +204,7 @@ protected:
201 s32 mix_id{}; 204 s32 mix_id{};
202 s32 processing_order{}; 205 s32 processing_order{};
203 bool enabled = false; 206 bool enabled = false;
207 std::vector<u8> work_buffer{};
204}; 208};
205 209
206template <typename T> 210template <typename T>
@@ -212,7 +216,7 @@ public:
212 return internal_params; 216 return internal_params;
213 } 217 }
214 218
215 const I3dl2ReverbParams& GetParams() const { 219 const T& GetParams() const {
216 return internal_params; 220 return internal_params;
217 } 221 }
218 222
@@ -229,6 +233,27 @@ public:
229 void UpdateForCommandGeneration() override; 233 void UpdateForCommandGeneration() override;
230}; 234};
231 235
236struct I3dl2ReverbState {
237 f32 lowpass_0{};
238 f32 lowpass_1{};
239 f32 lowpass_2{};
240
241 DelayLineBase early_delay_line{};
242 std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{};
243 f32 early_gain{};
244 f32 late_gain{};
245
246 u32 early_to_late_taps{};
247 std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{};
248 std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{};
249 std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{};
250 f32 last_reverb_echo{};
251 DelayLineBase center_delay_line{};
252 std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{};
253 std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{};
254 f32 dry_gain{};
255};
256
232class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { 257class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
233public: 258public:
234 explicit EffectI3dl2Reverb(); 259 explicit EffectI3dl2Reverb();
@@ -237,8 +262,12 @@ public:
237 void Update(EffectInfo::InParams& in_params) override; 262 void Update(EffectInfo::InParams& in_params) override;
238 void UpdateForCommandGeneration() override; 263 void UpdateForCommandGeneration() override;
239 264
265 I3dl2ReverbState& GetState();
266 const I3dl2ReverbState& GetState() const;
267
240private: 268private:
241 bool skipped = false; 269 bool skipped = false;
270 I3dl2ReverbState state{};
242}; 271};
243 272
244class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { 273class 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
266 hle/service/am/applets/software_keyboard.h 266 hle/service/am/applets/software_keyboard.h
267 hle/service/am/applets/web_browser.cpp 267 hle/service/am/applets/web_browser.cpp
268 hle/service/am/applets/web_browser.h 268 hle/service/am/applets/web_browser.h
269 hle/service/am/applets/web_types.h
269 hle/service/am/idle.cpp 270 hle/service/am/idle.cpp
270 hle/service/am/idle.h 271 hle/service/am/idle.h
271 hle/service/am/omm.cpp 272 hle/service/am/omm.cpp
@@ -400,6 +401,7 @@ add_library(core STATIC
400 hle/service/hid/controllers/xpad.h 401 hle/service/hid/controllers/xpad.h
401 hle/service/lbl/lbl.cpp 402 hle/service/lbl/lbl.cpp
402 hle/service/lbl/lbl.h 403 hle/service/lbl/lbl.h
404 hle/service/ldn/errors.h
403 hle/service/ldn/ldn.cpp 405 hle/service/ldn/ldn.cpp
404 hle/service/ldn/ldn.h 406 hle/service/ldn/ldn.h
405 hle/service/ldr/ldr.cpp 407 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 {
31 bool allow_dual_joycons{}; 31 bool allow_dual_joycons{};
32 bool allow_left_joycon{}; 32 bool allow_left_joycon{};
33 bool allow_right_joycon{}; 33 bool allow_right_joycon{};
34 bool allow_gamecube_controller{};
34}; 35};
35 36
36class ControllerApplet { 37class 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) {
1047 1047
1048 const u64 offset{rp.Pop<u64>()}; 1048 const u64 offset{rp.Pop<u64>()};
1049 const std::vector<u8> data{ctx.ReadBuffer()}; 1049 const std::vector<u8> data{ctx.ReadBuffer()};
1050 const std::size_t size{std::min(data.size(), backing.GetSize() - offset)};
1050 1051
1051 LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size()); 1052 LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
1052 1053
1053 if (data.size() > backing.GetSize() - offset) { 1054 if (offset > backing.GetSize()) {
1054 LOG_ERROR(Service_AM, 1055 LOG_ERROR(Service_AM,
1055 "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}", 1056 "offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
1056 backing.GetSize(), data.size(), offset); 1057 backing.GetSize(), size, offset);
1057 1058
1058 IPC::ResponseBuilder rb{ctx, 2}; 1059 IPC::ResponseBuilder rb{ctx, 2};
1059 rb.Push(ERR_SIZE_OUT_OF_BOUNDS); 1060 rb.Push(ERR_SIZE_OUT_OF_BOUNDS);
1060 return; 1061 return;
1061 } 1062 }
1062 1063
1063 std::memcpy(backing.GetData().data() + offset, data.data(), data.size()); 1064 std::memcpy(backing.GetData().data() + offset, data.data(), size);
1064 1065
1065 IPC::ResponseBuilder rb{ctx, 2}; 1066 IPC::ResponseBuilder rb{ctx, 2};
1066 rb.Push(RESULT_SUCCESS); 1067 rb.Push(RESULT_SUCCESS);
@@ -1070,11 +1071,11 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
1070 IPC::RequestParser rp{ctx}; 1071 IPC::RequestParser rp{ctx};
1071 1072
1072 const u64 offset{rp.Pop<u64>()}; 1073 const u64 offset{rp.Pop<u64>()};
1073 const std::size_t size{ctx.GetWriteBufferSize()}; 1074 const std::size_t size{std::min(ctx.GetWriteBufferSize(), backing.GetSize() - offset)};
1074 1075
1075 LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size); 1076 LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
1076 1077
1077 if (size > backing.GetSize() - offset) { 1078 if (offset > backing.GetSize()) {
1078 LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}", 1079 LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
1079 backing.GetSize(), size, offset); 1080 backing.GetSize(), size, offset);
1080 1081
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 @@
21 21
22namespace Service::HID { 22namespace Service::HID {
23constexpr s32 HID_JOYSTICK_MAX = 0x7fff; 23constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
24constexpr s32 HID_TRIGGER_MAX = 0x7fff;
24[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff; 25[[maybe_unused]] constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
25constexpr std::size_t NPAD_OFFSET = 0x9A00; 26constexpr std::size_t NPAD_OFFSET = 0x9A00;
26constexpr u32 BATTERY_FULL = 2; 27constexpr u32 BATTERY_FULL = 2;
@@ -48,6 +49,8 @@ Controller_NPad::NPadControllerType Controller_NPad::MapSettingsTypeToNPad(
48 return NPadControllerType::JoyRight; 49 return NPadControllerType::JoyRight;
49 case Settings::ControllerType::Handheld: 50 case Settings::ControllerType::Handheld:
50 return NPadControllerType::Handheld; 51 return NPadControllerType::Handheld;
52 case Settings::ControllerType::GameCube:
53 return NPadControllerType::GameCube;
51 default: 54 default:
52 UNREACHABLE(); 55 UNREACHABLE();
53 return NPadControllerType::ProController; 56 return NPadControllerType::ProController;
@@ -67,6 +70,8 @@ Settings::ControllerType Controller_NPad::MapNPadToSettingsType(
67 return Settings::ControllerType::RightJoycon; 70 return Settings::ControllerType::RightJoycon;
68 case NPadControllerType::Handheld: 71 case NPadControllerType::Handheld:
69 return Settings::ControllerType::Handheld; 72 return Settings::ControllerType::Handheld;
73 case NPadControllerType::GameCube:
74 return Settings::ControllerType::GameCube;
70 default: 75 default:
71 UNREACHABLE(); 76 UNREACHABLE();
72 return Settings::ControllerType::ProController; 77 return Settings::ControllerType::ProController;
@@ -209,6 +214,13 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
209 controller.assignment_mode = NpadAssignments::Single; 214 controller.assignment_mode = NpadAssignments::Single;
210 controller.footer_type = AppletFooterUiType::JoyRightHorizontal; 215 controller.footer_type = AppletFooterUiType::JoyRightHorizontal;
211 break; 216 break;
217 case NPadControllerType::GameCube:
218 controller.style_set.gamecube.Assign(1);
219 // The GC Controller behaves like a wired Pro Controller
220 controller.device_type.fullkey.Assign(1);
221 controller.system_properties.is_vertical.Assign(1);
222 controller.system_properties.use_plus.Assign(1);
223 break;
212 case NPadControllerType::Pokeball: 224 case NPadControllerType::Pokeball:
213 controller.style_set.palma.Assign(1); 225 controller.style_set.palma.Assign(1);
214 controller.device_type.palma.Assign(1); 226 controller.device_type.palma.Assign(1);
@@ -259,6 +271,7 @@ void Controller_NPad::OnInit() {
259 style.joycon_right.Assign(1); 271 style.joycon_right.Assign(1);
260 style.joycon_dual.Assign(1); 272 style.joycon_dual.Assign(1);
261 style.fullkey.Assign(1); 273 style.fullkey.Assign(1);
274 style.gamecube.Assign(1);
262 style.palma.Assign(1); 275 style.palma.Assign(1);
263 } 276 }
264 277
@@ -339,6 +352,7 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
339 auto& pad_state = npad_pad_states[controller_idx].pad_states; 352 auto& pad_state = npad_pad_states[controller_idx].pad_states;
340 auto& lstick_entry = npad_pad_states[controller_idx].l_stick; 353 auto& lstick_entry = npad_pad_states[controller_idx].l_stick;
341 auto& rstick_entry = npad_pad_states[controller_idx].r_stick; 354 auto& rstick_entry = npad_pad_states[controller_idx].r_stick;
355 auto& trigger_entry = npad_trigger_states[controller_idx];
342 const auto& button_state = buttons[controller_idx]; 356 const auto& button_state = buttons[controller_idx];
343 const auto& analog_state = sticks[controller_idx]; 357 const auto& analog_state = sticks[controller_idx];
344 const auto [stick_l_x_f, stick_l_y_f] = 358 const auto [stick_l_x_f, stick_l_y_f] =
@@ -404,6 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
404 pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); 418 pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus());
405 pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); 419 pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus());
406 } 420 }
421
422 if (controller_type == NPadControllerType::GameCube) {
423 trigger_entry.l_analog = static_cast<s32>(
424 button_state[ZL - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0);
425 trigger_entry.r_analog = static_cast<s32>(
426 button_state[ZR - BUTTON_HID_BEGIN]->GetStatus() ? HID_TRIGGER_MAX : 0);
427 pad_state.zl.Assign(false);
428 pad_state.zr.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus());
429 pad_state.l.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus());
430 pad_state.r.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus());
431 }
407} 432}
408 433
409void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, 434void 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*
418 &npad.joy_left_states, &npad.joy_right_states, &npad.palma_states, 443 &npad.joy_left_states, &npad.joy_right_states, &npad.palma_states,
419 &npad.system_ext_states}; 444 &npad.system_ext_states};
420 445
446 // There is the posibility to have more controllers with analog triggers
447 const std::array<TriggerGeneric*, 1> controller_triggers{
448 &npad.gc_trigger_states,
449 };
450
421 for (auto* main_controller : controller_npads) { 451 for (auto* main_controller : controller_npads) {
422 main_controller->common.entry_count = 16; 452 main_controller->common.entry_count = 16;
423 main_controller->common.total_entry_count = 17; 453 main_controller->common.total_entry_count = 17;
@@ -435,6 +465,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
435 cur_entry.timestamp2 = cur_entry.timestamp; 465 cur_entry.timestamp2 = cur_entry.timestamp;
436 } 466 }
437 467
468 for (auto* analog_trigger : controller_triggers) {
469 analog_trigger->entry_count = 16;
470 analog_trigger->total_entry_count = 17;
471
472 const auto& last_entry = analog_trigger->trigger[analog_trigger->last_entry_index];
473
474 analog_trigger->timestamp = core_timing.GetCPUTicks();
475 analog_trigger->last_entry_index = (analog_trigger->last_entry_index + 1) % 17;
476
477 auto& cur_entry = analog_trigger->trigger[analog_trigger->last_entry_index];
478
479 cur_entry.timestamp = last_entry.timestamp + 1;
480 cur_entry.timestamp2 = cur_entry.timestamp;
481 }
482
438 const auto& controller_type = connected_controllers[i].type; 483 const auto& controller_type = connected_controllers[i].type;
439 484
440 if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { 485 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*
444 489
445 RequestPadStateUpdate(npad_index); 490 RequestPadStateUpdate(npad_index);
446 auto& pad_state = npad_pad_states[npad_index]; 491 auto& pad_state = npad_pad_states[npad_index];
492 auto& trigger_state = npad_trigger_states[npad_index];
447 493
448 auto& main_controller = 494 auto& main_controller =
449 npad.fullkey_states.npad[npad.fullkey_states.common.last_entry_index]; 495 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*
456 auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index]; 502 auto& pokeball_entry = npad.palma_states.npad[npad.palma_states.common.last_entry_index];
457 auto& libnx_entry = 503 auto& libnx_entry =
458 npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index]; 504 npad.system_ext_states.npad[npad.system_ext_states.common.last_entry_index];
505 auto& trigger_entry =
506 npad.gc_trigger_states.trigger[npad.gc_trigger_states.last_entry_index];
459 507
460 libnx_entry.connection_status.raw = 0; 508 libnx_entry.connection_status.raw = 0;
461 libnx_entry.connection_status.is_connected.Assign(1); 509 libnx_entry.connection_status.is_connected.Assign(1);
@@ -524,6 +572,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
524 572
525 libnx_entry.connection_status.is_right_connected.Assign(1); 573 libnx_entry.connection_status.is_right_connected.Assign(1);
526 break; 574 break;
575 case NPadControllerType::GameCube:
576 main_controller.connection_status.raw = 0;
577 main_controller.connection_status.is_connected.Assign(1);
578 main_controller.connection_status.is_wired.Assign(1);
579 main_controller.pad.pad_states.raw = pad_state.pad_states.raw;
580 main_controller.pad.l_stick = pad_state.l_stick;
581 main_controller.pad.r_stick = pad_state.r_stick;
582 trigger_entry.l_analog = trigger_state.l_analog;
583 trigger_entry.r_analog = trigger_state.r_analog;
584
585 libnx_entry.connection_status.is_wired.Assign(1);
586 break;
527 case NPadControllerType::Pokeball: 587 case NPadControllerType::Pokeball:
528 pokeball_entry.connection_status.raw = 0; 588 pokeball_entry.connection_status.raw = 0;
529 pokeball_entry.connection_status.is_connected.Assign(1); 589 pokeball_entry.connection_status.is_connected.Assign(1);
@@ -674,6 +734,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
674 right_sixaxis_entry.orientation = motion_devices[1].orientation; 734 right_sixaxis_entry.orientation = motion_devices[1].orientation;
675 } 735 }
676 break; 736 break;
737 case NPadControllerType::GameCube:
677 case NPadControllerType::Pokeball: 738 case NPadControllerType::Pokeball:
678 break; 739 break;
679 } 740 }
@@ -1135,6 +1196,8 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
1135 return style.joycon_left; 1196 return style.joycon_left;
1136 case NPadControllerType::JoyRight: 1197 case NPadControllerType::JoyRight:
1137 return style.joycon_right; 1198 return style.joycon_right;
1199 case NPadControllerType::GameCube:
1200 return style.gamecube;
1138 case NPadControllerType::Pokeball: 1201 case NPadControllerType::Pokeball:
1139 return style.palma; 1202 return style.palma;
1140 default: 1203 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:
51 JoyDual, 51 JoyDual,
52 JoyLeft, 52 JoyLeft,
53 JoyRight, 53 JoyRight,
54 GameCube,
54 Pokeball, 55 Pokeball,
55 }; 56 };
56 57
@@ -60,6 +61,7 @@ public:
60 JoyconDual = 5, 61 JoyconDual = 5,
61 JoyconLeft = 6, 62 JoyconLeft = 6,
62 JoyconRight = 7, 63 JoyconRight = 7,
64 GameCube = 8,
63 Pokeball = 9, 65 Pokeball = 9,
64 MaxNpadType = 10, 66 MaxNpadType = 10,
65 }; 67 };
@@ -389,6 +391,25 @@ private:
389 }; 391 };
390 static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size"); 392 static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
391 393
394 struct TriggerState {
395 s64_le timestamp{};
396 s64_le timestamp2{};
397 s32_le l_analog{};
398 s32_le r_analog{};
399 };
400 static_assert(sizeof(TriggerState) == 0x18, "TriggerState is an invalid size");
401
402 struct TriggerGeneric {
403 INSERT_PADDING_BYTES(0x4);
404 s64_le timestamp;
405 INSERT_PADDING_BYTES(0x4);
406 s64_le total_entry_count;
407 s64_le last_entry_index;
408 s64_le entry_count;
409 std::array<TriggerState, 17> trigger{};
410 };
411 static_assert(sizeof(TriggerGeneric) == 0x1C8, "TriggerGeneric is an invalid size");
412
392 struct NPadSystemProperties { 413 struct NPadSystemProperties {
393 union { 414 union {
394 s64_le raw{}; 415 s64_le raw{};
@@ -509,7 +530,9 @@ private:
509 AppletFooterUiType footer_type; 530 AppletFooterUiType footer_type;
510 // nfc_states needs to be checked switchbrew does not match with HW 531 // nfc_states needs to be checked switchbrew does not match with HW
511 NfcXcdHandle nfc_states; 532 NfcXcdHandle nfc_states;
512 INSERT_PADDING_BYTES(0xdef); 533 INSERT_PADDING_BYTES(0x8); // Mutex
534 TriggerGeneric gc_trigger_states;
535 INSERT_PADDING_BYTES(0xc1f);
513 }; 536 };
514 static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); 537 static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
515 538
@@ -560,6 +583,7 @@ private:
560 f32 sixaxis_fusion_parameter2{}; 583 f32 sixaxis_fusion_parameter2{};
561 bool sixaxis_at_rest{true}; 584 bool sixaxis_at_rest{true};
562 std::array<ControllerPad, 10> npad_pad_states{}; 585 std::array<ControllerPad, 10> npad_pad_states{};
586 std::array<TriggerState, 10> npad_trigger_states{};
563 bool is_in_lr_assignment_mode{false}; 587 bool is_in_lr_assignment_mode{false};
564 Core::System& system; 588 Core::System& system;
565}; 589};
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 @@
1// Copyright 2021 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/hle/result.h"
8
9namespace Service::LDN {
10
11constexpr ResultCode ERROR_DISABLED{ErrorModule::LDN, 22};
12
13} // 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 @@
6 6
7#include "core/hle/ipc_helpers.h" 7#include "core/hle/ipc_helpers.h"
8#include "core/hle/result.h" 8#include "core/hle/result.h"
9#include "core/hle/service/ldn/errors.h"
9#include "core/hle/service/ldn/ldn.h" 10#include "core/hle/service/ldn/ldn.h"
10#include "core/hle/service/sm/sm.h" 11#include "core/hle/service/sm/sm.h"
11 12
@@ -103,7 +104,7 @@ public:
103 : ServiceFramework{system_, "IUserLocalCommunicationService"} { 104 : ServiceFramework{system_, "IUserLocalCommunicationService"} {
104 // clang-format off 105 // clang-format off
105 static const FunctionInfo functions[] = { 106 static const FunctionInfo functions[] = {
106 {0, nullptr, "GetState"}, 107 {0, &IUserLocalCommunicationService::GetState, "GetState"},
107 {1, nullptr, "GetNetworkInfo"}, 108 {1, nullptr, "GetNetworkInfo"},
108 {2, nullptr, "GetIpv4Address"}, 109 {2, nullptr, "GetIpv4Address"},
109 {3, nullptr, "GetDisconnectReason"}, 110 {3, nullptr, "GetDisconnectReason"},
@@ -138,13 +139,38 @@ public:
138 RegisterHandlers(functions); 139 RegisterHandlers(functions);
139 } 140 }
140 141
141 void Initialize2(Kernel::HLERequestContext& ctx) { 142 void GetState(Kernel::HLERequestContext& ctx) {
142 LOG_WARNING(Service_LDN, "(STUBBED) called"); 143 LOG_WARNING(Service_LDN, "(STUBBED) called");
143 // Result success seem make this services start network and continue. 144
144 // If we just pass result error then it will stop and maybe try again and again. 145 IPC::ResponseBuilder rb{ctx, 3};
146
147 // Indicate a network error, as we do not actually emulate LDN
148 rb.Push(static_cast<u32>(State::Error));
149
150 rb.Push(RESULT_SUCCESS);
151 }
152
153 void Initialize2(Kernel::HLERequestContext& ctx) {
154 LOG_DEBUG(Service_LDN, "called");
155
156 is_initialized = true;
157
145 IPC::ResponseBuilder rb{ctx, 2}; 158 IPC::ResponseBuilder rb{ctx, 2};
146 rb.Push(RESULT_UNKNOWN); 159 rb.Push(RESULT_SUCCESS);
147 } 160 }
161
162private:
163 enum class State {
164 None,
165 Initialized,
166 AccessPointOpened,
167 AccessPointCreated,
168 StationOpened,
169 StationConnected,
170 Error,
171 };
172
173 bool is_initialized{};
148}; 174};
149 175
150class LDNS final : public ServiceFramework<LDNS> { 176class LDNS final : public ServiceFramework<LDNS> {
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index f67de37e3..a88ae452f 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -717,6 +717,13 @@ SDLState::SDLState() {
717 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { 717 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
718 LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError()); 718 LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
719 } 719 }
720// these hints are only defined on sdl2.0.9 or higher
721#if SDL_VERSION_ATLEAST(2, 0, 9)
722#if !SDL_VERSION_ATLEAST(2, 0, 12)
723 // There are also hints to toggle the individual drivers if needed.
724 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "0");
725#endif
726#endif
720 727
721 SDL_AddEventWatch(&SDLEventWatcher, this); 728 SDL_AddEventWatch(&SDLEventWatcher, this);
722 729
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 {
340 LeftJoycon, 340 LeftJoycon,
341 RightJoycon, 341 RightJoycon,
342 Handheld, 342 Handheld,
343 GameCube,
343}; 344};
344 345
345struct PlayerInput { 346struct PlayerInput {
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 31eb54123..12434db67 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -763,6 +763,37 @@ void Image::DownloadMemory(ImageBufferMap& map,
763 } 763 }
764} 764}
765 765
766GLuint Image::StorageHandle() noexcept {
767 switch (info.format) {
768 case PixelFormat::A8B8G8R8_SRGB:
769 case PixelFormat::B8G8R8A8_SRGB:
770 case PixelFormat::BC1_RGBA_SRGB:
771 case PixelFormat::BC2_SRGB:
772 case PixelFormat::BC3_SRGB:
773 case PixelFormat::BC7_SRGB:
774 case PixelFormat::ASTC_2D_4X4_SRGB:
775 case PixelFormat::ASTC_2D_8X8_SRGB:
776 case PixelFormat::ASTC_2D_8X5_SRGB:
777 case PixelFormat::ASTC_2D_5X4_SRGB:
778 case PixelFormat::ASTC_2D_5X5_SRGB:
779 case PixelFormat::ASTC_2D_10X8_SRGB:
780 case PixelFormat::ASTC_2D_6X6_SRGB:
781 case PixelFormat::ASTC_2D_10X10_SRGB:
782 case PixelFormat::ASTC_2D_12X12_SRGB:
783 case PixelFormat::ASTC_2D_8X6_SRGB:
784 case PixelFormat::ASTC_2D_6X5_SRGB:
785 if (store_view.handle != 0) {
786 return store_view.handle;
787 }
788 store_view.Create();
789 glTextureView(store_view.handle, ImageTarget(info), texture.handle, GL_RGBA8, 0,
790 info.resources.levels, 0, info.resources.layers);
791 return store_view.handle;
792 default:
793 return texture.handle;
794 }
795}
796
766void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) { 797void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) {
767 // Compressed formats don't have a pixel format or type 798 // Compressed formats don't have a pixel format or type
768 const bool is_compressed = gl_format == GL_NONE; 799 const bool is_compressed = gl_format == GL_NONE;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 874cf54f4..a6172f009 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -145,6 +145,8 @@ public:
145 145
146 void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies); 146 void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies);
147 147
148 GLuint StorageHandle() noexcept;
149
148 GLuint Handle() const noexcept { 150 GLuint Handle() const noexcept {
149 return texture.handle; 151 return texture.handle;
150 } 152 }
@@ -155,8 +157,8 @@ private:
155 void CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset); 157 void CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset);
156 158
157 OGLTexture texture; 159 OGLTexture texture;
158 OGLTextureView store_view;
159 OGLBuffer buffer; 160 OGLBuffer buffer;
161 OGLTextureView store_view;
160 GLenum gl_internal_format = GL_NONE; 162 GLenum gl_internal_format = GL_NONE;
161 GLenum gl_format = GL_NONE; 163 GLenum gl_format = GL_NONE;
162 GLenum gl_type = GL_NONE; 164 GLenum gl_type = GL_NONE;
diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp
index 1b58e8617..31ec68505 100644
--- a/src/video_core/renderer_opengl/util_shaders.cpp
+++ b/src/video_core/renderer_opengl/util_shaders.cpp
@@ -93,7 +93,7 @@ void UtilShaders::BlockLinearUpload2D(Image& image, const ImageBufferMap& map,
93 glUniform1ui(7, params.block_height_mask); 93 glUniform1ui(7, params.block_height_mask);
94 glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, 94 glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset,
95 image.guest_size_bytes - swizzle.buffer_offset); 95 image.guest_size_bytes - swizzle.buffer_offset);
96 glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), swizzle.level, GL_TRUE, 0, 96 glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0,
97 GL_WRITE_ONLY, store_format); 97 GL_WRITE_ONLY, store_format);
98 glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers); 98 glDispatchCompute(num_dispatches_x, num_dispatches_y, image.info.resources.layers);
99 } 99 }
@@ -134,7 +134,7 @@ void UtilShaders::BlockLinearUpload3D(Image& image, const ImageBufferMap& map,
134 glUniform1ui(9, params.block_depth_mask); 134 glUniform1ui(9, params.block_depth_mask);
135 glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset, 135 glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BINDING_INPUT_BUFFER, map.buffer, input_offset,
136 image.guest_size_bytes - swizzle.buffer_offset); 136 image.guest_size_bytes - swizzle.buffer_offset);
137 glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), swizzle.level, GL_TRUE, 0, 137 glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), swizzle.level, GL_TRUE, 0,
138 GL_WRITE_ONLY, store_format); 138 GL_WRITE_ONLY, store_format);
139 glDispatchCompute(num_dispatches_x, num_dispatches_y, num_dispatches_z); 139 glDispatchCompute(num_dispatches_x, num_dispatches_y, num_dispatches_z);
140 } 140 }
@@ -164,7 +164,8 @@ void UtilShaders::PitchUpload(Image& image, const ImageBufferMap& map,
164 glUniform2i(LOC_DESTINATION, 0, 0); 164 glUniform2i(LOC_DESTINATION, 0, 0);
165 glUniform1ui(LOC_BYTES_PER_BLOCK, bytes_per_block); 165 glUniform1ui(LOC_BYTES_PER_BLOCK, bytes_per_block);
166 glUniform1ui(LOC_PITCH, pitch); 166 glUniform1ui(LOC_PITCH, pitch);
167 glBindImageTexture(BINDING_OUTPUT_IMAGE, image.Handle(), 0, GL_FALSE, 0, GL_WRITE_ONLY, format); 167 glBindImageTexture(BINDING_OUTPUT_IMAGE, image.StorageHandle(), 0, GL_FALSE, 0, GL_WRITE_ONLY,
168 format);
168 for (const SwizzleParameters& swizzle : swizzles) { 169 for (const SwizzleParameters& swizzle : swizzles) {
169 const Extent3D num_tiles = swizzle.num_tiles; 170 const Extent3D num_tiles = swizzle.num_tiles;
170 const size_t input_offset = swizzle.buffer_offset + map.offset; 171 const size_t input_offset = swizzle.buffer_offset + map.offset;
@@ -195,9 +196,9 @@ void UtilShaders::CopyBC4(Image& dst_image, Image& src_image, std::span<const Im
195 196
196 glUniform3ui(LOC_SRC_OFFSET, copy.src_offset.x, copy.src_offset.y, copy.src_offset.z); 197 glUniform3ui(LOC_SRC_OFFSET, copy.src_offset.x, copy.src_offset.y, copy.src_offset.z);
197 glUniform3ui(LOC_DST_OFFSET, copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z); 198 glUniform3ui(LOC_DST_OFFSET, copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z);
198 glBindImageTexture(BINDING_INPUT_IMAGE, src_image.Handle(), copy.src_subresource.base_level, 199 glBindImageTexture(BINDING_INPUT_IMAGE, src_image.StorageHandle(),
199 GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI); 200 copy.src_subresource.base_level, GL_FALSE, 0, GL_READ_ONLY, GL_RG32UI);
200 glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.Handle(), 201 glBindImageTexture(BINDING_OUTPUT_IMAGE, dst_image.StorageHandle(),
201 copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI); 202 copy.dst_subresource.base_level, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI);
202 glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth); 203 glDispatchCompute(copy.extent.width, copy.extent.height, copy.extent.depth);
203 } 204 }
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
index 5be6dabd9..362278f01 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -12,14 +12,15 @@
12#include "common/cityhash.h" 12#include "common/cityhash.h"
13#include "common/common_types.h" 13#include "common/common_types.h"
14#include "video_core/renderer_vulkan/fixed_pipeline_state.h" 14#include "video_core/renderer_vulkan/fixed_pipeline_state.h"
15#include "video_core/renderer_vulkan/vk_state_tracker.h"
15 16
16namespace Vulkan { 17namespace Vulkan {
17 18
18namespace { 19namespace {
19 20
20constexpr std::size_t POINT = 0; 21constexpr size_t POINT = 0;
21constexpr std::size_t LINE = 1; 22constexpr size_t LINE = 1;
22constexpr std::size_t POLYGON = 2; 23constexpr size_t POLYGON = 2;
23constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { 24constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
24 POINT, // Points 25 POINT, // Points
25 LINE, // Lines 26 LINE, // Lines
@@ -40,10 +41,14 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
40 41
41} // Anonymous namespace 42} // Anonymous namespace
42 43
43void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) { 44void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d,
44 const std::array enabled_lut = {regs.polygon_offset_point_enable, 45 bool has_extended_dynamic_state) {
45 regs.polygon_offset_line_enable, 46 const Maxwell& regs = maxwell3d.regs;
46 regs.polygon_offset_fill_enable}; 47 const std::array enabled_lut{
48 regs.polygon_offset_point_enable,
49 regs.polygon_offset_line_enable,
50 regs.polygon_offset_fill_enable,
51 };
47 const u32 topology_index = static_cast<u32>(regs.draw.topology.Value()); 52 const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
48 53
49 raw1 = 0; 54 raw1 = 0;
@@ -64,45 +69,53 @@ void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_sta
64 69
65 raw2 = 0; 70 raw2 = 0;
66 const auto test_func = 71 const auto test_func =
67 regs.alpha_test_enabled == 1 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always; 72 regs.alpha_test_enabled != 0 ? regs.alpha_test_func : Maxwell::ComparisonOp::Always;
68 alpha_test_func.Assign(PackComparisonOp(test_func)); 73 alpha_test_func.Assign(PackComparisonOp(test_func));
69 early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0); 74 early_z.Assign(regs.force_early_fragment_tests != 0 ? 1 : 0);
70 75
71 alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref); 76 alpha_test_ref = Common::BitCast<u32>(regs.alpha_test_ref);
72 point_size = Common::BitCast<u32>(regs.point_size); 77 point_size = Common::BitCast<u32>(regs.point_size);
73 78
74 for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { 79 if (maxwell3d.dirty.flags[Dirty::InstanceDivisors]) {
75 binding_divisors[index] = 80 maxwell3d.dirty.flags[Dirty::InstanceDivisors] = false;
76 regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0; 81 for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
82 const bool is_enabled = regs.instanced_arrays.IsInstancingEnabled(index);
83 binding_divisors[index] = is_enabled ? regs.vertex_array[index].divisor : 0;
84 }
77 } 85 }
78 86 if (maxwell3d.dirty.flags[Dirty::VertexAttributes]) {
79 for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) { 87 maxwell3d.dirty.flags[Dirty::VertexAttributes] = false;
80 const auto& input = regs.vertex_attrib_format[index]; 88 for (size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
81 auto& attribute = attributes[index]; 89 const auto& input = regs.vertex_attrib_format[index];
82 attribute.raw = 0; 90 auto& attribute = attributes[index];
83 attribute.enabled.Assign(input.IsConstant() ? 0 : 1); 91 attribute.raw = 0;
84 attribute.buffer.Assign(input.buffer); 92 attribute.enabled.Assign(input.IsConstant() ? 0 : 1);
85 attribute.offset.Assign(input.offset); 93 attribute.buffer.Assign(input.buffer);
86 attribute.type.Assign(static_cast<u32>(input.type.Value())); 94 attribute.offset.Assign(input.offset);
87 attribute.size.Assign(static_cast<u32>(input.size.Value())); 95 attribute.type.Assign(static_cast<u32>(input.type.Value()));
88 attribute.binding_index_enabled.Assign(regs.vertex_array[index].IsEnabled() ? 1 : 0); 96 attribute.size.Assign(static_cast<u32>(input.size.Value()));
97 }
89 } 98 }
90 99 if (maxwell3d.dirty.flags[Dirty::Blending]) {
91 for (std::size_t index = 0; index < std::size(attachments); ++index) { 100 maxwell3d.dirty.flags[Dirty::Blending] = false;
92 attachments[index].Fill(regs, index); 101 for (size_t index = 0; index < attachments.size(); ++index) {
102 attachments[index].Refresh(regs, index);
103 }
104 }
105 if (maxwell3d.dirty.flags[Dirty::ViewportSwizzles]) {
106 maxwell3d.dirty.flags[Dirty::ViewportSwizzles] = false;
107 const auto& transform = regs.viewport_transform;
108 std::ranges::transform(transform, viewport_swizzles.begin(), [](const auto& viewport) {
109 return static_cast<u16>(viewport.swizzle.raw);
110 });
93 } 111 }
94
95 const auto& transform = regs.viewport_transform;
96 std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(),
97 [](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); });
98
99 if (!has_extended_dynamic_state) { 112 if (!has_extended_dynamic_state) {
100 no_extended_dynamic_state.Assign(1); 113 no_extended_dynamic_state.Assign(1);
101 dynamic_state.Fill(regs); 114 dynamic_state.Refresh(regs);
102 } 115 }
103} 116}
104 117
105void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) { 118void FixedPipelineState::BlendingAttachment::Refresh(const Maxwell& regs, size_t index) {
106 const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index]; 119 const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index];
107 120
108 raw = 0; 121 raw = 0;
@@ -141,7 +154,7 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size
141 enable.Assign(1); 154 enable.Assign(1);
142} 155}
143 156
144void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) { 157void FixedPipelineState::DynamicState::Refresh(const Maxwell& regs) {
145 u32 packed_front_face = PackFrontFace(regs.front_face); 158 u32 packed_front_face = PackFrontFace(regs.front_face);
146 if (regs.screen_y_control.triangle_rast_flip != 0) { 159 if (regs.screen_y_control.triangle_rast_flip != 0) {
147 // Flip front face 160 // Flip front face
@@ -178,9 +191,9 @@ void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) {
178 }); 191 });
179} 192}
180 193
181std::size_t FixedPipelineState::Hash() const noexcept { 194size_t FixedPipelineState::Hash() const noexcept {
182 const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size()); 195 const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
183 return static_cast<std::size_t>(hash); 196 return static_cast<size_t>(hash);
184} 197}
185 198
186bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept { 199bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
index 465a55fdb..a0eb83a68 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -58,7 +58,7 @@ struct FixedPipelineState {
58 BitField<30, 1, u32> enable; 58 BitField<30, 1, u32> enable;
59 }; 59 };
60 60
61 void Fill(const Maxwell& regs, std::size_t index); 61 void Refresh(const Maxwell& regs, size_t index);
62 62
63 constexpr std::array<bool, 4> Mask() const noexcept { 63 constexpr std::array<bool, 4> Mask() const noexcept {
64 return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0}; 64 return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0};
@@ -96,8 +96,6 @@ struct FixedPipelineState {
96 BitField<6, 14, u32> offset; 96 BitField<6, 14, u32> offset;
97 BitField<20, 3, u32> type; 97 BitField<20, 3, u32> type;
98 BitField<23, 6, u32> size; 98 BitField<23, 6, u32> size;
99 // Not really an element of a vertex attribute, but it can be packed here
100 BitField<29, 1, u32> binding_index_enabled;
101 99
102 constexpr Maxwell::VertexAttribute::Type Type() const noexcept { 100 constexpr Maxwell::VertexAttribute::Type Type() const noexcept {
103 return static_cast<Maxwell::VertexAttribute::Type>(type.Value()); 101 return static_cast<Maxwell::VertexAttribute::Type>(type.Value());
@@ -108,7 +106,7 @@ struct FixedPipelineState {
108 } 106 }
109 }; 107 };
110 108
111 template <std::size_t Position> 109 template <size_t Position>
112 union StencilFace { 110 union StencilFace {
113 BitField<Position + 0, 3, u32> action_stencil_fail; 111 BitField<Position + 0, 3, u32> action_stencil_fail;
114 BitField<Position + 3, 3, u32> action_depth_fail; 112 BitField<Position + 3, 3, u32> action_depth_fail;
@@ -152,7 +150,7 @@ struct FixedPipelineState {
152 // Vertex stride is a 12 bits value, we have 4 bits to spare per element 150 // Vertex stride is a 12 bits value, we have 4 bits to spare per element
153 std::array<u16, Maxwell::NumVertexArrays> vertex_strides; 151 std::array<u16, Maxwell::NumVertexArrays> vertex_strides;
154 152
155 void Fill(const Maxwell& regs); 153 void Refresh(const Maxwell& regs);
156 154
157 Maxwell::ComparisonOp DepthTestFunc() const noexcept { 155 Maxwell::ComparisonOp DepthTestFunc() const noexcept {
158 return UnpackComparisonOp(depth_test_func); 156 return UnpackComparisonOp(depth_test_func);
@@ -199,9 +197,9 @@ struct FixedPipelineState {
199 std::array<u16, Maxwell::NumViewports> viewport_swizzles; 197 std::array<u16, Maxwell::NumViewports> viewport_swizzles;
200 DynamicState dynamic_state; 198 DynamicState dynamic_state;
201 199
202 void Fill(const Maxwell& regs, bool has_extended_dynamic_state); 200 void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, bool has_extended_dynamic_state);
203 201
204 std::size_t Hash() const noexcept; 202 size_t Hash() const noexcept;
205 203
206 bool operator==(const FixedPipelineState& rhs) const noexcept; 204 bool operator==(const FixedPipelineState& rhs) const noexcept;
207 205
@@ -209,8 +207,8 @@ struct FixedPipelineState {
209 return !operator==(rhs); 207 return !operator==(rhs);
210 } 208 }
211 209
212 std::size_t Size() const noexcept { 210 size_t Size() const noexcept {
213 const std::size_t total_size = sizeof *this; 211 const size_t total_size = sizeof *this;
214 return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState)); 212 return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState));
215 } 213 }
216}; 214};
@@ -224,7 +222,7 @@ namespace std {
224 222
225template <> 223template <>
226struct hash<Vulkan::FixedPipelineState> { 224struct hash<Vulkan::FixedPipelineState> {
227 std::size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept { 225 size_t operator()(const Vulkan::FixedPipelineState& k) const noexcept {
228 return k.Hash(); 226 return k.Hash();
229 } 227 }
230}; 228};
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index d50dca604..fc6dd83eb 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -221,9 +221,6 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const SPIRVProgram& program,
221 std::vector<VkVertexInputBindingDescription> vertex_bindings; 221 std::vector<VkVertexInputBindingDescription> vertex_bindings;
222 std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors; 222 std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors;
223 for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) { 223 for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
224 if (state.attributes[index].binding_index_enabled == 0) {
225 continue;
226 }
227 const bool instanced = state.binding_divisors[index] != 0; 224 const bool instanced = state.binding_divisors[index] != 0;
228 const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; 225 const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
229 vertex_bindings.push_back({ 226 vertex_bindings.push_back({
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 684d4e3a6..dfd38f575 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -267,8 +267,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
267 267
268 query_cache.UpdateCounters(); 268 query_cache.UpdateCounters();
269 269
270 GraphicsPipelineCacheKey key; 270 graphics_key.fixed_state.Refresh(maxwell3d, device.IsExtExtendedDynamicStateSupported());
271 key.fixed_state.Fill(maxwell3d.regs, device.IsExtExtendedDynamicStateSupported());
272 271
273 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; 272 std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
274 273
@@ -276,14 +275,15 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
276 texture_cache.UpdateRenderTargets(false); 275 texture_cache.UpdateRenderTargets(false);
277 276
278 const auto shaders = pipeline_cache.GetShaders(); 277 const auto shaders = pipeline_cache.GetShaders();
279 key.shaders = GetShaderAddresses(shaders); 278 graphics_key.shaders = GetShaderAddresses(shaders);
279
280 SetupShaderDescriptors(shaders, is_indexed); 280 SetupShaderDescriptors(shaders, is_indexed);
281 281
282 const Framebuffer* const framebuffer = texture_cache.GetFramebuffer(); 282 const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
283 key.renderpass = framebuffer->RenderPass(); 283 graphics_key.renderpass = framebuffer->RenderPass();
284 284
285 auto* const pipeline = 285 VKGraphicsPipeline* const pipeline = pipeline_cache.GetGraphicsPipeline(
286 pipeline_cache.GetGraphicsPipeline(key, framebuffer->NumColorBuffers(), async_shaders); 286 graphics_key, framebuffer->NumColorBuffers(), async_shaders);
287 if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) { 287 if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) {
288 // Async graphics pipeline was not ready. 288 // Async graphics pipeline was not ready.
289 return; 289 return;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 7fc6741da..acea1ba2d 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -20,6 +20,7 @@
20#include "video_core/renderer_vulkan/vk_buffer_cache.h" 20#include "video_core/renderer_vulkan/vk_buffer_cache.h"
21#include "video_core/renderer_vulkan/vk_descriptor_pool.h" 21#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
22#include "video_core/renderer_vulkan/vk_fence_manager.h" 22#include "video_core/renderer_vulkan/vk_fence_manager.h"
23#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
23#include "video_core/renderer_vulkan/vk_pipeline_cache.h" 24#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
24#include "video_core/renderer_vulkan/vk_query_cache.h" 25#include "video_core/renderer_vulkan/vk_query_cache.h"
25#include "video_core/renderer_vulkan/vk_scheduler.h" 26#include "video_core/renderer_vulkan/vk_scheduler.h"
@@ -173,6 +174,8 @@ private:
173 VKUpdateDescriptorQueue update_descriptor_queue; 174 VKUpdateDescriptorQueue update_descriptor_queue;
174 BlitImageHelper blit_image; 175 BlitImageHelper blit_image;
175 176
177 GraphicsPipelineCacheKey graphics_key;
178
176 TextureCacheRuntime texture_cache_runtime; 179 TextureCacheRuntime texture_cache_runtime;
177 TextureCache texture_cache; 180 TextureCache texture_cache;
178 BufferCacheRuntime buffer_cache_runtime; 181 BufferCacheRuntime buffer_cache_runtime;
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;
17size_t ResourcePool::CommitResource() { 17size_t ResourcePool::CommitResource() {
18 // Refresh semaphore to query updated results 18 // Refresh semaphore to query updated results
19 master_semaphore.Refresh(); 19 master_semaphore.Refresh();
20 20 const u64 gpu_tick = master_semaphore.KnownGpuTick();
21 const auto search = [this](size_t begin, size_t end) -> std::optional<size_t> { 21 const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional<size_t> {
22 for (size_t iterator = begin; iterator < end; ++iterator) { 22 for (size_t iterator = begin; iterator < end; ++iterator) {
23 if (master_semaphore.IsFree(ticks[iterator])) { 23 if (gpu_tick >= ticks[iterator]) {
24 ticks[iterator] = master_semaphore.CurrentTick(); 24 ticks[iterator] = master_semaphore.CurrentTick();
25 return iterator; 25 return iterator;
26 } 26 }
27 } 27 }
28 return {}; 28 return std::nullopt;
29 }; 29 };
30 // Try to find a free resource from the hinted position to the end. 30 // Try to find a free resource from the hinted position to the end.
31 auto found = search(free_iterator, ticks.size()); 31 std::optional<size_t> found = search(hint_iterator, ticks.size());
32 if (!found) { 32 if (!found) {
33 // Search from beginning to the hinted position. 33 // Search from beginning to the hinted position.
34 found = search(0, free_iterator); 34 found = search(0, hint_iterator);
35 if (!found) { 35 if (!found) {
36 // Both searches failed, the pool is full; handle it. 36 // Both searches failed, the pool is full; handle it.
37 const size_t free_resource = ManageOverflow(); 37 const size_t free_resource = ManageOverflow();
@@ -41,7 +41,7 @@ size_t ResourcePool::CommitResource() {
41 } 41 }
42 } 42 }
43 // Free iterator is hinted to the resource after the one that's been commited. 43 // Free iterator is hinted to the resource after the one that's been commited.
44 free_iterator = (*found + 1) % ticks.size(); 44 hint_iterator = (*found + 1) % ticks.size();
45 return *found; 45 return *found;
46} 46}
47 47
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:
36 36
37 MasterSemaphore& master_semaphore; 37 MasterSemaphore& master_semaphore;
38 size_t grow_step = 0; ///< Number of new resources created after an overflow 38 size_t grow_step = 0; ///< Number of new resources created after an overflow
39 size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found 39 size_t hint_iterator = 0; ///< Hint to where the next free resources is likely to be found
40 std::vector<u64> ticks; ///< Ticks for each resource 40 std::vector<u64> ticks; ///< Ticks for each resource
41}; 41};
42 42
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index e81fad007..956f86845 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -18,9 +18,7 @@
18#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32))) 18#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32)))
19 19
20namespace Vulkan { 20namespace Vulkan {
21
22namespace { 21namespace {
23
24using namespace Dirty; 22using namespace Dirty;
25using namespace VideoCommon::Dirty; 23using namespace VideoCommon::Dirty;
26using Tegra::Engines::Maxwell3D; 24using Tegra::Engines::Maxwell3D;
@@ -128,6 +126,34 @@ void SetupDirtyStencilTestEnable(Tables& tables) {
128 tables[0][OFF(stencil_enable)] = StencilTestEnable; 126 tables[0][OFF(stencil_enable)] = StencilTestEnable;
129} 127}
130 128
129void SetupDirtyBlending(Tables& tables) {
130 tables[0][OFF(color_mask_common)] = Blending;
131 tables[0][OFF(independent_blend_enable)] = Blending;
132 FillBlock(tables[0], OFF(color_mask), NUM(color_mask), Blending);
133 FillBlock(tables[0], OFF(blend), NUM(blend), Blending);
134 FillBlock(tables[0], OFF(independent_blend), NUM(independent_blend), Blending);
135}
136
137void SetupDirtyInstanceDivisors(Tables& tables) {
138 static constexpr size_t divisor_offset = 3;
139 for (size_t index = 0; index < Regs::NumVertexArrays; ++index) {
140 tables[0][OFF(instanced_arrays) + index] = InstanceDivisors;
141 tables[0][OFF(vertex_array) + index * NUM(vertex_array[0]) + divisor_offset] =
142 InstanceDivisors;
143 }
144}
145
146void SetupDirtyVertexAttributes(Tables& tables) {
147 FillBlock(tables[0], OFF(vertex_attrib_format), NUM(vertex_attrib_format), VertexAttributes);
148}
149
150void SetupDirtyViewportSwizzles(Tables& tables) {
151 static constexpr size_t swizzle_offset = 6;
152 for (size_t index = 0; index < Regs::NumViewports; ++index) {
153 tables[0][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] =
154 ViewportSwizzles;
155 }
156}
131} // Anonymous namespace 157} // Anonymous namespace
132 158
133StateTracker::StateTracker(Tegra::GPU& gpu) 159StateTracker::StateTracker(Tegra::GPU& gpu)
@@ -148,6 +174,10 @@ StateTracker::StateTracker(Tegra::GPU& gpu)
148 SetupDirtyFrontFace(tables); 174 SetupDirtyFrontFace(tables);
149 SetupDirtyStencilOp(tables); 175 SetupDirtyStencilOp(tables);
150 SetupDirtyStencilTestEnable(tables); 176 SetupDirtyStencilTestEnable(tables);
177 SetupDirtyBlending(tables);
178 SetupDirtyInstanceDivisors(tables);
179 SetupDirtyVertexAttributes(tables);
180 SetupDirtyViewportSwizzles(tables);
151} 181}
152 182
153} // namespace Vulkan 183} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.h b/src/video_core/renderer_vulkan/vk_state_tracker.h
index c335d2bdf..84e918a71 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.h
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.h
@@ -35,6 +35,11 @@ enum : u8 {
35 StencilOp, 35 StencilOp,
36 StencilTestEnable, 36 StencilTestEnable,
37 37
38 Blending,
39 InstanceDivisors,
40 VertexAttributes,
41 ViewportSwizzles,
42
38 Last 43 Last
39}; 44};
40static_assert(Last <= std::numeric_limits<u8>::max()); 45static_assert(Last <= std::numeric_limits<u8>::max());
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() {
64 64
65void AsyncShaders::KillWorkers() { 65void AsyncShaders::KillWorkers() {
66 is_thread_exiting.store(true); 66 is_thread_exiting.store(true);
67 cv.notify_all();
67 for (auto& thread : worker_threads) { 68 for (auto& thread : worker_threads) {
68 thread.detach(); 69 thread.detach();
69 } 70 }
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
151 util/util.h 151 util/util.h
152 compatdb.cpp 152 compatdb.cpp
153 compatdb.h 153 compatdb.h
154 yuzu.qrc
154 yuzu.rc 155 yuzu.rc
155) 156)
156 157
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,
67 return parameters.allow_right_joycon; 67 return parameters.allow_right_joycon;
68 case Settings::ControllerType::Handheld: 68 case Settings::ControllerType::Handheld:
69 return parameters.enable_single_mode && parameters.allow_handheld; 69 return parameters.enable_single_mode && parameters.allow_handheld;
70 case Settings::ControllerType::GameCube:
71 return parameters.allow_gamecube_controller;
70 default: 72 default:
71 return false; 73 return false;
72 } 74 }
@@ -370,7 +372,7 @@ void QtControllerSelectorDialog::SetSupportedControllers() {
370 QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme)); 372 QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
371 } 373 }
372 374
373 if (parameters.allow_pro_controller) { 375 if (parameters.allow_pro_controller || parameters.allow_gamecube_controller) {
374 ui->controllerSupported5->setStyleSheet( 376 ui->controllerSupported5->setStyleSheet(
375 QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme)); 377 QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
376 } else { 378 } else {
@@ -420,6 +422,10 @@ void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index
420 Settings::ControllerType::Handheld); 422 Settings::ControllerType::Handheld);
421 emulated_controllers[player_index]->addItem(tr("Handheld")); 423 emulated_controllers[player_index]->addItem(tr("Handheld"));
422 } 424 }
425
426 pairs.emplace_back(emulated_controllers[player_index]->count(),
427 Settings::ControllerType::GameCube);
428 emulated_controllers[player_index]->addItem(tr("GameCube Controller"));
423} 429}
424 430
425Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex( 431Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex(
@@ -461,6 +467,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
461 switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(), 467 switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
462 player_index)) { 468 player_index)) {
463 case Settings::ControllerType::ProController: 469 case Settings::ControllerType::ProController:
470 case Settings::ControllerType::GameCube:
464 return QStringLiteral("image: url(:/controller/applet_pro_controller%0); "); 471 return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
465 case Settings::ControllerType::DualJoyconDetached: 472 case Settings::ControllerType::DualJoyconDetached:
466 return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); "); 473 return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 0635d13d0..3d6f64300 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -614,12 +614,6 @@ void Config::ReadDataStorageValues() {
614 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))) 614 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)))
615 .toString() 615 .toString()
616 .toStdString()); 616 .toStdString());
617 FS::GetUserPath(FS::UserPath::CacheDir,
618 qt_config
619 ->value(QStringLiteral("cache_directory"),
620 QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)))
621 .toString()
622 .toStdString());
623 Settings::values.gamecard_inserted = 617 Settings::values.gamecard_inserted =
624 ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); 618 ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool();
625 Settings::values.gamecard_current_game = 619 Settings::values.gamecard_current_game =
@@ -1218,9 +1212,6 @@ void Config::SaveDataStorageValues() {
1218 WriteSetting(QStringLiteral("dump_directory"), 1212 WriteSetting(QStringLiteral("dump_directory"),
1219 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)), 1213 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)),
1220 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir))); 1214 QString::fromStdString(FS::GetUserPath(FS::UserPath::DumpDir)));
1221 WriteSetting(QStringLiteral("cache_directory"),
1222 QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)),
1223 QString::fromStdString(FS::GetUserPath(FS::UserPath::CacheDir)));
1224 WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); 1215 WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false);
1225 WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, 1216 WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game,
1226 false); 1217 false);
diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp
index 7ab4a80f7..bde2d4620 100644
--- a/src/yuzu/configuration/configure_filesystem.cpp
+++ b/src/yuzu/configuration/configure_filesystem.cpp
@@ -26,8 +26,6 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
26 [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); }); 26 [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); });
27 connect(ui->load_path_button, &QToolButton::pressed, this, 27 connect(ui->load_path_button, &QToolButton::pressed, this,
28 [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); }); 28 [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); });
29 connect(ui->cache_directory_button, &QToolButton::pressed, this,
30 [this] { SetDirectory(DirectoryTarget::Cache, ui->cache_directory_edit); });
31 29
32 connect(ui->reset_game_list_cache, &QPushButton::pressed, this, 30 connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
33 &ConfigureFilesystem::ResetMetadata); 31 &ConfigureFilesystem::ResetMetadata);
@@ -50,8 +48,6 @@ void ConfigureFilesystem::setConfiguration() {
50 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir))); 48 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir)));
51 ui->load_path_edit->setText( 49 ui->load_path_edit->setText(
52 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir))); 50 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)));
53 ui->cache_directory_edit->setText(
54 QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)));
55 51
56 ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); 52 ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
57 ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); 53 ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
@@ -72,9 +68,6 @@ void ConfigureFilesystem::applyConfiguration() {
72 ui->dump_path_edit->text().toStdString()); 68 ui->dump_path_edit->text().toStdString());
73 Common::FS::GetUserPath(Common::FS::UserPath::LoadDir, 69 Common::FS::GetUserPath(Common::FS::UserPath::LoadDir,
74 ui->load_path_edit->text().toStdString()); 70 ui->load_path_edit->text().toStdString());
75 Common::FS::GetUserPath(Common::FS::UserPath::CacheDir,
76 ui->cache_directory_edit->text().toStdString());
77 Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();
78 71
79 Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); 72 Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
80 Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); 73 Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked();
@@ -103,9 +96,6 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)
103 case DirectoryTarget::Load: 96 case DirectoryTarget::Load:
104 caption = tr("Select Mod Load Directory..."); 97 caption = tr("Select Mod Load Directory...");
105 break; 98 break;
106 case DirectoryTarget::Cache:
107 caption = tr("Select Cache Directory...");
108 break;
109 } 99 }
110 100
111 QString str; 101 QString str;
diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h
index a79303760..2147cd405 100644
--- a/src/yuzu/configuration/configure_filesystem.h
+++ b/src/yuzu/configuration/configure_filesystem.h
@@ -32,7 +32,6 @@ private:
32 Gamecard, 32 Gamecard,
33 Dump, 33 Dump,
34 Load, 34 Load,
35 Cache,
36 }; 35 };
37 36
38 void SetDirectory(DirectoryTarget target, QLineEdit* edit); 37 void SetDirectory(DirectoryTarget target, QLineEdit* edit);
diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui
index 84bea0600..62b9abc7a 100644
--- a/src/yuzu/configuration/configure_filesystem.ui
+++ b/src/yuzu/configuration/configure_filesystem.ui
@@ -198,40 +198,7 @@
198 <string>Caching</string> 198 <string>Caching</string>
199 </property> 199 </property>
200 <layout class="QGridLayout" name="gridLayout_5"> 200 <layout class="QGridLayout" name="gridLayout_5">
201 <item row="0" column="0"> 201 <item row="0" column="0" colspan="2">
202 <widget class="QLabel" name="label_10">
203 <property name="text">
204 <string>Cache Directory</string>
205 </property>
206 </widget>
207 </item>
208 <item row="0" column="1">
209 <spacer name="horizontalSpacer_3">
210 <property name="orientation">
211 <enum>Qt::Horizontal</enum>
212 </property>
213 <property name="sizeType">
214 <enum>QSizePolicy::Fixed</enum>
215 </property>
216 <property name="sizeHint" stdset="0">
217 <size>
218 <width>40</width>
219 <height>20</height>
220 </size>
221 </property>
222 </spacer>
223 </item>
224 <item row="0" column="2">
225 <widget class="QLineEdit" name="cache_directory_edit"/>
226 </item>
227 <item row="0" column="3">
228 <widget class="QToolButton" name="cache_directory_button">
229 <property name="text">
230 <string>...</string>
231 </property>
232 </widget>
233 </item>
234 <item row="1" column="0" colspan="4">
235 <layout class="QHBoxLayout" name="horizontalLayout_2"> 202 <layout class="QHBoxLayout" name="horizontalLayout_2">
236 <item> 203 <item>
237 <widget class="QCheckBox" name="cache_game_list"> 204 <widget class="QCheckBox" name="cache_game_list">
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
467 467
468 UpdateControllerIcon(); 468 UpdateControllerIcon();
469 UpdateControllerAvailableButtons(); 469 UpdateControllerAvailableButtons();
470 UpdateControllerEnabledButtons();
471 UpdateControllerButtonNames();
470 UpdateMotionButtons(); 472 UpdateMotionButtons();
471 connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { 473 connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
472 UpdateControllerIcon(); 474 UpdateControllerIcon();
473 UpdateControllerAvailableButtons(); 475 UpdateControllerAvailableButtons();
476 UpdateControllerEnabledButtons();
477 UpdateControllerButtonNames();
474 UpdateMotionButtons(); 478 UpdateMotionButtons();
475 }); 479 });
476 480
@@ -558,9 +562,6 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
558 &ConfigureInputPlayer::SaveProfile); 562 &ConfigureInputPlayer::SaveProfile);
559 563
560 LoadConfiguration(); 564 LoadConfiguration();
561
562 // TODO(wwylele): enable this when we actually emulate it
563 ui->buttonHome->setEnabled(false);
564 ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param); 565 ui->controllerFrame->SetPlayerInput(player_index, buttons_param, analogs_param);
565 ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked()); 566 ui->controllerFrame->SetConnectedStatus(ui->groupConnectedController->isChecked());
566} 567}
@@ -924,6 +925,12 @@ void ConfigureInputPlayer::SetConnectableControllers() {
924 Settings::ControllerType::Handheld); 925 Settings::ControllerType::Handheld);
925 ui->comboControllerType->addItem(tr("Handheld")); 926 ui->comboControllerType->addItem(tr("Handheld"));
926 } 927 }
928
929 if (enable_all || npad_style_set.gamecube == 1) {
930 index_controller_type_pairs.emplace_back(ui->comboControllerType->count(),
931 Settings::ControllerType::GameCube);
932 ui->comboControllerType->addItem(tr("GameCube Controller"));
933 }
927 }; 934 };
928 935
929 Core::System& system{Core::System::GetInstance()}; 936 Core::System& system{Core::System::GetInstance()};
@@ -1014,7 +1021,7 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
1014 1021
1015 // List of all the widgets that will be hidden by any of the following layouts that need 1022 // List of all the widgets that will be hidden by any of the following layouts that need
1016 // "unhidden" after the controller type changes 1023 // "unhidden" after the controller type changes
1017 const std::array<QWidget*, 9> layout_show = { 1024 const std::array<QWidget*, 11> layout_show = {
1018 ui->buttonShoulderButtonsSLSR, 1025 ui->buttonShoulderButtonsSLSR,
1019 ui->horizontalSpacerShoulderButtonsWidget, 1026 ui->horizontalSpacerShoulderButtonsWidget,
1020 ui->horizontalSpacerShoulderButtonsWidget2, 1027 ui->horizontalSpacerShoulderButtonsWidget2,
@@ -1024,6 +1031,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
1024 ui->buttonShoulderButtonsRight, 1031 ui->buttonShoulderButtonsRight,
1025 ui->buttonMiscButtonsPlusHome, 1032 ui->buttonMiscButtonsPlusHome,
1026 ui->bottomRight, 1033 ui->bottomRight,
1034 ui->buttonMiscButtonsMinusGroup,
1035 ui->buttonMiscButtonsScreenshotGroup,
1027 }; 1036 };
1028 1037
1029 for (auto* widget : layout_show) { 1038 for (auto* widget : layout_show) {
@@ -1056,6 +1065,14 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
1056 ui->bottomLeft, 1065 ui->bottomLeft,
1057 }; 1066 };
1058 break; 1067 break;
1068 case Settings::ControllerType::GameCube:
1069 layout_hidden = {
1070 ui->buttonShoulderButtonsSLSR,
1071 ui->horizontalSpacerShoulderButtonsWidget2,
1072 ui->buttonMiscButtonsMinusGroup,
1073 ui->buttonMiscButtonsScreenshotGroup,
1074 };
1075 break;
1059 } 1076 }
1060 1077
1061 for (auto* widget : layout_hidden) { 1078 for (auto* widget : layout_hidden) {
@@ -1063,6 +1080,52 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
1063 } 1080 }
1064} 1081}
1065 1082
1083void ConfigureInputPlayer::UpdateControllerEnabledButtons() {
1084 auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
1085 if (debug) {
1086 layout = Settings::ControllerType::ProController;
1087 }
1088
1089 // List of all the widgets that will be disabled by any of the following layouts that need
1090 // "enabled" after the controller type changes
1091 const std::array<QWidget*, 4> layout_enable = {
1092 ui->buttonHome,
1093 ui->buttonLStickPressedGroup,
1094 ui->groupRStickPressed,
1095 ui->buttonShoulderButtonsButtonLGroup,
1096 };
1097
1098 for (auto* widget : layout_enable) {
1099 widget->setEnabled(true);
1100 }
1101
1102 std::vector<QWidget*> layout_disable;
1103 switch (layout) {
1104 case Settings::ControllerType::ProController:
1105 case Settings::ControllerType::DualJoyconDetached:
1106 case Settings::ControllerType::Handheld:
1107 case Settings::ControllerType::LeftJoycon:
1108 case Settings::ControllerType::RightJoycon:
1109 // TODO(wwylele): enable this when we actually emulate it
1110 layout_disable = {
1111 ui->buttonHome,
1112 };
1113 break;
1114 case Settings::ControllerType::GameCube:
1115 layout_disable = {
1116 ui->buttonHome,
1117 ui->buttonLStickPressedGroup,
1118 ui->groupRStickPressed,
1119 ui->buttonShoulderButtonsButtonLGroup,
1120 };
1121 break;
1122 }
1123
1124 for (auto* widget : layout_disable) {
1125 widget->setEnabled(false);
1126 }
1127}
1128
1066void ConfigureInputPlayer::UpdateMotionButtons() { 1129void ConfigureInputPlayer::UpdateMotionButtons() {
1067 if (debug) { 1130 if (debug) {
1068 // Motion isn't used with the debug controller, hide both groupboxes. 1131 // Motion isn't used with the debug controller, hide both groupboxes.
@@ -1085,6 +1148,11 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
1085 ui->buttonMotionLeftGroup->hide(); 1148 ui->buttonMotionLeftGroup->hide();
1086 ui->buttonMotionRightGroup->show(); 1149 ui->buttonMotionRightGroup->show();
1087 break; 1150 break;
1151 case Settings::ControllerType::GameCube:
1152 // Hide both "Motion 1/2".
1153 ui->buttonMotionLeftGroup->hide();
1154 ui->buttonMotionRightGroup->hide();
1155 break;
1088 case Settings::ControllerType::DualJoyconDetached: 1156 case Settings::ControllerType::DualJoyconDetached:
1089 default: 1157 default:
1090 // Show both "Motion 1/2". 1158 // Show both "Motion 1/2".
@@ -1094,6 +1162,36 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
1094 } 1162 }
1095} 1163}
1096 1164
1165void ConfigureInputPlayer::UpdateControllerButtonNames() {
1166 auto layout = GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
1167 if (debug) {
1168 layout = Settings::ControllerType::ProController;
1169 }
1170
1171 switch (layout) {
1172 case Settings::ControllerType::ProController:
1173 case Settings::ControllerType::DualJoyconDetached:
1174 case Settings::ControllerType::Handheld:
1175 case Settings::ControllerType::LeftJoycon:
1176 case Settings::ControllerType::RightJoycon:
1177 ui->buttonMiscButtonsPlusGroup->setTitle(tr("Plus"));
1178 ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("ZL"));
1179 ui->buttonShoulderButtonsZRGroup->setTitle(tr("ZR"));
1180 ui->buttonShoulderButtonsRGroup->setTitle(tr("R"));
1181 ui->LStick->setTitle(tr("Left Stick"));
1182 ui->RStick->setTitle(tr("Right Stick"));
1183 break;
1184 case Settings::ControllerType::GameCube:
1185 ui->buttonMiscButtonsPlusGroup->setTitle(tr("Start / Pause"));
1186 ui->buttonShoulderButtonsButtonZLGroup->setTitle(tr("L"));
1187 ui->buttonShoulderButtonsZRGroup->setTitle(tr("R"));
1188 ui->buttonShoulderButtonsRGroup->setTitle(tr("Z"));
1189 ui->LStick->setTitle(tr("Control Stick"));
1190 ui->RStick->setTitle(tr("C-Stick"));
1191 break;
1192 }
1193}
1194
1097void ConfigureInputPlayer::UpdateMappingWithDefaults() { 1195void ConfigureInputPlayer::UpdateMappingWithDefaults() {
1098 if (ui->comboDevices->currentIndex() == 0) { 1196 if (ui->comboDevices->currentIndex() == 0) {
1099 return; 1197 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:
143 /// Hides and disables controller settings based on the current controller type. 143 /// Hides and disables controller settings based on the current controller type.
144 void UpdateControllerAvailableButtons(); 144 void UpdateControllerAvailableButtons();
145 145
146 /// Disables controller settings based on the current controller type.
147 void UpdateControllerEnabledButtons();
148
146 /// Shows or hides motion groupboxes based on the current controller type. 149 /// Shows or hides motion groupboxes based on the current controller type.
147 void UpdateMotionButtons(); 150 void UpdateMotionButtons();
148 151
152 /// Alters the button names based on the current controller type.
153 void UpdateControllerButtonNames();
154
149 /// Gets the default controller mapping for this device and auto configures the input to match. 155 /// Gets the default controller mapping for this device and auto configures the input to match.
150 void UpdateMappingWithDefaults(); 156 void UpdateMappingWithDefaults();
151 157
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) {
227 case Settings::ControllerType::RightJoycon: 227 case Settings::ControllerType::RightJoycon:
228 DrawRightController(p, center); 228 DrawRightController(p, center);
229 break; 229 break;
230 case Settings::ControllerType::GameCube:
231 DrawGCController(p, center);
232 break;
230 case Settings::ControllerType::ProController: 233 case Settings::ControllerType::ProController:
231 default: 234 default:
232 DrawProController(p, center); 235 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() {
42 42
43QAction* ControllerDialog::toggleViewAction() { 43QAction* ControllerDialog::toggleViewAction() {
44 if (toggle_view_action == nullptr) { 44 if (toggle_view_action == nullptr) {
45 toggle_view_action = new QAction(windowTitle(), this); 45 toggle_view_action = new QAction(tr("&Controller P1"), this);
46 toggle_view_action->setCheckable(true); 46 toggle_view_action->setCheckable(true);
47 toggle_view_action->setChecked(isVisible()); 47 toggle_view_action->setChecked(isVisible());
48 connect(toggle_view_action, &QAction::toggled, this, &ControllerDialog::setVisible); 48 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) {
2770 .arg(errors)); 2770 .arg(errors));
2771 } 2771 }
2772 2772
2773 QProgressDialog prog; 2773 QProgressDialog prog(this);
2774 prog.setRange(0, 0); 2774 prog.setRange(0, 0);
2775 prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your " 2775 prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your "
2776 "system's performance.")); 2776 "system's performance."));
@@ -2952,7 +2952,7 @@ void GMainWindow::filterBarSetChecked(bool state) {
2952} 2952}
2953 2953
2954void GMainWindow::UpdateUITheme() { 2954void GMainWindow::UpdateUITheme() {
2955 const QString default_icons = QStringLiteral(":/icons/default"); 2955 const QString default_icons = QStringLiteral("default");
2956 const QString& current_theme = UISettings::values.theme; 2956 const QString& current_theme = UISettings::values.theme;
2957 const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second); 2957 const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);
2958 QStringList theme_paths(default_theme_paths); 2958 QStringList theme_paths(default_theme_paths);
@@ -2968,7 +2968,6 @@ void GMainWindow::UpdateUITheme() {
2968 qApp->setStyleSheet({}); 2968 qApp->setStyleSheet({});
2969 setStyleSheet({}); 2969 setStyleSheet({});
2970 } 2970 }
2971 theme_paths.append(default_icons);
2972 QIcon::setThemeName(default_icons); 2971 QIcon::setThemeName(default_icons);
2973 } else { 2972 } else {
2974 const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss")); 2973 const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
@@ -2980,10 +2979,7 @@ void GMainWindow::UpdateUITheme() {
2980 } else { 2979 } else {
2981 LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); 2980 LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
2982 } 2981 }
2983 2982 QIcon::setThemeName(current_theme);
2984 const QString theme_name = QStringLiteral(":/icons/") + current_theme;
2985 theme_paths.append({default_icons, theme_name});
2986 QIcon::setThemeName(theme_name);
2987 } 2983 }
2988 2984
2989 QIcon::setThemeSearchPaths(theme_paths); 2985 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 @@
14 <string>yuzu</string> 14 <string>yuzu</string>
15 </property> 15 </property>
16 <property name="windowIcon"> 16 <property name="windowIcon">
17 <iconset> 17 <iconset resource="yuzu.qrc">
18 <normaloff>../dist/yuzu.ico</normaloff>../dist/yuzu.ico</iconset> 18 <normaloff>:/img/yuzu.ico</normaloff>:/img/yuzu.ico</iconset>
19 </property> 19 </property>
20 <property name="tabShape"> 20 <property name="tabShape">
21 <enum>QTabWidget::Rounded</enum> 21 <enum>QTabWidget::Rounded</enum>
@@ -303,6 +303,8 @@
303 </property> 303 </property>
304 </action> 304 </action>
305 </widget> 305 </widget>
306 <resources/> 306 <resources>
307 <include location="yuzu.qrc"/>
308 </resources>
307 <connections/> 309 <connections/>
308</ui> 310</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 @@
1<RCC>
2 <qresource prefix="/img">
3 <file alias="yuzu.ico">../../dist/yuzu.ico</file>
4 </qresource>
5</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 @@
1set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) 1set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
2 2
3function(create_resource file output filename)
4 # Read hex data from file
5 file(READ ${file} filedata HEX)
6 # Convert hex data for C compatibility
7 string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata})
8 # Write data to output file
9 set(RESOURCES_DIR "${PROJECT_BINARY_DIR}/dist" PARENT_SCOPE)
10 file(WRITE "${PROJECT_BINARY_DIR}/dist/${output}" "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n")
11endfunction()
12
3add_executable(yuzu-cmd 13add_executable(yuzu-cmd
4 config.cpp 14 config.cpp
5 config.h 15 config.h
@@ -24,6 +34,9 @@ if (MSVC)
24endif() 34endif()
25target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads) 35target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads)
26 36
37create_resource("../../dist/yuzu.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
38target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR})
39
27target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include) 40target_include_directories(yuzu-cmd PRIVATE ../../externals/Vulkan-Headers/include)
28 41
29if(UNIX AND NOT APPLE) 42if(UNIX AND NOT APPLE)
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index aa0a9f288..6d8bc5509 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -329,9 +329,6 @@ void Config::ReadValues() {
329 FS::GetUserPath( 329 FS::GetUserPath(
330 FS::UserPath::DumpDir, 330 FS::UserPath::DumpDir,
331 sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir))); 331 sdl2_config->Get("Data Storage", "dump_directory", FS::GetUserPath(FS::UserPath::DumpDir)));
332 FS::GetUserPath(FS::UserPath::CacheDir,
333 sdl2_config->Get("Data Storage", "cache_directory",
334 FS::GetUserPath(FS::UserPath::CacheDir)));
335 Settings::values.gamecard_inserted = 332 Settings::values.gamecard_inserted =
336 sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); 333 sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false);
337 Settings::values.gamecard_current_game = 334 Settings::values.gamecard_current_game =
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 @@
12#include "input_common/mouse/mouse_input.h" 12#include "input_common/mouse/mouse_input.h"
13#include "input_common/sdl/sdl.h" 13#include "input_common/sdl/sdl.h"
14#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 14#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
15#include "yuzu_cmd/yuzu_icon.h"
15 16
16EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_) 17EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_)
17 : input_subsystem{input_subsystem_} { 18 : input_subsystem{input_subsystem_} {
@@ -194,6 +195,22 @@ void EmuWindow_SDL2::WaitEvent() {
194 } 195 }
195} 196}
196 197
198void EmuWindow_SDL2::SetWindowIcon() {
199 SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size);
200 if (yuzu_icon_stream == nullptr) {
201 LOG_WARNING(Frontend, "Failed to create yuzu icon stream.");
202 return;
203 }
204 SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1);
205 if (window_icon == nullptr) {
206 LOG_WARNING(Frontend, "Failed to read BMP from stream.");
207 return;
208 }
209 // The icon is attached to the window pointer
210 SDL_SetWindowIcon(render_window, window_icon);
211 SDL_FreeSurface(window_icon);
212}
213
197void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) { 214void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) {
198 SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); 215 SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
199} 216}
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:
32 /// Wait for the next event on the main thread. 32 /// Wait for the next event on the main thread.
33 void WaitEvent(); 33 void WaitEvent();
34 34
35 // Sets the window icon from yuzu.bmp
36 void SetWindowIcon();
37
35protected: 38protected:
36 /// Called by WaitEvent when a key is pressed or released. 39 /// Called by WaitEvent when a key is pressed or released.
37 void OnKeyEvent(int key, u8 state); 40 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
107 dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, 107 dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
108 SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); 108 SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
109 109
110 SetWindowIcon();
111
110 if (fullscreen) { 112 if (fullscreen) {
111 Fullscreen(); 113 Fullscreen();
112 } 114 }
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
35 std::exit(EXIT_FAILURE); 35 std::exit(EXIT_FAILURE);
36 } 36 }
37 37
38 SetWindowIcon();
39
38 switch (wm.subsystem) { 40 switch (wm.subsystem) {
39#ifdef SDL_VIDEO_DRIVER_WINDOWS 41#ifdef SDL_VIDEO_DRIVER_WINDOWS
40 case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: 42 case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: