summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/audio_core/command_generator.cpp23
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h4
-rw-r--r--src/core/core.cpp2
-rw-r--r--src/core/file_sys/content_archive.cpp10
-rw-r--r--src/core/file_sys/control_metadata.h2
-rw-r--r--src/core/file_sys/ips_layer.cpp6
-rw-r--r--src/core/file_sys/submission_package.cpp10
-rw-r--r--src/core/file_sys/vfs.cpp7
-rw-r--r--src/core/file_sys/vfs_offset.cpp7
-rw-r--r--src/core/file_sys/vfs_static.h8
-rw-r--r--src/core/frontend/input.h22
-rw-r--r--src/core/hle/service/am/am.cpp10
-rw-r--r--src/core/hle/service/am/am.h2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp114
-rw-r--r--src/core/hle/service/hid/controllers/npad.h64
-rw-r--r--src/core/hle/service/hid/hid.cpp54
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/nfp/nfp.cpp23
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp4
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h2
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp6
-rw-r--r--src/core/hle/service/service.cpp22
-rw-r--r--src/core/hle/service/sm/sm.cpp11
-rw-r--r--src/core/hle/service/sm/sm.h7
-rw-r--r--src/core/hle/service/sockets/bsd.cpp2
-rw-r--r--src/core/loader/nso.cpp6
-rw-r--r--src/core/memory.cpp2
-rw-r--r--src/core/settings.h1
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp97
-rw-r--r--src/input_common/gcadapter/gc_adapter.h5
-rw-r--r--src/input_common/main.cpp57
-rw-r--r--src/input_common/main.h24
-rw-r--r--src/input_common/motion_emu.cpp17
-rw-r--r--src/input_common/settings.cpp7
-rw-r--r--src/input_common/settings.h17
-rw-r--r--src/input_common/udp/client.cpp176
-rw-r--r--src/input_common/udp/client.h77
-rw-r--r--src/input_common/udp/udp.cpp181
-rw-r--r--src/input_common/udp/udp.h61
-rw-r--r--src/video_core/engines/fermi_2d.cpp12
-rw-r--r--src/video_core/engines/fermi_2d.h4
-rw-r--r--src/video_core/engines/maxwell_3d.cpp2
-rw-r--r--src/video_core/macro/macro.cpp2
-rw-r--r--src/video_core/memory_manager.cpp6
-rw-r--r--src/video_core/renderer_base.h5
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp12
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp288
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h16
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp5
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h1
-rw-r--r--src/video_core/shader/ast.h25
-rw-r--r--src/video_core/shader/control_flow.cpp27
-rw-r--r--src/video_core/shader/decode/image.cpp1
-rw-r--r--src/video_core/shader/decode/texture.cpp2
-rw-r--r--src/video_core/shader/track.cpp4
-rw-r--r--src/video_core/texture_cache/surface_base.cpp10
-rw-r--r--src/yuzu/bootmanager.cpp23
-rw-r--r--src/yuzu/configuration/config.cpp31
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configure_input.cpp7
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp195
-rw-r--r--src/yuzu/configuration/configure_input_player.h10
-rw-r--r--src/yuzu/configuration/configure_input_player.ui109
-rw-r--r--src/yuzu/game_list.cpp80
-rw-r--r--src/yuzu/game_list.h24
-rw-r--r--src/yuzu/install_dialog.h5
-rw-r--r--src/yuzu/main.cpp15
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h9
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp15
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp9
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h7
-rw-r--r--src/yuzu_cmd/yuzu.cpp7
-rw-r--r--src/yuzu_tester/config.cpp1
77 files changed, 1392 insertions, 748 deletions
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
index 84782cde6..8f7da49e6 100644
--- a/src/audio_core/command_generator.cpp
+++ b/src/audio_core/command_generator.cpp
@@ -196,7 +196,7 @@ void CommandGenerator::PreCommand() {
196 for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { 196 for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
197 const auto& base = splitter_context.GetInfo(i); 197 const auto& base = splitter_context.GetInfo(i);
198 std::string graph = fmt::format("b[{}]", i); 198 std::string graph = fmt::format("b[{}]", i);
199 auto* head = base.GetHead(); 199 const auto* head = base.GetHead();
200 while (head != nullptr) { 200 while (head != nullptr) {
201 graph += fmt::format("->{}", head->GetMixId()); 201 graph += fmt::format("->{}", head->GetMixId());
202 head = head->GetNextDestination(); 202 head = head->GetNextDestination();
@@ -214,7 +214,7 @@ void CommandGenerator::PostCommand() {
214 214
215void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, 215void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
216 s32 channel) { 216 s32 channel) {
217 auto& in_params = voice_info.GetInParams(); 217 const auto& in_params = voice_info.GetInParams();
218 const auto depop = in_params.should_depop; 218 const auto depop = in_params.should_depop;
219 219
220 if (depop) { 220 if (depop) {
@@ -405,7 +405,7 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset,
405} 405}
406 406
407void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) { 407void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
408 auto aux = dynamic_cast<EffectAuxInfo*>(info); 408 auto* aux = dynamic_cast<EffectAuxInfo*>(info);
409 const auto& params = aux->GetParams(); 409 const auto& params = aux->GetParams();
410 if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) { 410 if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
411 const auto max_channels = params.count; 411 const auto max_channels = params.count;
@@ -571,7 +571,7 @@ void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
571 if (dumping_frame) { 571 if (dumping_frame) {
572 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand"); 572 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
573 } 573 }
574 auto& in_params = mix_info.GetInParams(); 574 const auto& in_params = mix_info.GetInParams();
575 GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, 575 GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
576 in_params.sample_rate); 576 in_params.sample_rate);
577 577
@@ -650,7 +650,7 @@ void CommandGenerator::GenerateFinalMixCommand() {
650 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); 650 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
651 } 651 }
652 auto& mix_info = mix_context.GetFinalMixInfo(); 652 auto& mix_info = mix_context.GetFinalMixInfo();
653 const auto in_params = mix_info.GetInParams(); 653 const auto& in_params = mix_info.GetInParams();
654 654
655 GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, 655 GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
656 in_params.sample_rate); 656 in_params.sample_rate);
@@ -674,7 +674,7 @@ void CommandGenerator::GenerateFinalMixCommand() {
674 674
675s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, 675s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
676 s32 sample_count, s32 channel, std::size_t mix_offset) { 676 s32 sample_count, s32 channel, std::size_t mix_offset) {
677 auto& in_params = voice_info.GetInParams(); 677 const auto& in_params = voice_info.GetInParams();
678 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; 678 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
679 if (wave_buffer.buffer_address == 0) { 679 if (wave_buffer.buffer_address == 0) {
680 return 0; 680 return 0;
@@ -714,7 +714,7 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s
714 714
715s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, 715s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
716 s32 sample_count, s32 channel, std::size_t mix_offset) { 716 s32 sample_count, s32 channel, std::size_t mix_offset) {
717 auto& in_params = voice_info.GetInParams(); 717 const auto& in_params = voice_info.GetInParams();
718 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; 718 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
719 if (wave_buffer.buffer_address == 0) { 719 if (wave_buffer.buffer_address == 0) {
720 return 0; 720 return 0;
@@ -766,8 +766,8 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s
766 val = std::clamp<s32>(val, -32768, 32767); 766 val = std::clamp<s32>(val, -32768, 32767);
767 // Advance output feedback. 767 // Advance output feedback.
768 yn2 = yn1; 768 yn2 = yn1;
769 yn1 = val; 769 yn1 = static_cast<s16>(val);
770 return static_cast<s16>(val); 770 return yn1;
771 }; 771 };
772 772
773 std::size_t buffer_offset{}; 773 std::size_t buffer_offset{};
@@ -853,7 +853,7 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
853 VoiceState& dsp_state, s32 channel, 853 VoiceState& dsp_state, s32 channel,
854 s32 target_sample_rate, s32 sample_count, 854 s32 target_sample_rate, s32 sample_count,
855 s32 node_id) { 855 s32 node_id) {
856 auto& in_params = voice_info.GetInParams(); 856 const auto& in_params = voice_info.GetInParams();
857 if (dumping_frame) { 857 if (dumping_frame) {
858 LOG_DEBUG(Audio, 858 LOG_DEBUG(Audio,
859 "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " 859 "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
@@ -867,7 +867,8 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o
867 static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) * 867 static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
868 static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f))); 868 static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
869 auto* output_base = output; 869 auto* output_base = output;
870 if ((dsp_state.fraction + sample_count * resample_rate) > (SCALED_MIX_BUFFER_SIZE - 4ULL)) { 870 if (dsp_state.fraction + sample_count * resample_rate >
871 static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) {
871 return; 872 return;
872 } 873 }
873 874
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
index 54556e0f9..caefc09f4 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -34,7 +34,7 @@ std::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigne
34 CoprocReg CRm, unsigned opc2) { 34 CoprocReg CRm, unsigned opc2) {
35 LOG_CRITICAL(Core_ARM, "CP15: cdp{} p15, {}, {}, {}, {}, {}", two ? "2" : "", opc1, CRd, CRn, 35 LOG_CRITICAL(Core_ARM, "CP15: cdp{} p15, {}, {}, {}, {}, {}", two ? "2" : "", opc1, CRd, CRn,
36 CRm, opc2); 36 CRm, opc2);
37 return {}; 37 return std::nullopt;
38} 38}
39 39
40CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, 40CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
@@ -115,7 +115,7 @@ std::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_trans
115 LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "", 115 LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "",
116 long_transfer ? "l" : "", CRd); 116 long_transfer ? "l" : "", CRd);
117 } 117 }
118 return {}; 118 return std::nullopt;
119} 119}
120 120
121std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, 121std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
@@ -127,7 +127,7 @@ std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_tran
127 LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "", 127 LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "",
128 long_transfer ? "l" : "", CRd); 128 long_transfer ? "l" : "", CRd);
129 } 129 }
130 return {}; 130 return std::nullopt;
131} 131}
132 132
133} // namespace Core 133} // namespace Core
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
index 7356d252e..dc6f4af3a 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -35,8 +35,8 @@ public:
35 std::optional<u8> option) override; 35 std::optional<u8> option) override;
36 36
37 ARM_Dynarmic_32& parent; 37 ARM_Dynarmic_32& parent;
38 u32 uprw; 38 u32 uprw = 0;
39 u32 uro; 39 u32 uro = 0;
40}; 40};
41 41
42} // namespace Core 42} // namespace Core
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 44aaba242..81e8cc338 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -178,7 +178,7 @@ struct System::Impl {
178 arp_manager.ResetAll(); 178 arp_manager.ResetAll();
179 179
180 telemetry_session = std::make_unique<Core::TelemetrySession>(); 180 telemetry_session = std::make_unique<Core::TelemetrySession>();
181 service_manager = std::make_shared<Service::SM::ServiceManager>(); 181 service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
182 182
183 Service::Init(service_manager, system); 183 Service::Init(service_manager, system);
184 GDBStub::DeferStart(); 184 GDBStub::DeferStart();
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 426fb6bb5..76af47ff9 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -323,7 +323,7 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
323 subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); 323 subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
324 subsection_buckets.back().entries.push_back({size, {0}, 0}); 324 subsection_buckets.back().entries.push_back({size, {0}, 0});
325 325
326 std::optional<Core::Crypto::Key128> key = {}; 326 std::optional<Core::Crypto::Key128> key;
327 if (encrypted) { 327 if (encrypted) {
328 if (has_rights_id) { 328 if (has_rights_id) {
329 status = Loader::ResultStatus::Success; 329 status = Loader::ResultStatus::Success;
@@ -442,18 +442,18 @@ std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
442 memcpy(rights_id.data(), header.rights_id.data(), 16); 442 memcpy(rights_id.data(), header.rights_id.data(), 16);
443 if (rights_id == u128{}) { 443 if (rights_id == u128{}) {
444 status = Loader::ResultStatus::ErrorInvalidRightsID; 444 status = Loader::ResultStatus::ErrorInvalidRightsID;
445 return {}; 445 return std::nullopt;
446 } 446 }
447 447
448 auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); 448 auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
449 if (titlekey == Core::Crypto::Key128{}) { 449 if (titlekey == Core::Crypto::Key128{}) {
450 status = Loader::ResultStatus::ErrorMissingTitlekey; 450 status = Loader::ResultStatus::ErrorMissingTitlekey;
451 return {}; 451 return std::nullopt;
452 } 452 }
453 453
454 if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { 454 if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
455 status = Loader::ResultStatus::ErrorMissingTitlekek; 455 status = Loader::ResultStatus::ErrorMissingTitlekek;
456 return {}; 456 return std::nullopt;
457 } 457 }
458 458
459 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( 459 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -477,7 +477,7 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
477 case NCASectionCryptoType::BKTR: 477 case NCASectionCryptoType::BKTR:
478 LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); 478 LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
479 { 479 {
480 std::optional<Core::Crypto::Key128> key = {}; 480 std::optional<Core::Crypto::Key128> key;
481 if (has_rights_id) { 481 if (has_rights_id) {
482 status = Loader::ResultStatus::Success; 482 status = Loader::ResultStatus::Success;
483 key = GetTitlekey(); 483 key = GetTitlekey();
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 9ab86e35b..403c4219a 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -83,7 +83,7 @@ enum class Language : u8 {
83 Italian = 7, 83 Italian = 7,
84 Dutch = 8, 84 Dutch = 8,
85 CanadianFrench = 9, 85 CanadianFrench = 9,
86 Portugese = 10, 86 Portuguese = 10,
87 Russian = 11, 87 Russian = 11,
88 Korean = 12, 88 Korean = 12,
89 Taiwanese = 13, 89 Taiwanese = 13,
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index a08a70efd..dd779310f 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -245,9 +245,11 @@ void IPSwitchCompiler::Parse() {
245 245
246 // Read rest of patch 246 // Read rest of patch
247 while (true) { 247 while (true) {
248 if (i + 1 >= lines.size()) 248 if (i + 1 >= lines.size()) {
249 break; 249 break;
250 const auto patch_line = lines[++i]; 250 }
251
252 const auto& patch_line = lines[++i];
251 253
252 // Start of new patch 254 // Start of new patch
253 if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) { 255 if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index b9ce93b7c..aab957bf2 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -267,9 +267,9 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
267 } 267 }
268 268
269 const CNMT cnmt(inner_file); 269 const CNMT cnmt(inner_file);
270 auto& ncas_title = ncas[cnmt.GetTitleID()];
271 270
272 ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca; 271 ncas[cnmt.GetTitleID()][{cnmt.GetType(), ContentRecordType::Meta}] = nca;
272
273 for (const auto& rec : cnmt.GetContentRecords()) { 273 for (const auto& rec : cnmt.GetContentRecords()) {
274 const auto id_string = Common::HexToString(rec.nca_id, false); 274 const auto id_string = Common::HexToString(rec.nca_id, false);
275 auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); 275 auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
@@ -287,12 +287,12 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
287 287
288 auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); 288 auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0);
289 if (next_nca->GetType() == NCAContentType::Program) { 289 if (next_nca->GetType() == NCAContentType::Program) {
290 program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); 290 program_status[next_nca->GetTitleId()] = next_nca->GetStatus();
291 } 291 }
292 if (next_nca->GetStatus() == Loader::ResultStatus::Success || 292 if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
293 (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && 293 (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
294 (cnmt.GetTitleID() & 0x800) != 0)) { 294 (next_nca->GetTitleId() & 0x800) != 0)) {
295 ncas_title[{cnmt.GetType(), rec.type}] = std::move(next_nca); 295 ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
296 } 296 }
297 } 297 }
298 298
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index a4c3f67c4..b2f026b6d 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -169,11 +169,12 @@ VfsDirectory::~VfsDirectory() = default;
169 169
170std::optional<u8> VfsFile::ReadByte(std::size_t offset) const { 170std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
171 u8 out{}; 171 u8 out{};
172 std::size_t size = Read(&out, 1, offset); 172 const std::size_t size = Read(&out, sizeof(u8), offset);
173 if (size == 1) 173 if (size == 1) {
174 return out; 174 return out;
175 }
175 176
176 return {}; 177 return std::nullopt;
177} 178}
178 179
179std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const { 180std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
index c96f88488..7714d3de5 100644
--- a/src/core/file_sys/vfs_offset.cpp
+++ b/src/core/file_sys/vfs_offset.cpp
@@ -58,10 +58,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t
58} 58}
59 59
60std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const { 60std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
61 if (r_offset < size) 61 if (r_offset >= size) {
62 return file->ReadByte(offset + r_offset); 62 return std::nullopt;
63 }
63 64
64 return {}; 65 return file->ReadByte(offset + r_offset);
65} 66}
66 67
67std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const { 68std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
index 9f5a90b1b..8b27c30fa 100644
--- a/src/core/file_sys/vfs_static.h
+++ b/src/core/file_sys/vfs_static.h
@@ -54,9 +54,11 @@ public:
54 } 54 }
55 55
56 std::optional<u8> ReadByte(std::size_t offset) const override { 56 std::optional<u8> ReadByte(std::size_t offset) const override {
57 if (offset < size) 57 if (offset >= size) {
58 return value; 58 return std::nullopt;
59 return {}; 59 }
60
61 return value;
60 } 62 }
61 63
62 std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override { 64 std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 2b098b7c6..9da0d2829 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -119,11 +119,11 @@ using ButtonDevice = InputDevice<bool>;
119using AnalogDevice = InputDevice<std::tuple<float, float>>; 119using AnalogDevice = InputDevice<std::tuple<float, float>>;
120 120
121/** 121/**
122 * A motion device is an input device that returns a tuple of accelerometer state vector and 122 * A motion status is an object that returns a tuple of accelerometer state vector,
123 * gyroscope state vector. 123 * gyroscope state vector, rotation state vector and orientation state matrix.
124 * 124 *
125 * For both vectors: 125 * For both vectors:
126 * x+ is the same direction as LEFT on D-pad. 126 * x+ is the same direction as RIGHT on D-pad.
127 * y+ is normal to the touch screen, pointing outward. 127 * y+ is normal to the touch screen, pointing outward.
128 * z+ is the same direction as UP on D-pad. 128 * z+ is the same direction as UP on D-pad.
129 * 129 *
@@ -133,8 +133,22 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>;
133 * For gyroscope state vector: 133 * For gyroscope state vector:
134 * Orientation is determined by right-hand rule. 134 * Orientation is determined by right-hand rule.
135 * Units: deg/sec 135 * Units: deg/sec
136 *
137 * For rotation state vector
138 * Units: rotations
139 *
140 * For orientation state matrix
141 * x vector
142 * y vector
143 * z vector
144 */
145using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
146 std::array<Common::Vec3f, 3>>;
147
148/**
149 * A motion device is an input device that returns a motion status object
136 */ 150 */
137using MotionDevice = InputDevice<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>; 151using MotionDevice = InputDevice<MotionStatus>;
138 152
139/** 153/**
140 * A touch device is an input device that returns a tuple of two floats and a bool. The floats are 154 * A touch device is an input device that returns a tuple of two floats and a bool. The floats are
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 7d92b25a3..d7a81f64a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1192,7 +1192,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1192 {120, nullptr, "ExecuteProgram"}, 1192 {120, nullptr, "ExecuteProgram"},
1193 {121, nullptr, "ClearUserChannel"}, 1193 {121, nullptr, "ClearUserChannel"},
1194 {122, nullptr, "UnpopToUserChannel"}, 1194 {122, nullptr, "UnpopToUserChannel"},
1195 {123, nullptr, "GetPreviousProgramIndex"}, 1195 {123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"},
1196 {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"}, 1196 {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
1197 {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, 1197 {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"},
1198 {140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"}, 1198 {140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"},
@@ -1554,6 +1554,14 @@ void IApplicationFunctions::QueryApplicationPlayStatisticsByUid(Kernel::HLEReque
1554 rb.Push<u32>(0); 1554 rb.Push<u32>(0);
1555} 1555}
1556 1556
1557void IApplicationFunctions::GetPreviousProgramIndex(Kernel::HLERequestContext& ctx) {
1558 LOG_WARNING(Service_AM, "(STUBBED) called");
1559
1560 IPC::ResponseBuilder rb{ctx, 3};
1561 rb.Push(RESULT_SUCCESS);
1562 rb.Push<s32>(previous_program_index);
1563}
1564
1557void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) { 1565void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) {
1558 LOG_WARNING(Service_AM, "(STUBBED) called"); 1566 LOG_WARNING(Service_AM, "(STUBBED) called");
1559 1567
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 6e69796ec..bcc06affe 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -288,11 +288,13 @@ private:
288 void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx); 288 void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx);
289 void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx); 289 void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx);
290 void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx); 290 void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx);
291 void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx);
291 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); 292 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
292 void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx); 293 void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx);
293 294
294 bool launch_popped_application_specific = false; 295 bool launch_popped_application_specific = false;
295 bool launch_popped_account_preselect = false; 296 bool launch_popped_account_preselect = false;
297 s32 previous_program_index{-1};
296 Kernel::EventPair gpu_error_detected_event; 298 Kernel::EventPair gpu_error_detected_event;
297 Kernel::EventPair friend_invitation_storage_channel_event; 299 Kernel::EventPair friend_invitation_storage_channel_event;
298 Core::System& system; 300 Core::System& system;
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 7818c098f..620386cd1 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -250,6 +250,9 @@ void Controller_NPad::OnLoadInputDevices() {
250 std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, 250 std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
251 players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, 251 players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
252 sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>); 252 sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>);
253 std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
254 players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END,
255 motions[i].begin(), Input::CreateDevice<Input::MotionDevice>);
253 } 256 }
254} 257}
255 258
@@ -266,6 +269,7 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
266 auto& rstick_entry = npad_pad_states[controller_idx].r_stick; 269 auto& rstick_entry = npad_pad_states[controller_idx].r_stick;
267 const auto& button_state = buttons[controller_idx]; 270 const auto& button_state = buttons[controller_idx];
268 const auto& analog_state = sticks[controller_idx]; 271 const auto& analog_state = sticks[controller_idx];
272 const auto& motion_state = motions[controller_idx];
269 const auto [stick_l_x_f, stick_l_y_f] = 273 const auto [stick_l_x_f, stick_l_y_f] =
270 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); 274 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
271 const auto [stick_r_x_f, stick_r_y_f] = 275 const auto [stick_r_x_f, stick_r_y_f] =
@@ -360,6 +364,45 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
360 continue; 364 continue;
361 } 365 }
362 const u32 npad_index = static_cast<u32>(i); 366 const u32 npad_index = static_cast<u32>(i);
367
368 const std::array<SixAxisGeneric*, 6> controller_sixaxes{
369 &npad.sixaxis_full, &npad.sixaxis_handheld, &npad.sixaxis_dual_left,
370 &npad.sixaxis_dual_right, &npad.sixaxis_left, &npad.sixaxis_right,
371 };
372
373 for (auto* sixaxis_sensor : controller_sixaxes) {
374 sixaxis_sensor->common.entry_count = 16;
375 sixaxis_sensor->common.total_entry_count = 17;
376
377 const auto& last_entry =
378 sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index];
379
380 sixaxis_sensor->common.timestamp = core_timing.GetCPUTicks();
381 sixaxis_sensor->common.last_entry_index =
382 (sixaxis_sensor->common.last_entry_index + 1) % 17;
383
384 auto& cur_entry = sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index];
385
386 cur_entry.timestamp = last_entry.timestamp + 1;
387 cur_entry.timestamp2 = cur_entry.timestamp;
388 }
389
390 // Try to read sixaxis sensor states
391 std::array<MotionDevice, 2> motion_devices;
392
393 if (sixaxis_sensors_enabled && Settings::values.motion_enabled) {
394 sixaxis_at_rest = true;
395 for (std::size_t e = 0; e < motion_devices.size(); ++e) {
396 const auto& device = motions[i][e];
397 if (device) {
398 std::tie(motion_devices[e].accel, motion_devices[e].gyro,
399 motion_devices[e].rotation, motion_devices[e].orientation) =
400 device->GetStatus();
401 sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f;
402 }
403 }
404 }
405
363 RequestPadStateUpdate(npad_index); 406 RequestPadStateUpdate(npad_index);
364 auto& pad_state = npad_pad_states[npad_index]; 407 auto& pad_state = npad_pad_states[npad_index];
365 408
@@ -377,6 +420,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
377 420
378 libnx_entry.connection_status.raw = 0; 421 libnx_entry.connection_status.raw = 0;
379 libnx_entry.connection_status.IsConnected.Assign(1); 422 libnx_entry.connection_status.IsConnected.Assign(1);
423 auto& full_sixaxis_entry =
424 npad.sixaxis_full.sixaxis[npad.sixaxis_full.common.last_entry_index];
425 auto& handheld_sixaxis_entry =
426 npad.sixaxis_handheld.sixaxis[npad.sixaxis_handheld.common.last_entry_index];
427 auto& dual_left_sixaxis_entry =
428 npad.sixaxis_dual_left.sixaxis[npad.sixaxis_dual_left.common.last_entry_index];
429 auto& dual_right_sixaxis_entry =
430 npad.sixaxis_dual_right.sixaxis[npad.sixaxis_dual_right.common.last_entry_index];
431 auto& left_sixaxis_entry =
432 npad.sixaxis_left.sixaxis[npad.sixaxis_left.common.last_entry_index];
433 auto& right_sixaxis_entry =
434 npad.sixaxis_right.sixaxis[npad.sixaxis_right.common.last_entry_index];
380 435
381 switch (controller_type) { 436 switch (controller_type) {
382 case NPadControllerType::None: 437 case NPadControllerType::None:
@@ -391,6 +446,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
391 main_controller.pad.r_stick = pad_state.r_stick; 446 main_controller.pad.r_stick = pad_state.r_stick;
392 447
393 libnx_entry.connection_status.IsWired.Assign(1); 448 libnx_entry.connection_status.IsWired.Assign(1);
449
450 if (sixaxis_sensors_enabled && motions[i][0]) {
451 full_sixaxis_entry.accel = motion_devices[0].accel;
452 full_sixaxis_entry.gyro = motion_devices[0].gyro;
453 full_sixaxis_entry.rotation = motion_devices[0].rotation;
454 full_sixaxis_entry.orientation = motion_devices[0].orientation;
455 }
394 break; 456 break;
395 case NPadControllerType::Handheld: 457 case NPadControllerType::Handheld:
396 handheld_entry.connection_status.raw = 0; 458 handheld_entry.connection_status.raw = 0;
@@ -409,6 +471,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
409 libnx_entry.connection_status.IsRightJoyConnected.Assign(1); 471 libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
410 libnx_entry.connection_status.IsLeftJoyWired.Assign(1); 472 libnx_entry.connection_status.IsLeftJoyWired.Assign(1);
411 libnx_entry.connection_status.IsRightJoyWired.Assign(1); 473 libnx_entry.connection_status.IsRightJoyWired.Assign(1);
474
475 if (sixaxis_sensors_enabled && motions[i][0]) {
476 handheld_sixaxis_entry.accel = motion_devices[0].accel;
477 handheld_sixaxis_entry.gyro = motion_devices[0].gyro;
478 handheld_sixaxis_entry.rotation = motion_devices[0].rotation;
479 handheld_sixaxis_entry.orientation = motion_devices[0].orientation;
480 }
412 break; 481 break;
413 case NPadControllerType::JoyDual: 482 case NPadControllerType::JoyDual:
414 dual_entry.connection_status.raw = 0; 483 dual_entry.connection_status.raw = 0;
@@ -421,6 +490,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
421 490
422 libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); 491 libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
423 libnx_entry.connection_status.IsRightJoyConnected.Assign(1); 492 libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
493
494 if (sixaxis_sensors_enabled && motions[i][0]) {
495 // Set motion for the left joycon
496 dual_left_sixaxis_entry.accel = motion_devices[0].accel;
497 dual_left_sixaxis_entry.gyro = motion_devices[0].gyro;
498 dual_left_sixaxis_entry.rotation = motion_devices[0].rotation;
499 dual_left_sixaxis_entry.orientation = motion_devices[0].orientation;
500 }
501 if (sixaxis_sensors_enabled && motions[i][1]) {
502 // Set motion for the right joycon
503 dual_right_sixaxis_entry.accel = motion_devices[1].accel;
504 dual_right_sixaxis_entry.gyro = motion_devices[1].gyro;
505 dual_right_sixaxis_entry.rotation = motion_devices[1].rotation;
506 dual_right_sixaxis_entry.orientation = motion_devices[1].orientation;
507 }
424 break; 508 break;
425 case NPadControllerType::JoyLeft: 509 case NPadControllerType::JoyLeft:
426 left_entry.connection_status.raw = 0; 510 left_entry.connection_status.raw = 0;
@@ -431,6 +515,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
431 left_entry.pad.r_stick = pad_state.r_stick; 515 left_entry.pad.r_stick = pad_state.r_stick;
432 516
433 libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); 517 libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
518
519 if (sixaxis_sensors_enabled && motions[i][0]) {
520 left_sixaxis_entry.accel = motion_devices[0].accel;
521 left_sixaxis_entry.gyro = motion_devices[0].gyro;
522 left_sixaxis_entry.rotation = motion_devices[0].rotation;
523 left_sixaxis_entry.orientation = motion_devices[0].orientation;
524 }
434 break; 525 break;
435 case NPadControllerType::JoyRight: 526 case NPadControllerType::JoyRight:
436 right_entry.connection_status.raw = 0; 527 right_entry.connection_status.raw = 0;
@@ -441,6 +532,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
441 right_entry.pad.r_stick = pad_state.r_stick; 532 right_entry.pad.r_stick = pad_state.r_stick;
442 533
443 libnx_entry.connection_status.IsRightJoyConnected.Assign(1); 534 libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
535
536 if (sixaxis_sensors_enabled && motions[i][1]) {
537 right_sixaxis_entry.accel = motion_devices[1].accel;
538 right_sixaxis_entry.gyro = motion_devices[1].gyro;
539 right_sixaxis_entry.rotation = motion_devices[1].rotation;
540 right_sixaxis_entry.orientation = motion_devices[1].orientation;
541 }
444 break; 542 break;
445 case NPadControllerType::Pokeball: 543 case NPadControllerType::Pokeball:
446 pokeball_entry.connection_status.raw = 0; 544 pokeball_entry.connection_status.raw = 0;
@@ -495,6 +593,14 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
495 return hold_type; 593 return hold_type;
496} 594}
497 595
596void Controller_NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode) {
597 handheld_activation_mode = activation_mode;
598}
599
600Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActivationMode() const {
601 return handheld_activation_mode;
602}
603
498void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) { 604void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
499 const std::size_t npad_index = NPadIdToIndex(npad_id); 605 const std::size_t npad_index = NPadIdToIndex(npad_id);
500 ASSERT(npad_index < shared_memory_entries.size()); 606 ASSERT(npad_index < shared_memory_entries.size());
@@ -582,6 +688,14 @@ Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMo
582 return gyroscope_zero_drift_mode; 688 return gyroscope_zero_drift_mode;
583} 689}
584 690
691bool Controller_NPad::IsSixAxisSensorAtRest() const {
692 return sixaxis_at_rest;
693}
694
695void Controller_NPad::SetSixAxisEnabled(bool six_axis_status) {
696 sixaxis_sensors_enabled = six_axis_status;
697}
698
585void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) { 699void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) {
586 const auto npad_index_1 = NPadIdToIndex(npad_id_1); 700 const auto npad_index_1 = NPadIdToIndex(npad_id_1);
587 const auto npad_index_2 = NPadIdToIndex(npad_id_2); 701 const auto npad_index_2 = NPadIdToIndex(npad_id_2);
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index e9788da8d..654d97c3f 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -74,6 +74,12 @@ public:
74 Single = 1, 74 Single = 1,
75 }; 75 };
76 76
77 enum class NpadHandheldActivationMode : u64 {
78 Dual = 0,
79 Single = 1,
80 None = 2,
81 };
82
77 enum class NPadControllerType { 83 enum class NPadControllerType {
78 None, 84 None,
79 ProController, 85 ProController,
@@ -110,6 +116,9 @@ public:
110 void SetHoldType(NpadHoldType joy_hold_type); 116 void SetHoldType(NpadHoldType joy_hold_type);
111 NpadHoldType GetHoldType() const; 117 NpadHoldType GetHoldType() const;
112 118
119 void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode);
120 NpadHandheldActivationMode GetNpadHandheldActivationMode() const;
121
113 void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode); 122 void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
114 123
115 void VibrateController(const std::vector<u32>& controller_ids, 124 void VibrateController(const std::vector<u32>& controller_ids,
@@ -130,6 +139,8 @@ public:
130 139
131 void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); 140 void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode);
132 GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; 141 GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const;
142 bool IsSixAxisSensorAtRest() const;
143 void SetSixAxisEnabled(bool six_axis_status);
133 LedPattern GetLedPattern(u32 npad_id); 144 LedPattern GetLedPattern(u32 npad_id);
134 void SetVibrationEnabled(bool can_vibrate); 145 void SetVibrationEnabled(bool can_vibrate);
135 bool IsVibrationEnabled() const; 146 bool IsVibrationEnabled() const;
@@ -252,6 +263,24 @@ private:
252 }; 263 };
253 static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size"); 264 static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
254 265
266 struct SixAxisStates {
267 s64_le timestamp{};
268 INSERT_PADDING_WORDS(2);
269 s64_le timestamp2{};
270 Common::Vec3f accel{};
271 Common::Vec3f gyro{};
272 Common::Vec3f rotation{};
273 std::array<Common::Vec3f, 3> orientation{};
274 s64_le always_one{1};
275 };
276 static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size");
277
278 struct SixAxisGeneric {
279 CommonHeader common{};
280 std::array<SixAxisStates, 17> sixaxis{};
281 };
282 static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
283
255 enum class ColorReadError : u32_le { 284 enum class ColorReadError : u32_le {
256 ReadOk = 0, 285 ReadOk = 0,
257 ColorDoesntExist = 1, 286 ColorDoesntExist = 1,
@@ -281,6 +310,13 @@ private:
281 }; 310 };
282 }; 311 };
283 312
313 struct MotionDevice {
314 Common::Vec3f accel;
315 Common::Vec3f gyro;
316 Common::Vec3f rotation;
317 std::array<Common::Vec3f, 3> orientation;
318 };
319
284 struct NPadEntry { 320 struct NPadEntry {
285 NPadType joy_styles; 321 NPadType joy_styles;
286 NPadAssignments pad_assignment; 322 NPadAssignments pad_assignment;
@@ -300,9 +336,12 @@ private:
300 NPadGeneric pokeball_states; 336 NPadGeneric pokeball_states;
301 NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be 337 NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
302 // relying on this for the time being 338 // relying on this for the time being
303 INSERT_PADDING_BYTES( 339 SixAxisGeneric sixaxis_full;
304 0x708 * 340 SixAxisGeneric sixaxis_handheld;
305 6); // TODO(ogniK): SixAxis states, require more information before implementation 341 SixAxisGeneric sixaxis_dual_left;
342 SixAxisGeneric sixaxis_dual_right;
343 SixAxisGeneric sixaxis_left;
344 SixAxisGeneric sixaxis_right;
306 NPadDevice device_type; 345 NPadDevice device_type;
307 NPadProperties properties; 346 NPadProperties properties;
308 INSERT_PADDING_WORDS(1); 347 INSERT_PADDING_WORDS(1);
@@ -325,22 +364,29 @@ private:
325 364
326 NPadType style{}; 365 NPadType style{};
327 std::array<NPadEntry, 10> shared_memory_entries{}; 366 std::array<NPadEntry, 10> shared_memory_entries{};
328 std::array< 367 using ButtonArray = std::array<
329 std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>, 368 std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
330 10> 369 10>;
331 buttons; 370 using StickArray = std::array<
332 std::array<
333 std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>, 371 std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
334 10> 372 10>;
335 sticks; 373 using MotionArray = std::array<
374 std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTION_HID>,
375 10>;
376 ButtonArray buttons;
377 StickArray sticks;
378 MotionArray motions;
336 std::vector<u32> supported_npad_id_types{}; 379 std::vector<u32> supported_npad_id_types{};
337 NpadHoldType hold_type{NpadHoldType::Vertical}; 380 NpadHoldType hold_type{NpadHoldType::Vertical};
381 NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
338 // Each controller should have their own styleset changed event 382 // Each controller should have their own styleset changed event
339 std::array<Kernel::EventPair, 10> styleset_changed_events; 383 std::array<Kernel::EventPair, 10> styleset_changed_events;
340 Vibration last_processed_vibration{}; 384 Vibration last_processed_vibration{};
341 std::array<ControllerHolder, 10> connected_controllers{}; 385 std::array<ControllerHolder, 10> connected_controllers{};
342 GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; 386 GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
343 bool can_controllers_vibrate{true}; 387 bool can_controllers_vibrate{true};
388 bool sixaxis_sensors_enabled{true};
389 bool sixaxis_at_rest{true};
344 std::array<ControllerPad, 10> npad_pad_states{}; 390 std::array<ControllerPad, 10> npad_pad_states{};
345 bool is_in_lr_assignment_mode{false}; 391 bool is_in_lr_assignment_mode{false};
346 Core::System& system; 392 Core::System& system;
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index bd3c2f26b..395e83b3f 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -40,7 +40,7 @@ namespace Service::HID {
40// Updating period for each HID device. 40// Updating period for each HID device.
41// HID is polled every 15ms, this value was derived from 41// HID is polled every 15ms, this value was derived from
42// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet 42// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet
43constexpr auto pad_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.6Hz) 43constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz)
44constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; 44constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
45 45
46IAppletResource::IAppletResource(Core::System& system) 46IAppletResource::IAppletResource(Core::System& system)
@@ -164,8 +164,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
164 {56, nullptr, "ActivateJoyXpad"}, 164 {56, nullptr, "ActivateJoyXpad"},
165 {58, nullptr, "GetJoyXpadLifoHandle"}, 165 {58, nullptr, "GetJoyXpadLifoHandle"},
166 {59, nullptr, "GetJoyXpadIds"}, 166 {59, nullptr, "GetJoyXpadIds"},
167 {60, nullptr, "ActivateSixAxisSensor"}, 167 {60, &Hid::ActivateSixAxisSensor, "ActivateSixAxisSensor"},
168 {61, nullptr, "DeactivateSixAxisSensor"}, 168 {61, &Hid::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"},
169 {62, nullptr, "GetSixAxisSensorLifoHandle"}, 169 {62, nullptr, "GetSixAxisSensorLifoHandle"},
170 {63, nullptr, "ActivateJoySixAxisSensor"}, 170 {63, nullptr, "ActivateJoySixAxisSensor"},
171 {64, nullptr, "DeactivateJoySixAxisSensor"}, 171 {64, nullptr, "DeactivateJoySixAxisSensor"},
@@ -329,6 +329,31 @@ void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) {
329 rb.Push(0); 329 rb.Push(0);
330} 330}
331 331
332void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
333 IPC::RequestParser rp{ctx};
334 const auto handle{rp.Pop<u32>()};
335 const auto applet_resource_user_id{rp.Pop<u64>()};
336 applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true);
337 LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle,
338 applet_resource_user_id);
339
340 IPC::ResponseBuilder rb{ctx, 2};
341 rb.Push(RESULT_SUCCESS);
342}
343
344void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
345 IPC::RequestParser rp{ctx};
346 const auto handle{rp.Pop<u32>()};
347 const auto applet_resource_user_id{rp.Pop<u64>()};
348 applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false);
349
350 LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle,
351 applet_resource_user_id);
352
353 IPC::ResponseBuilder rb{ctx, 2};
354 rb.Push(RESULT_SUCCESS);
355}
356
332void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { 357void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) {
333 IPC::RequestParser rp{ctx}; 358 IPC::RequestParser rp{ctx};
334 const auto applet_resource_user_id{rp.Pop<u64>()}; 359 const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -484,13 +509,13 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
484 const auto handle{rp.Pop<u32>()}; 509 const auto handle{rp.Pop<u32>()};
485 const auto applet_resource_user_id{rp.Pop<u64>()}; 510 const auto applet_resource_user_id{rp.Pop<u64>()};
486 511
487 LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, 512 LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle,
488 applet_resource_user_id); 513 applet_resource_user_id);
489 514
490 IPC::ResponseBuilder rb{ctx, 3}; 515 IPC::ResponseBuilder rb{ctx, 3};
491 rb.Push(RESULT_SUCCESS); 516 rb.Push(RESULT_SUCCESS);
492 // TODO (Hexagon12): Properly implement reading gyroscope values from controllers. 517 rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
493 rb.Push(true); 518 .IsSixAxisSensorAtRest());
494} 519}
495 520
496void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { 521void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
@@ -714,8 +739,11 @@ void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
714 const auto applet_resource_user_id{rp.Pop<u64>()}; 739 const auto applet_resource_user_id{rp.Pop<u64>()};
715 const auto mode{rp.Pop<u64>()}; 740 const auto mode{rp.Pop<u64>()};
716 741
717 LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}", 742 LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, mode={}", applet_resource_user_id,
718 applet_resource_user_id, mode); 743 mode);
744
745 applet_resource->GetController<Controller_NPad>(HidController::NPad)
746 .SetNpadHandheldActivationMode(Controller_NPad::NpadHandheldActivationMode{mode});
719 747
720 IPC::ResponseBuilder rb{ctx, 2}; 748 IPC::ResponseBuilder rb{ctx, 2};
721 rb.Push(RESULT_SUCCESS); 749 rb.Push(RESULT_SUCCESS);
@@ -725,11 +753,13 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
725 IPC::RequestParser rp{ctx}; 753 IPC::RequestParser rp{ctx};
726 const auto applet_resource_user_id{rp.Pop<u64>()}; 754 const auto applet_resource_user_id{rp.Pop<u64>()};
727 755
728 LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", 756 LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
729 applet_resource_user_id);
730 757
731 IPC::ResponseBuilder rb{ctx, 2}; 758 IPC::ResponseBuilder rb{ctx, 4};
732 rb.Push(RESULT_SUCCESS); 759 rb.Push(RESULT_SUCCESS);
760 rb.Push<u64>(
761 static_cast<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
762 .GetNpadHandheldActivationMode()));
733} 763}
734 764
735void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { 765void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index efb07547f..e04aaf1e9 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -86,6 +86,8 @@ private:
86 void CreateAppletResource(Kernel::HLERequestContext& ctx); 86 void CreateAppletResource(Kernel::HLERequestContext& ctx);
87 void ActivateXpad(Kernel::HLERequestContext& ctx); 87 void ActivateXpad(Kernel::HLERequestContext& ctx);
88 void GetXpadIDs(Kernel::HLERequestContext& ctx); 88 void GetXpadIDs(Kernel::HLERequestContext& ctx);
89 void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx);
90 void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
89 void ActivateDebugPad(Kernel::HLERequestContext& ctx); 91 void ActivateDebugPad(Kernel::HLERequestContext& ctx);
90 void ActivateTouchScreen(Kernel::HLERequestContext& ctx); 92 void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
91 void ActivateMouse(Kernel::HLERequestContext& ctx); 93 void ActivateMouse(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 5e2d769a4..a0469ffbd 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -2,6 +2,7 @@
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 <array>
5#include <atomic> 6#include <atomic>
6 7
7#include "common/logging/log.h" 8#include "common/logging/log.h"
@@ -72,10 +73,10 @@ private:
72 std::array<u8, 10> uuid; 73 std::array<u8, 10> uuid;
73 u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it 74 u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it
74 // mean something else 75 // mean something else
75 INSERT_PADDING_BYTES(0x15); 76 std::array<u8, 0x15> padding_1;
76 u32_le protocol; 77 u32_le protocol;
77 u32_le tag_type; 78 u32_le tag_type;
78 INSERT_PADDING_BYTES(0x2c); 79 std::array<u8, 0x2c> padding_2;
79 }; 80 };
80 static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size"); 81 static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size");
81 82
@@ -213,13 +214,15 @@ private:
213 LOG_DEBUG(Service_NFP, "called"); 214 LOG_DEBUG(Service_NFP, "called");
214 215
215 IPC::ResponseBuilder rb{ctx, 2}; 216 IPC::ResponseBuilder rb{ctx, 2};
216 auto amiibo = nfp_interface.GetAmiiboBuffer(); 217 const auto& amiibo = nfp_interface.GetAmiiboBuffer();
217 TagInfo tag_info{}; 218 const TagInfo tag_info{
218 tag_info.uuid = amiibo.uuid; 219 .uuid = amiibo.uuid,
219 tag_info.uuid_length = static_cast<u8>(tag_info.uuid.size()); 220 .uuid_length = static_cast<u8>(tag_info.uuid.size()),
220 221 .padding_1 = {},
221 tag_info.protocol = 1; // TODO(ogniK): Figure out actual values 222 .protocol = 1, // TODO(ogniK): Figure out actual values
222 tag_info.tag_type = 2; 223 .tag_type = 2,
224 .padding_2 = {},
225 };
223 ctx.WriteBuffer(tag_info); 226 ctx.WriteBuffer(tag_info);
224 rb.Push(RESULT_SUCCESS); 227 rb.Push(RESULT_SUCCESS);
225 } 228 }
@@ -236,7 +239,7 @@ private:
236 LOG_DEBUG(Service_NFP, "called"); 239 LOG_DEBUG(Service_NFP, "called");
237 240
238 IPC::ResponseBuilder rb{ctx, 2}; 241 IPC::ResponseBuilder rb{ctx, 2};
239 auto amiibo = nfp_interface.GetAmiiboBuffer(); 242 const auto& amiibo = nfp_interface.GetAmiiboBuffer();
240 ctx.WriteBuffer(amiibo.model_info); 243 ctx.WriteBuffer(amiibo.model_info);
241 rb.Push(RESULT_SUCCESS); 244 rb.Push(RESULT_SUCCESS);
242 } 245 }
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index d4ba88147..39bd2a45b 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -265,7 +265,7 @@ std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gp
265 } 265 }
266 } 266 }
267 267
268 return {}; 268 return std::nullopt;
269} 269}
270 270
271void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, 271void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
@@ -286,7 +286,7 @@ std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
286 return size; 286 return size;
287 } 287 }
288 288
289 return {}; 289 return std::nullopt;
290} 290}
291 291
292} // namespace Service::Nvidia::Devices 292} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index d7a1bef91..7706a5590 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -54,7 +54,7 @@ struct EventInterface {
54 } 54 }
55 mask = mask >> 1; 55 mask = mask >> 1;
56 } 56 }
57 return {}; 57 return std::nullopt;
58 } 58 }
59 void SetEventStatus(const u32 event_id, EventState new_status) { 59 void SetEventStatus(const u32 event_id, EventState new_status) {
60 EventState old_status = status[event_id]; 60 EventState old_status = status[event_id];
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index f644a460d..c64673dba 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -114,7 +114,7 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
114 [&](const VI::Display& display) { return display.GetName() == name; }); 114 [&](const VI::Display& display) { return display.GetName() == name; });
115 115
116 if (itr == displays.end()) { 116 if (itr == displays.end()) {
117 return {}; 117 return std::nullopt;
118 } 118 }
119 119
120 return itr->GetID(); 120 return itr->GetID();
@@ -124,7 +124,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
124 auto* const display = FindDisplay(display_id); 124 auto* const display = FindDisplay(display_id);
125 125
126 if (display == nullptr) { 126 if (display == nullptr) {
127 return {}; 127 return std::nullopt;
128 } 128 }
129 129
130 const u64 layer_id = next_layer_id++; 130 const u64 layer_id = next_layer_id++;
@@ -144,7 +144,7 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) co
144 const auto* const layer = FindLayer(display_id, layer_id); 144 const auto* const layer = FindLayer(display_id, layer_id);
145 145
146 if (layer == nullptr) { 146 if (layer == nullptr) {
147 return {}; 147 return std::nullopt;
148 } 148 }
149 149
150 return layer->GetBufferQueue().GetId(); 150 return layer->GetBufferQueue().GetId();
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 94bc5ade7..76b3533ec 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -72,25 +72,6 @@
72 72
73namespace Service { 73namespace Service {
74 74
75/**
76 * Creates a function string for logging, complete with the name (or header code, depending
77 * on what's passed in) the port name, and all the cmd_buff arguments.
78 */
79[[maybe_unused]] static std::string MakeFunctionString(std::string_view name,
80 std::string_view port_name,
81 const u32* cmd_buff) {
82 // Number of params == bits 0-5 + bits 6-11
83 int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F);
84
85 std::string function_string = fmt::format("function '{}': port={}", name, port_name);
86 for (int i = 1; i <= num_params; ++i) {
87 function_string += fmt::format(", cmd_buff[{}]=0x{:X}", i, cmd_buff[i]);
88 }
89 return function_string;
90}
91
92////////////////////////////////////////////////////////////////////////////////////////////////////
93
94ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions, 75ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions,
95 InvokerFn* handler_invoker) 76 InvokerFn* handler_invoker)
96 : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {} 77 : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {}
@@ -189,9 +170,6 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
189 return RESULT_SUCCESS; 170 return RESULT_SUCCESS;
190} 171}
191 172
192////////////////////////////////////////////////////////////////////////////////////////////////////
193// Module interface
194
195/// Initialize ServiceManager 173/// Initialize ServiceManager
196void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { 174void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
197 // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it 175 // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 586b3d8eb..9c1da361b 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -19,7 +19,7 @@ constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::SM, 4);
19constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6); 19constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6);
20constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7); 20constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7);
21 21
22ServiceManager::ServiceManager() = default; 22ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {}
23ServiceManager::~ServiceManager() = default; 23ServiceManager::~ServiceManager() = default;
24 24
25void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { 25void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
@@ -27,11 +27,11 @@ void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
27} 27}
28 28
29static ResultCode ValidateServiceName(const std::string& name) { 29static ResultCode ValidateServiceName(const std::string& name) {
30 if (name.size() <= 0 || name.size() > 8) { 30 if (name.empty() || name.size() > 8) {
31 LOG_ERROR(Service_SM, "Invalid service name! service={}", name); 31 LOG_ERROR(Service_SM, "Invalid service name! service={}", name);
32 return ERR_INVALID_NAME; 32 return ERR_INVALID_NAME;
33 } 33 }
34 if (name.find('\0') != std::string::npos) { 34 if (name.rfind('\0') != std::string::npos) {
35 LOG_ERROR(Service_SM, "A non null terminated service was passed"); 35 LOG_ERROR(Service_SM, "A non null terminated service was passed");
36 return ERR_INVALID_NAME; 36 return ERR_INVALID_NAME;
37 } 37 }
@@ -48,8 +48,8 @@ void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self,
48 self->controller_interface = std::make_unique<Controller>(); 48 self->controller_interface = std::make_unique<Controller>();
49} 49}
50 50
51ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService( 51ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(std::string name,
52 std::string name, unsigned int max_sessions) { 52 u32 max_sessions) {
53 53
54 CASCADE_CODE(ValidateServiceName(name)); 54 CASCADE_CODE(ValidateServiceName(name));
55 55
@@ -58,7 +58,6 @@ ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(
58 return ERR_ALREADY_REGISTERED; 58 return ERR_ALREADY_REGISTERED;
59 } 59 }
60 60
61 auto& kernel = Core::System::GetInstance().Kernel();
62 auto [server_port, client_port] = 61 auto [server_port, client_port] =
63 Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name); 62 Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);
64 63
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index aabf166b7..6790c86f0 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -48,11 +48,11 @@ class ServiceManager {
48public: 48public:
49 static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel); 49 static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel);
50 50
51 ServiceManager(); 51 explicit ServiceManager(Kernel::KernelCore& kernel_);
52 ~ServiceManager(); 52 ~ServiceManager();
53 53
54 ResultVal<std::shared_ptr<Kernel::ServerPort>> RegisterService(std::string name, 54 ResultVal<std::shared_ptr<Kernel::ServerPort>> RegisterService(std::string name,
55 unsigned int max_sessions); 55 u32 max_sessions);
56 ResultCode UnregisterService(const std::string& name); 56 ResultCode UnregisterService(const std::string& name);
57 ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name); 57 ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name);
58 ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name); 58 ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name);
@@ -79,6 +79,9 @@ private:
79 79
80 /// Map of registered services, retrieved using GetServicePort or ConnectToService. 80 /// Map of registered services, retrieved using GetServicePort or ConnectToService.
81 std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services; 81 std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services;
82
83 /// Kernel context
84 Kernel::KernelCore& kernel;
82}; 85};
83 86
84} // namespace Service::SM 87} // namespace Service::SM
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 7b9dd42d8..a74be9370 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -497,7 +497,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u
497 return {0, Errno::SUCCESS}; 497 return {0, Errno::SUCCESS};
498 } 498 }
499 499
500 std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd]; 500 const std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd];
501 if (!descriptor) { 501 if (!descriptor) {
502 LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd); 502 LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd);
503 pollfd.revents = POLL_NVAL; 503 pollfd.revents = POLL_NVAL;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 60373cc5f..1e70f6e11 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -76,16 +76,16 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, Core::S
76 bool should_pass_arguments, bool load_into_process, 76 bool should_pass_arguments, bool load_into_process,
77 std::optional<FileSys::PatchManager> pm) { 77 std::optional<FileSys::PatchManager> pm) {
78 if (file.GetSize() < sizeof(NSOHeader)) { 78 if (file.GetSize() < sizeof(NSOHeader)) {
79 return {}; 79 return std::nullopt;
80 } 80 }
81 81
82 NSOHeader nso_header{}; 82 NSOHeader nso_header{};
83 if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) { 83 if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) {
84 return {}; 84 return std::nullopt;
85 } 85 }
86 86
87 if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) { 87 if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
88 return {}; 88 return std::nullopt;
89 } 89 }
90 90
91 // Build program image 91 // Build program image
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 86d17c6cb..c3f4829d7 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -567,7 +567,7 @@ struct Memory::Impl {
567 * @param page_table The page table to use to perform the mapping. 567 * @param page_table The page table to use to perform the mapping.
568 * @param base The base address to begin mapping at. 568 * @param base The base address to begin mapping at.
569 * @param size The total size of the range in bytes. 569 * @param size The total size of the range in bytes.
570 * @param memory The memory to map. 570 * @param target The target address to begin mapping from.
571 * @param type The page type to map the memory as. 571 * @param type The page type to map the memory as.
572 */ 572 */
573 void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target, 573 void MapPages(Common::PageTable& page_table, VAddr base, u64 size, PAddr target,
diff --git a/src/core/settings.h b/src/core/settings.h
index 80f0d95a7..9834f44bb 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -152,6 +152,7 @@ struct Values {
152 152
153 bool vibration_enabled; 153 bool vibration_enabled;
154 154
155 bool motion_enabled;
155 std::string motion_device; 156 std::string motion_device;
156 std::string touch_device; 157 std::string touch_device;
157 TouchscreenInput touchscreen; 158 TouchscreenInput touchscreen;
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
index c6c423c4b..89c148aba 100644
--- a/src/input_common/gcadapter/gc_adapter.cpp
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -4,9 +4,20 @@
4 4
5#include <chrono> 5#include <chrono>
6#include <thread> 6#include <thread>
7
8#ifdef _MSC_VER
9#pragma warning(push)
10#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
11#endif
7#include <libusb.h> 12#include <libusb.h>
13#ifdef _MSC_VER
14#pragma warning(pop)
15#endif
16
8#include "common/logging/log.h" 17#include "common/logging/log.h"
18#include "common/param_package.h"
9#include "input_common/gcadapter/gc_adapter.h" 19#include "input_common/gcadapter/gc_adapter.h"
20#include "input_common/settings.h"
10 21
11namespace GCAdapter { 22namespace GCAdapter {
12 23
@@ -283,6 +294,92 @@ void Adapter::Reset() {
283 } 294 }
284} 295}
285 296
297std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
298 std::vector<Common::ParamPackage> devices;
299 for (std::size_t port = 0; port < state.size(); ++port) {
300 if (!DeviceConnected(port)) {
301 continue;
302 }
303 std::string name = fmt::format("Gamecube Controller {}", port);
304 devices.emplace_back(Common::ParamPackage{
305 {"class", "gcpad"},
306 {"display", std::move(name)},
307 {"port", std::to_string(port)},
308 });
309 }
310 return devices;
311}
312
313InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
314 const Common::ParamPackage& params) const {
315 // This list is missing ZL/ZR since those are not considered buttons.
316 // We will add those afterwards
317 // This list also excludes any button that can't be really mapped
318 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
319 switch_to_gcadapter_button = {
320 std::pair{Settings::NativeButton::A, PadButton::PAD_BUTTON_A},
321 {Settings::NativeButton::B, PadButton::PAD_BUTTON_B},
322 {Settings::NativeButton::X, PadButton::PAD_BUTTON_X},
323 {Settings::NativeButton::Y, PadButton::PAD_BUTTON_Y},
324 {Settings::NativeButton::Plus, PadButton::PAD_BUTTON_START},
325 {Settings::NativeButton::DLeft, PadButton::PAD_BUTTON_LEFT},
326 {Settings::NativeButton::DUp, PadButton::PAD_BUTTON_UP},
327 {Settings::NativeButton::DRight, PadButton::PAD_BUTTON_RIGHT},
328 {Settings::NativeButton::DDown, PadButton::PAD_BUTTON_DOWN},
329 {Settings::NativeButton::SL, PadButton::PAD_TRIGGER_L},
330 {Settings::NativeButton::SR, PadButton::PAD_TRIGGER_R},
331 {Settings::NativeButton::R, PadButton::PAD_TRIGGER_Z},
332 };
333 if (!params.Has("port")) {
334 return {};
335 }
336
337 InputCommon::ButtonMapping mapping{};
338 for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
339 Common::ParamPackage button_params({{"engine", "gcpad"}});
340 button_params.Set("port", params.Get("port", 0));
341 button_params.Set("button", static_cast<int>(gcadapter_button));
342 mapping.insert_or_assign(switch_button, std::move(button_params));
343 }
344
345 // Add the missing bindings for ZL/ZR
346 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
347 switch_to_gcadapter_axis = {
348 std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
349 {Settings::NativeButton::ZR, PadAxes::TriggerRight},
350 };
351 for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
352 Common::ParamPackage button_params({{"engine", "gcpad"}});
353 button_params.Set("port", params.Get("port", 0));
354 button_params.Set("button", static_cast<int>(PadButton::PAD_STICK));
355 button_params.Set("axis", static_cast<int>(gcadapter_axis));
356 mapping.insert_or_assign(switch_button, std::move(button_params));
357 }
358 return mapping;
359}
360
361InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
362 const Common::ParamPackage& params) const {
363 if (!params.Has("port")) {
364 return {};
365 }
366
367 InputCommon::AnalogMapping mapping = {};
368 Common::ParamPackage left_analog_params;
369 left_analog_params.Set("engine", "gcpad");
370 left_analog_params.Set("port", params.Get("port", 0));
371 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
372 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
373 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
374 Common::ParamPackage right_analog_params;
375 right_analog_params.Set("engine", "gcpad");
376 right_analog_params.Set("port", params.Get("port", 0));
377 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
378 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
379 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
380 return mapping;
381}
382
286bool Adapter::DeviceConnected(std::size_t port) const { 383bool Adapter::DeviceConnected(std::size_t port) const {
287 return adapter_controllers_status[port] != ControllerTypes::None; 384 return adapter_controllers_status[port] != ControllerTypes::None;
288} 385}
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
index 20e97d283..75bf9fe74 100644
--- a/src/input_common/gcadapter/gc_adapter.h
+++ b/src/input_common/gcadapter/gc_adapter.h
@@ -10,6 +10,7 @@
10#include <unordered_map> 10#include <unordered_map>
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/threadsafe_queue.h" 12#include "common/threadsafe_queue.h"
13#include "input_common/main.h"
13 14
14struct libusb_context; 15struct libusb_context;
15struct libusb_device; 16struct libusb_device;
@@ -75,6 +76,10 @@ public:
75 void BeginConfiguration(); 76 void BeginConfiguration();
76 void EndConfiguration(); 77 void EndConfiguration();
77 78
79 std::vector<Common::ParamPackage> GetInputDevices() const;
80 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
81 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
82
78 /// Returns true if there is a device connected to port 83 /// Returns true if there is a device connected to port
79 bool DeviceConnected(std::size_t port) const; 84 bool DeviceConnected(std::size_t port) const;
80 85
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index ea1a1cee6..8da829132 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -12,6 +12,7 @@
12#include "input_common/main.h" 12#include "input_common/main.h"
13#include "input_common/motion_emu.h" 13#include "input_common/motion_emu.h"
14#include "input_common/touch_from_button.h" 14#include "input_common/touch_from_button.h"
15#include "input_common/udp/client.h"
15#include "input_common/udp/udp.h" 16#include "input_common/udp/udp.h"
16#ifdef HAVE_SDL2 17#ifdef HAVE_SDL2
17#include "input_common/sdl/sdl.h" 18#include "input_common/sdl/sdl.h"
@@ -21,7 +22,7 @@ namespace InputCommon {
21 22
22struct InputSubsystem::Impl { 23struct InputSubsystem::Impl {
23 void Initialize() { 24 void Initialize() {
24 auto gcadapter = std::make_shared<GCAdapter::Adapter>(); 25 gcadapter = std::make_shared<GCAdapter::Adapter>();
25 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); 26 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
26 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); 27 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
27 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); 28 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
@@ -40,7 +41,11 @@ struct InputSubsystem::Impl {
40 sdl = SDL::Init(); 41 sdl = SDL::Init();
41#endif 42#endif
42 43
43 udp = CemuhookUDP::Init(); 44 udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
45 udpmotion = std::make_shared<UDPMotionFactory>(udp);
46 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
47 udptouch = std::make_shared<UDPTouchFactory>(udp);
48 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
44 } 49 }
45 50
46 void Shutdown() { 51 void Shutdown() {
@@ -53,12 +58,17 @@ struct InputSubsystem::Impl {
53#ifdef HAVE_SDL2 58#ifdef HAVE_SDL2
54 sdl.reset(); 59 sdl.reset();
55#endif 60#endif
56 udp.reset();
57 Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); 61 Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
58 Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); 62 Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
59 63
60 gcbuttons.reset(); 64 gcbuttons.reset();
61 gcanalog.reset(); 65 gcanalog.reset();
66
67 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
68 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
69
70 udpmotion.reset();
71 udptouch.reset();
62 } 72 }
63 73
64 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { 74 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
@@ -72,6 +82,8 @@ struct InputSubsystem::Impl {
72#endif 82#endif
73 auto udp_devices = udp->GetInputDevices(); 83 auto udp_devices = udp->GetInputDevices();
74 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); 84 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
85 auto gcpad_devices = gcadapter->GetInputDevices();
86 devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
75 return devices; 87 return devices;
76 } 88 }
77 89
@@ -84,6 +96,9 @@ struct InputSubsystem::Impl {
84 // TODO consider returning the SDL key codes for the default keybindings 96 // TODO consider returning the SDL key codes for the default keybindings
85 return {}; 97 return {};
86 } 98 }
99 if (params.Get("class", "") == "gcpad") {
100 return gcadapter->GetAnalogMappingForDevice(params);
101 }
87#ifdef HAVE_SDL2 102#ifdef HAVE_SDL2
88 if (params.Get("class", "") == "sdl") { 103 if (params.Get("class", "") == "sdl") {
89 return sdl->GetAnalogMappingForDevice(params); 104 return sdl->GetAnalogMappingForDevice(params);
@@ -101,6 +116,9 @@ struct InputSubsystem::Impl {
101 // TODO consider returning the SDL key codes for the default keybindings 116 // TODO consider returning the SDL key codes for the default keybindings
102 return {}; 117 return {};
103 } 118 }
119 if (params.Get("class", "") == "gcpad") {
120 return gcadapter->GetButtonMappingForDevice(params);
121 }
104#ifdef HAVE_SDL2 122#ifdef HAVE_SDL2
105 if (params.Get("class", "") == "sdl") { 123 if (params.Get("class", "") == "sdl") {
106 return sdl->GetButtonMappingForDevice(params); 124 return sdl->GetButtonMappingForDevice(params);
@@ -109,14 +127,29 @@ struct InputSubsystem::Impl {
109 return {}; 127 return {};
110 } 128 }
111 129
130 [[nodiscard]] MotionMapping GetMotionMappingForDevice(
131 const Common::ParamPackage& params) const {
132 if (!params.Has("class") || params.Get("class", "") == "any") {
133 return {};
134 }
135 if (params.Get("class", "") == "cemuhookudp") {
136 // TODO return the correct motion device
137 return {};
138 }
139 return {};
140 }
141
112 std::shared_ptr<Keyboard> keyboard; 142 std::shared_ptr<Keyboard> keyboard;
113 std::shared_ptr<MotionEmu> motion_emu; 143 std::shared_ptr<MotionEmu> motion_emu;
114#ifdef HAVE_SDL2 144#ifdef HAVE_SDL2
115 std::unique_ptr<SDL::State> sdl; 145 std::unique_ptr<SDL::State> sdl;
116#endif 146#endif
117 std::unique_ptr<CemuhookUDP::State> udp;
118 std::shared_ptr<GCButtonFactory> gcbuttons; 147 std::shared_ptr<GCButtonFactory> gcbuttons;
119 std::shared_ptr<GCAnalogFactory> gcanalog; 148 std::shared_ptr<GCAnalogFactory> gcanalog;
149 std::shared_ptr<UDPMotionFactory> udpmotion;
150 std::shared_ptr<UDPTouchFactory> udptouch;
151 std::shared_ptr<CemuhookUDP::Client> udp;
152 std::shared_ptr<GCAdapter::Adapter> gcadapter;
120}; 153};
121 154
122InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} 155InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -175,6 +208,22 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
175 return impl->gcbuttons.get(); 208 return impl->gcbuttons.get();
176} 209}
177 210
211UDPMotionFactory* InputSubsystem::GetUDPMotions() {
212 return impl->udpmotion.get();
213}
214
215const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
216 return impl->udpmotion.get();
217}
218
219UDPTouchFactory* InputSubsystem::GetUDPTouch() {
220 return impl->udptouch.get();
221}
222
223const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
224 return impl->udptouch.get();
225}
226
178void InputSubsystem::ReloadInputDevices() { 227void InputSubsystem::ReloadInputDevices() {
179 if (!impl->udp) { 228 if (!impl->udp) {
180 return; 229 return;
diff --git a/src/input_common/main.h b/src/input_common/main.h
index f3fbf696e..dded3f1ef 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -21,10 +21,14 @@ namespace Settings::NativeButton {
21enum Values : int; 21enum Values : int;
22} 22}
23 23
24namespace Settings::NativeMotion {
25enum Values : int;
26}
27
24namespace InputCommon { 28namespace InputCommon {
25namespace Polling { 29namespace Polling {
26 30
27enum class DeviceType { Button, AnalogPreferred }; 31enum class DeviceType { Button, AnalogPreferred, Motion };
28 32
29/** 33/**
30 * A class that can be used to get inputs from an input device like controllers without having to 34 * A class that can be used to get inputs from an input device like controllers without having to
@@ -50,6 +54,8 @@ public:
50 54
51class GCAnalogFactory; 55class GCAnalogFactory;
52class GCButtonFactory; 56class GCButtonFactory;
57class UDPMotionFactory;
58class UDPTouchFactory;
53class Keyboard; 59class Keyboard;
54class MotionEmu; 60class MotionEmu;
55 61
@@ -59,6 +65,7 @@ class MotionEmu;
59 */ 65 */
60using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; 66using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
61using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; 67using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
68using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>;
62 69
63class InputSubsystem { 70class InputSubsystem {
64public: 71public:
@@ -103,6 +110,9 @@ public:
103 /// Retrieves the button mappings for the given device. 110 /// Retrieves the button mappings for the given device.
104 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const; 111 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
105 112
113 /// Retrieves the motion mappings for the given device.
114 [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
115
106 /// Retrieves the underlying GameCube analog handler. 116 /// Retrieves the underlying GameCube analog handler.
107 [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); 117 [[nodiscard]] GCAnalogFactory* GetGCAnalogs();
108 118
@@ -115,6 +125,18 @@ public:
115 /// Retrieves the underlying GameCube button handler. 125 /// Retrieves the underlying GameCube button handler.
116 [[nodiscard]] const GCButtonFactory* GetGCButtons() const; 126 [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
117 127
128 /// Retrieves the underlying udp motion handler.
129 [[nodiscard]] UDPMotionFactory* GetUDPMotions();
130
131 /// Retrieves the underlying udp motion handler.
132 [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
133
134 /// Retrieves the underlying udp touch handler.
135 [[nodiscard]] UDPTouchFactory* GetUDPTouch();
136
137 /// Retrieves the underlying udp touch handler.
138 [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
139
118 /// Reloads the input devices 140 /// Reloads the input devices
119 void ReloadInputDevices(); 141 void ReloadInputDevices();
120 142
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index d4cdf76a3..69fd3c1d2 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -56,7 +56,7 @@ public:
56 is_tilting = false; 56 is_tilting = false;
57 } 57 }
58 58
59 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() { 59 Input::MotionStatus GetStatus() {
60 std::lock_guard guard{status_mutex}; 60 std::lock_guard guard{status_mutex};
61 return status; 61 return status;
62 } 62 }
@@ -76,7 +76,7 @@ private:
76 76
77 Common::Event shutdown_event; 77 Common::Event shutdown_event;
78 78
79 std::tuple<Common::Vec3<float>, Common::Vec3<float>> status; 79 Input::MotionStatus status;
80 std::mutex status_mutex; 80 std::mutex status_mutex;
81 81
82 // Note: always keep the thread declaration at the end so that other objects are initialized 82 // Note: always keep the thread declaration at the end so that other objects are initialized
@@ -113,10 +113,19 @@ private:
113 gravity = QuaternionRotate(inv_q, gravity); 113 gravity = QuaternionRotate(inv_q, gravity);
114 angular_rate = QuaternionRotate(inv_q, angular_rate); 114 angular_rate = QuaternionRotate(inv_q, angular_rate);
115 115
116 // TODO: Calculate the correct rotation vector and orientation matrix
117 const auto matrix4x4 = q.ToMatrix();
118 const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f);
119 const std::array orientation{
120 Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
121 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
122 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]),
123 };
124
116 // Update the sensor state 125 // Update the sensor state
117 { 126 {
118 std::lock_guard guard{status_mutex}; 127 std::lock_guard guard{status_mutex};
119 status = std::make_tuple(gravity, angular_rate); 128 status = std::make_tuple(gravity, angular_rate, rotation, orientation);
120 } 129 }
121 } 130 }
122 } 131 }
@@ -131,7 +140,7 @@ public:
131 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); 140 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
132 } 141 }
133 142
134 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { 143 Input::MotionStatus GetStatus() const override {
135 return device->GetStatus(); 144 return device->GetStatus();
136 } 145 }
137 146
diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp
index 80c719cf4..b66c05856 100644
--- a/src/input_common/settings.cpp
+++ b/src/input_common/settings.cpp
@@ -14,6 +14,13 @@ const std::array<const char*, NumButtons> mapping = {{
14}}; 14}};
15} 15}
16 16
17namespace NativeMotion {
18const std::array<const char*, NumMotions> mapping = {{
19 "motionleft",
20 "motionright",
21}};
22}
23
17namespace NativeAnalog { 24namespace NativeAnalog {
18const std::array<const char*, NumAnalogs> mapping = {{ 25const std::array<const char*, NumAnalogs> mapping = {{
19 "lstick", 26 "lstick",
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
index 2d258960b..ab0b95cf1 100644
--- a/src/input_common/settings.h
+++ b/src/input_common/settings.h
@@ -66,6 +66,21 @@ constexpr int NUM_STICKS_HID = NumAnalogs;
66extern const std::array<const char*, NumAnalogs> mapping; 66extern const std::array<const char*, NumAnalogs> mapping;
67} // namespace NativeAnalog 67} // namespace NativeAnalog
68 68
69namespace NativeMotion {
70enum Values : int {
71 MOTIONLEFT,
72 MOTIONRIGHT,
73
74 NumMotions,
75};
76
77constexpr int MOTION_HID_BEGIN = MOTIONLEFT;
78constexpr int MOTION_HID_END = NumMotions;
79constexpr int NUM_MOTION_HID = NumMotions;
80
81extern const std::array<const char*, NumMotions> mapping;
82} // namespace NativeMotion
83
69namespace NativeMouseButton { 84namespace NativeMouseButton {
70enum Values { 85enum Values {
71 Left, 86 Left,
@@ -292,6 +307,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
292 307
293using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; 308using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
294using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; 309using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
310using MotionRaw = std::array<std::string, NativeMotion::NumMotions>;
295using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; 311using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
296using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; 312using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
297using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; 313using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
@@ -314,6 +330,7 @@ struct PlayerInput {
314 ControllerType controller_type; 330 ControllerType controller_type;
315 ButtonsRaw buttons; 331 ButtonsRaw buttons;
316 AnalogsRaw analogs; 332 AnalogsRaw analogs;
333 MotionRaw motions;
317 std::string lstick_mod; 334 std::string lstick_mod;
318 std::string rstick_mod; 335 std::string rstick_mod;
319 336
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index 3f4eaf448..2b6a68d4b 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -2,14 +2,13 @@
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 <algorithm>
6#include <array>
7#include <chrono> 5#include <chrono>
8#include <cstring> 6#include <cstring>
9#include <functional> 7#include <functional>
10#include <thread> 8#include <thread>
11#include <boost/asio.hpp> 9#include <boost/asio.hpp>
12#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/settings.h"
13#include "input_common/udp/client.h" 12#include "input_common/udp/client.h"
14#include "input_common/udp/protocol.h" 13#include "input_common/udp/protocol.h"
15 14
@@ -131,21 +130,59 @@ static void SocketLoop(Socket* socket) {
131 socket->Loop(); 130 socket->Loop();
132} 131}
133 132
134Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, 133Client::Client() {
135 u8 pad_index, u32 client_id) 134 LOG_INFO(Input, "Udp Initialization started");
136 : status(std::move(status)) { 135 for (std::size_t client = 0; client < clients.size(); client++) {
137 StartCommunication(host, port, pad_index, client_id); 136 u8 pad = client % 4;
137 StartCommunication(client, Settings::values.udp_input_address,
138 Settings::values.udp_input_port, pad, 24872);
139 // Set motion parameters
140 // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
141 // Real HW values are unknown, 0.0001 is an approximate to Standard
142 clients[client].motion.SetGyroThreshold(0.0001f);
143 }
138} 144}
139 145
140Client::~Client() { 146Client::~Client() {
141 socket->Stop(); 147 Reset();
142 thread.join(); 148}
149
150std::vector<Common::ParamPackage> Client::GetInputDevices() const {
151 std::vector<Common::ParamPackage> devices;
152 for (std::size_t client = 0; client < clients.size(); client++) {
153 if (!DeviceConnected(client)) {
154 continue;
155 }
156 std::string name = fmt::format("UDP Controller {}", client);
157 devices.emplace_back(Common::ParamPackage{
158 {"class", "cemuhookudp"},
159 {"display", std::move(name)},
160 {"port", std::to_string(client)},
161 });
162 }
163 return devices;
143} 164}
144 165
166bool Client::DeviceConnected(std::size_t pad) const {
167 // Use last timestamp to detect if the socket has stopped sending data
168 const auto now = std::chrono::system_clock::now();
169 u64 time_difference =
170 std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update)
171 .count();
172 return time_difference < 1000 && clients[pad].active == 1;
173}
174
175void Client::ReloadUDPClient() {
176 for (std::size_t client = 0; client < clients.size(); client++) {
177 ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client);
178 }
179}
145void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { 180void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
146 socket->Stop(); 181 // client number must be determined from host / port and pad index
147 thread.join(); 182 std::size_t client = pad_index;
148 StartCommunication(host, port, pad_index, client_id); 183 clients[client].socket->Stop();
184 clients[client].thread.join();
185 StartCommunication(client, host, port, pad_index, client_id);
149} 186}
150 187
151void Client::OnVersion(Response::Version data) { 188void Client::OnVersion(Response::Version data) {
@@ -157,23 +194,39 @@ void Client::OnPortInfo(Response::PortInfo data) {
157} 194}
158 195
159void Client::OnPadData(Response::PadData data) { 196void Client::OnPadData(Response::PadData data) {
197 // client number must be determined from host / port and pad index
198 std::size_t client = data.info.id;
160 LOG_TRACE(Input, "PadData packet received"); 199 LOG_TRACE(Input, "PadData packet received");
161 if (data.packet_counter <= packet_sequence) { 200 if (data.packet_counter == clients[client].packet_sequence) {
162 LOG_WARNING( 201 LOG_WARNING(
163 Input, 202 Input,
164 "PadData packet dropped because its stale info. Current count: {} Packet count: {}", 203 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
165 packet_sequence, data.packet_counter); 204 clients[client].packet_sequence, data.packet_counter);
166 return; 205 return;
167 } 206 }
168 packet_sequence = data.packet_counter; 207 clients[client].active = data.info.is_pad_active;
169 // TODO: Check how the Switch handles motions and how the CemuhookUDP motion 208 clients[client].packet_sequence = data.packet_counter;
170 // directions correspond to the ones of the Switch 209 const auto now = std::chrono::system_clock::now();
171 Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); 210 u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>(
172 Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); 211 now - clients[client].last_motion_update)
173 { 212 .count();
174 std::lock_guard guard(status->update_mutex); 213 clients[client].last_motion_update = now;
214 Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
215 clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
216 // Gyroscope values are not it the correct scale from better joy.
217 // Dividing by 312 allows us to make one full turn = 1 turn
218 // This must be a configurable valued called sensitivity
219 clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
220 clients[client].motion.UpdateRotation(time_difference);
221 clients[client].motion.UpdateOrientation(time_difference);
222 Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
223 Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
224 Common::Vec3f rotation = clients[client].motion.GetRotations();
225 std::array<Common::Vec3f, 3> orientation = clients[client].motion.GetOrientation();
175 226
176 status->motion_status = {accel, gyro}; 227 {
228 std::lock_guard guard(clients[client].status.update_mutex);
229 clients[client].status.motion_status = {accelerometer, gyroscope, rotation, orientation};
177 230
178 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates 231 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
179 // between a simple "tap" and a hard press that causes the touch screen to click. 232 // between a simple "tap" and a hard press that causes the touch screen to click.
@@ -182,11 +235,11 @@ void Client::OnPadData(Response::PadData data) {
182 float x = 0; 235 float x = 0;
183 float y = 0; 236 float y = 0;
184 237
185 if (is_active && status->touch_calibration) { 238 if (is_active && clients[client].status.touch_calibration) {
186 const u16 min_x = status->touch_calibration->min_x; 239 const u16 min_x = clients[client].status.touch_calibration->min_x;
187 const u16 max_x = status->touch_calibration->max_x; 240 const u16 max_x = clients[client].status.touch_calibration->max_x;
188 const u16 min_y = status->touch_calibration->min_y; 241 const u16 min_y = clients[client].status.touch_calibration->min_y;
189 const u16 max_y = status->touch_calibration->max_y; 242 const u16 max_y = clients[client].status.touch_calibration->max_y;
190 243
191 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / 244 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
192 static_cast<float>(max_x - min_x); 245 static_cast<float>(max_x - min_x);
@@ -194,17 +247,80 @@ void Client::OnPadData(Response::PadData data) {
194 static_cast<float>(max_y - min_y); 247 static_cast<float>(max_y - min_y);
195 } 248 }
196 249
197 status->touch_status = {x, y, is_active}; 250 clients[client].status.touch_status = {x, y, is_active};
251
252 if (configuring) {
253 UpdateYuzuSettings(client, accelerometer, gyroscope, is_active);
254 }
198 } 255 }
199} 256}
200 257
201void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { 258void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
259 u32 client_id) {
202 SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, 260 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
203 [this](Response::PortInfo info) { OnPortInfo(info); }, 261 [this](Response::PortInfo info) { OnPortInfo(info); },
204 [this](Response::PadData data) { OnPadData(data); }}; 262 [this](Response::PadData data) { OnPadData(data); }};
205 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); 263 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
206 socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); 264 clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
207 thread = std::thread{SocketLoop, this->socket.get()}; 265 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
266}
267
268void Client::Reset() {
269 for (std::size_t client = 0; client < clients.size(); client++) {
270 clients[client].socket->Stop();
271 clients[client].thread.join();
272 }
273}
274
275void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
276 const Common::Vec3<float>& gyro, bool touch) {
277 UDPPadStatus pad;
278 if (touch) {
279 pad.touch = PadTouch::Click;
280 pad_queue[client].Push(pad);
281 }
282 for (size_t i = 0; i < 3; ++i) {
283 if (gyro[i] > 6.0f || gyro[i] < -6.0f) {
284 pad.motion = static_cast<PadMotion>(i);
285 pad.motion_value = gyro[i];
286 pad_queue[client].Push(pad);
287 }
288 if (acc[i] > 2.0f || acc[i] < -2.0f) {
289 pad.motion = static_cast<PadMotion>(i + 3);
290 pad.motion_value = acc[i];
291 pad_queue[client].Push(pad);
292 }
293 }
294}
295
296void Client::BeginConfiguration() {
297 for (auto& pq : pad_queue) {
298 pq.Clear();
299 }
300 configuring = true;
301}
302
303void Client::EndConfiguration() {
304 for (auto& pq : pad_queue) {
305 pq.Clear();
306 }
307 configuring = false;
308}
309
310DeviceStatus& Client::GetPadState(std::size_t pad) {
311 return clients[pad].status;
312}
313
314const DeviceStatus& Client::GetPadState(std::size_t pad) const {
315 return clients[pad].status;
316}
317
318std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() {
319 return pad_queue;
320}
321
322const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const {
323 return pad_queue;
208} 324}
209 325
210void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, 326void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index b8c654755..523dc6a7a 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -12,8 +12,12 @@
12#include <thread> 12#include <thread>
13#include <tuple> 13#include <tuple>
14#include "common/common_types.h" 14#include "common/common_types.h"
15#include "common/param_package.h"
15#include "common/thread.h" 16#include "common/thread.h"
17#include "common/threadsafe_queue.h"
16#include "common/vector_math.h" 18#include "common/vector_math.h"
19#include "core/frontend/input.h"
20#include "input_common/motion_input.h"
17 21
18namespace InputCommon::CemuhookUDP { 22namespace InputCommon::CemuhookUDP {
19 23
@@ -28,9 +32,30 @@ struct PortInfo;
28struct Version; 32struct Version;
29} // namespace Response 33} // namespace Response
30 34
35enum class PadMotion {
36 GyroX,
37 GyroY,
38 GyroZ,
39 AccX,
40 AccY,
41 AccZ,
42 Undefined,
43};
44
45enum class PadTouch {
46 Click,
47 Undefined,
48};
49
50struct UDPPadStatus {
51 PadTouch touch{PadTouch::Undefined};
52 PadMotion motion{PadMotion::Undefined};
53 f32 motion_value{0.0f};
54};
55
31struct DeviceStatus { 56struct DeviceStatus {
32 std::mutex update_mutex; 57 std::mutex update_mutex;
33 std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status; 58 Input::MotionStatus motion_status;
34 std::tuple<float, float, bool> touch_status; 59 std::tuple<float, float, bool> touch_status;
35 60
36 // calibration data for scaling the device's touch area to 3ds 61 // calibration data for scaling the device's touch area to 3ds
@@ -45,22 +70,58 @@ struct DeviceStatus {
45 70
46class Client { 71class Client {
47public: 72public:
48 explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, 73 // Initialize the UDP client capture and read sequence
49 u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); 74 Client();
75
76 // Close and release the client
50 ~Client(); 77 ~Client();
78
79 // Used for polling
80 void BeginConfiguration();
81 void EndConfiguration();
82
83 std::vector<Common::ParamPackage> GetInputDevices() const;
84
85 bool DeviceConnected(std::size_t pad) const;
86 void ReloadUDPClient();
51 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, 87 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
52 u32 client_id = 24872); 88 u32 client_id = 24872);
53 89
90 std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue();
91 const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const;
92
93 DeviceStatus& GetPadState(std::size_t pad);
94 const DeviceStatus& GetPadState(std::size_t pad) const;
95
54private: 96private:
97 struct ClientData {
98 std::unique_ptr<Socket> socket;
99 DeviceStatus status;
100 std::thread thread;
101 u64 packet_sequence = 0;
102 u8 active;
103
104 // Realtime values
105 // motion is initalized with PID values for drift correction on joycons
106 InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
107 std::chrono::time_point<std::chrono::system_clock> last_motion_update;
108 };
109
110 // For shutting down, clear all data, join all threads, release usb
111 void Reset();
112
55 void OnVersion(Response::Version); 113 void OnVersion(Response::Version);
56 void OnPortInfo(Response::PortInfo); 114 void OnPortInfo(Response::PortInfo);
57 void OnPadData(Response::PadData); 115 void OnPadData(Response::PadData);
58 void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); 116 void StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
117 u32 client_id);
118 void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
119 const Common::Vec3<float>& gyro, bool touch);
120
121 bool configuring = false;
59 122
60 std::unique_ptr<Socket> socket; 123 std::array<ClientData, 4> clients;
61 std::shared_ptr<DeviceStatus> status; 124 std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue;
62 std::thread thread;
63 u64 packet_sequence = 0;
64}; 125};
65 126
66/// An async job allowing configuration of the touchpad calibration. 127/// An async job allowing configuration of the touchpad calibration.
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 4b347e47e..eba077a36 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -1,105 +1,144 @@
1// Copyright 2018 Citra Emulator Project 1// Copyright 2020 yuzu Emulator Project
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 <atomic>
6#include <list>
5#include <mutex> 7#include <mutex>
6#include <optional> 8#include <utility>
7#include <tuple> 9#include "common/assert.h"
8 10#include "common/threadsafe_queue.h"
9#include "common/param_package.h"
10#include "core/frontend/input.h"
11#include "core/settings.h"
12#include "input_common/udp/client.h" 11#include "input_common/udp/client.h"
13#include "input_common/udp/udp.h" 12#include "input_common/udp/udp.h"
14 13
15namespace InputCommon::CemuhookUDP { 14namespace InputCommon {
16 15
17class UDPTouchDevice final : public Input::TouchDevice { 16class UDPMotion final : public Input::MotionDevice {
18public: 17public:
19 explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 18 UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
20 std::tuple<float, float, bool> GetStatus() const override { 19 : ip(ip_), port(port_), pad(pad_), client(client_) {}
21 std::lock_guard guard(status->update_mutex); 20
22 return status->touch_status; 21 Input::MotionStatus GetStatus() const override {
22 return client->GetPadState(pad).motion_status;
23 } 23 }
24 24
25private: 25private:
26 std::shared_ptr<DeviceStatus> status; 26 const std::string ip;
27 const int port;
28 const int pad;
29 CemuhookUDP::Client* client;
30 mutable std::mutex mutex;
27}; 31};
28 32
29class UDPMotionDevice final : public Input::MotionDevice { 33/// A motion device factory that creates motion devices from JC Adapter
30public: 34UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
31 explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 35 : client(std::move(client_)) {}
32 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { 36
33 std::lock_guard guard(status->update_mutex); 37/**
34 return status->motion_status; 38 * Creates motion device
35 } 39 * @param params contains parameters for creating the device:
40 * - "port": the nth jcpad on the adapter
41 */
42std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
43 const std::string ip = params.Get("ip", "127.0.0.1");
44 const int port = params.Get("port", 26760);
45 const int pad = params.Get("pad_index", 0);
46
47 return std::make_unique<UDPMotion>(ip, port, pad, client.get());
48}
36 49
37private: 50void UDPMotionFactory::BeginConfiguration() {
38 std::shared_ptr<DeviceStatus> status; 51 polling = true;
39}; 52 client->BeginConfiguration();
53}
40 54
41class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { 55void UDPMotionFactory::EndConfiguration() {
42public: 56 polling = false;
43 explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 57 client->EndConfiguration();
44 58}
45 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { 59
46 { 60Common::ParamPackage UDPMotionFactory::GetNextInput() {
47 std::lock_guard guard(status->update_mutex); 61 Common::ParamPackage params;
48 status->touch_calibration = DeviceStatus::CalibrationData{}; 62 CemuhookUDP::UDPPadStatus pad;
49 // These default values work well for DS4 but probably not other touch inputs 63 auto& queue = client->GetPadQueue();
50 status->touch_calibration->min_x = params.Get("min_x", 100); 64 for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
51 status->touch_calibration->min_y = params.Get("min_y", 50); 65 while (queue[pad_number].Pop(pad)) {
52 status->touch_calibration->max_x = params.Get("max_x", 1800); 66 if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
53 status->touch_calibration->max_y = params.Get("max_y", 850); 67 continue;
68 }
69 params.Set("engine", "cemuhookudp");
70 params.Set("ip", "127.0.0.1");
71 params.Set("port", 26760);
72 params.Set("pad_index", static_cast<int>(pad_number));
73 params.Set("motion", static_cast<u16>(pad.motion));
74 return params;
54 } 75 }
55 return std::make_unique<UDPTouchDevice>(status);
56 } 76 }
77 return params;
78}
57 79
58private: 80class UDPTouch final : public Input::TouchDevice {
59 std::shared_ptr<DeviceStatus> status;
60};
61
62class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
63public: 81public:
64 explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 82 UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
83 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
65 84
66 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { 85 std::tuple<float, float, bool> GetStatus() const override {
67 return std::make_unique<UDPMotionDevice>(status); 86 return client->GetPadState(pad).touch_status;
68 } 87 }
69 88
70private: 89private:
71 std::shared_ptr<DeviceStatus> status; 90 const std::string ip;
91 const int port;
92 const int pad;
93 CemuhookUDP::Client* client;
94 mutable std::mutex mutex;
72}; 95};
73 96
74State::State() { 97/// A motion device factory that creates motion devices from JC Adapter
75 auto status = std::make_shared<DeviceStatus>(); 98UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
76 client = 99 : client(std::move(client_)) {}
77 std::make_unique<Client>(status, Settings::values.udp_input_address, 100
78 Settings::values.udp_input_port, Settings::values.udp_pad_index); 101/**
79 102 * Creates motion device
80 motion_factory = std::make_shared<UDPMotionFactory>(status); 103 * @param params contains parameters for creating the device:
81 touch_factory = std::make_shared<UDPTouchFactory>(status); 104 * - "port": the nth jcpad on the adapter
82 105 */
83 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory); 106std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
84 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory); 107 const std::string ip = params.Get("ip", "127.0.0.1");
108 const int port = params.Get("port", 26760);
109 const int pad = params.Get("pad_index", 0);
110
111 return std::make_unique<UDPTouch>(ip, port, pad, client.get());
85} 112}
86 113
87State::~State() { 114void UDPTouchFactory::BeginConfiguration() {
88 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); 115 polling = true;
89 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); 116 client->BeginConfiguration();
90} 117}
91 118
92std::vector<Common::ParamPackage> State::GetInputDevices() const { 119void UDPTouchFactory::EndConfiguration() {
93 // TODO support binding udp devices 120 polling = false;
94 return {}; 121 client->EndConfiguration();
95} 122}
96 123
97void State::ReloadUDPClient() { 124Common::ParamPackage UDPTouchFactory::GetNextInput() {
98 client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, 125 Common::ParamPackage params;
99 Settings::values.udp_pad_index); 126 CemuhookUDP::UDPPadStatus pad;
127 auto& queue = client->GetPadQueue();
128 for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
129 while (queue[pad_number].Pop(pad)) {
130 if (pad.touch == CemuhookUDP::PadTouch::Undefined) {
131 continue;
132 }
133 params.Set("engine", "cemuhookudp");
134 params.Set("ip", "127.0.0.1");
135 params.Set("port", 26760);
136 params.Set("pad_index", static_cast<int>(pad_number));
137 params.Set("touch", static_cast<u16>(pad.touch));
138 return params;
139 }
140 }
141 return params;
100} 142}
101 143
102std::unique_ptr<State> Init() { 144} // namespace InputCommon
103 return std::make_unique<State>();
104}
105} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
index 672a5c812..ea3fd4175 100644
--- a/src/input_common/udp/udp.h
+++ b/src/input_common/udp/udp.h
@@ -1,32 +1,57 @@
1// Copyright 2018 Citra Emulator Project 1// Copyright 2020 yuzu Emulator Project
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#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include <vector> 8#include "core/frontend/input.h"
9#include "common/param_package.h" 9#include "input_common/udp/client.h"
10 10
11namespace InputCommon::CemuhookUDP { 11namespace InputCommon {
12 12
13class Client; 13/// A motion device factory that creates motion devices from udp clients
14class UDPMotionFactory; 14class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
15class UDPTouchFactory;
16
17class State {
18public: 15public:
19 State(); 16 explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
20 ~State(); 17
21 void ReloadUDPClient(); 18 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
22 std::vector<Common::ParamPackage> GetInputDevices() const; 19
20 Common::ParamPackage GetNextInput();
21
22 /// For device input configuration/polling
23 void BeginConfiguration();
24 void EndConfiguration();
25
26 bool IsPolling() const {
27 return polling;
28 }
23 29
24private: 30private:
25 std::unique_ptr<Client> client; 31 std::shared_ptr<CemuhookUDP::Client> client;
26 std::shared_ptr<UDPMotionFactory> motion_factory; 32 bool polling = false;
27 std::shared_ptr<UDPTouchFactory> touch_factory;
28}; 33};
29 34
30std::unique_ptr<State> Init(); 35/// A touch device factory that creates touch devices from udp clients
36class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
37public:
38 explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
39
40 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
41
42 Common::ParamPackage GetNextInput();
43
44 /// For device input configuration/polling
45 void BeginConfiguration();
46 void EndConfiguration();
47
48 bool IsPolling() const {
49 return polling;
50 }
51
52private:
53 std::shared_ptr<CemuhookUDP::Client> client;
54 bool polling = false;
55};
31 56
32} // namespace InputCommon::CemuhookUDP 57} // namespace InputCommon
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 6e50661a3..9409c4075 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -87,12 +87,12 @@ void Fermi2D::HandleSurfaceCopy() {
87 const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2}; 87 const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2};
88 const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y, dst_blit_x2, 88 const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y, dst_blit_x2,
89 dst_blit_y2}; 89 dst_blit_y2};
90 Config copy_config; 90 const Config copy_config{
91 copy_config.operation = regs.operation; 91 .operation = regs.operation,
92 copy_config.filter = regs.blit_control.filter; 92 .filter = regs.blit_control.filter,
93 copy_config.src_rect = src_rect; 93 .src_rect = src_rect,
94 copy_config.dst_rect = dst_rect; 94 .dst_rect = dst_rect,
95 95 };
96 if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) { 96 if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) {
97 UNIMPLEMENTED(); 97 UNIMPLEMENTED();
98 } 98 }
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 213abfaae..0909709ec 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -145,8 +145,8 @@ public:
145 } regs{}; 145 } regs{};
146 146
147 struct Config { 147 struct Config {
148 Operation operation; 148 Operation operation{};
149 Filter filter; 149 Filter filter{};
150 Common::Rectangle<u32> src_rect; 150 Common::Rectangle<u32> src_rect;
151 Common::Rectangle<u32> dst_rect; 151 Common::Rectangle<u32> dst_rect;
152 }; 152 };
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 33854445f..57ebc785f 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -597,7 +597,7 @@ std::optional<u64> Maxwell3D::GetQueryResult() {
597 // Deferred. 597 // Deferred.
598 rasterizer->Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed, 598 rasterizer->Query(regs.query.QueryAddress(), VideoCore::QueryType::SamplesPassed,
599 system.GPU().GetTicks()); 599 system.GPU().GetTicks());
600 return {}; 600 return std::nullopt;
601 default: 601 default:
602 LOG_DEBUG(HW_GPU, "Unimplemented query select type {}", 602 LOG_DEBUG(HW_GPU, "Unimplemented query select type {}",
603 static_cast<u32>(regs.query.query_get.select.Value())); 603 static_cast<u32>(regs.query.query_get.select.Value()));
diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp
index a50e7b4e0..cd21a2112 100644
--- a/src/video_core/macro/macro.cpp
+++ b/src/video_core/macro/macro.cpp
@@ -36,7 +36,7 @@ void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method,
36 } 36 }
37 } else { 37 } else {
38 // Macro not compiled, check if it's uploaded and if so, compile it 38 // Macro not compiled, check if it's uploaded and if so, compile it
39 std::optional<u32> mid_method = std::nullopt; 39 std::optional<u32> mid_method;
40 const auto macro_code = uploaded_macro_code.find(method); 40 const auto macro_code = uploaded_macro_code.find(method);
41 if (macro_code == uploaded_macro_code.end()) { 41 if (macro_code == uploaded_macro_code.end()) {
42 for (const auto& [method_base, code] : uploaded_macro_code) { 42 for (const auto& [method_base, code] : uploaded_macro_code) {
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 16b2aaa27..02cf53d15 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -58,7 +58,7 @@ void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) {
58std::optional<GPUVAddr> MemoryManager::AllocateFixed(GPUVAddr gpu_addr, std::size_t size) { 58std::optional<GPUVAddr> MemoryManager::AllocateFixed(GPUVAddr gpu_addr, std::size_t size) {
59 for (u64 offset{}; offset < size; offset += page_size) { 59 for (u64 offset{}; offset < size; offset += page_size) {
60 if (!GetPageEntry(gpu_addr + offset).IsUnmapped()) { 60 if (!GetPageEntry(gpu_addr + offset).IsUnmapped()) {
61 return {}; 61 return std::nullopt;
62 } 62 }
63 } 63 }
64 64
@@ -135,13 +135,13 @@ std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size
135 } 135 }
136 } 136 }
137 137
138 return {}; 138 return std::nullopt;
139} 139}
140 140
141std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const { 141std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const {
142 const auto page_entry{GetPageEntry(gpu_addr)}; 142 const auto page_entry{GetPageEntry(gpu_addr)};
143 if (!page_entry.IsValid()) { 143 if (!page_entry.IsValid()) {
144 return {}; 144 return std::nullopt;
145 } 145 }
146 146
147 return page_entry.ToAddress() + (gpu_addr & page_mask); 147 return page_entry.ToAddress() + (gpu_addr & page_mask);
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 649074acd..5c650808b 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -46,11 +46,6 @@ public:
46 /// Finalize rendering the guest frame and draw into the presentation texture 46 /// Finalize rendering the guest frame and draw into the presentation texture
47 virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; 47 virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
48 48
49 /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
50 /// specific implementation)
51 /// Returns true if a frame was drawn
52 virtual bool TryPresent(int timeout_ms) = 0;
53
54 // Getter/setter functions: 49 // Getter/setter functions:
55 // ------------------------ 50 // ------------------------
56 51
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index ce3a65122..bbb8fb095 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -813,7 +813,7 @@ private:
813 const u8 location = static_cast<u8>(static_cast<u32>(index) * 4 + element); 813 const u8 location = static_cast<u8>(static_cast<u32>(index) * 4 + element);
814 const auto it = transform_feedback.find(location); 814 const auto it = transform_feedback.find(location);
815 if (it == transform_feedback.end()) { 815 if (it == transform_feedback.end()) {
816 return {}; 816 return std::nullopt;
817 } 817 }
818 return it->second.components; 818 return it->second.components;
819 } 819 }
@@ -1295,21 +1295,21 @@ private:
1295 switch (element) { 1295 switch (element) {
1296 case 0: 1296 case 0:
1297 UNIMPLEMENTED(); 1297 UNIMPLEMENTED();
1298 return {}; 1298 return std::nullopt;
1299 case 1: 1299 case 1:
1300 if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) { 1300 if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) {
1301 return {}; 1301 return std::nullopt;
1302 } 1302 }
1303 return {{"gl_Layer", Type::Int}}; 1303 return {{"gl_Layer", Type::Int}};
1304 case 2: 1304 case 2:
1305 if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) { 1305 if (stage == ShaderType::Vertex && !device.HasVertexViewportLayer()) {
1306 return {}; 1306 return std::nullopt;
1307 } 1307 }
1308 return {{"gl_ViewportIndex", Type::Int}}; 1308 return {{"gl_ViewportIndex", Type::Int}};
1309 case 3: 1309 case 3:
1310 return {{"gl_PointSize", Type::Float}}; 1310 return {{"gl_PointSize", Type::Float}};
1311 } 1311 }
1312 return {}; 1312 return std::nullopt;
1313 case Attribute::Index::FrontColor: 1313 case Attribute::Index::FrontColor:
1314 return {{"gl_FrontColor"s + GetSwizzle(element), Type::Float}}; 1314 return {{"gl_FrontColor"s + GetSwizzle(element), Type::Float}};
1315 case Attribute::Index::FrontSecondaryColor: 1315 case Attribute::Index::FrontSecondaryColor:
@@ -1332,7 +1332,7 @@ private:
1332 Type::Float}}; 1332 Type::Float}};
1333 } 1333 }
1334 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); 1334 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute));
1335 return {}; 1335 return std::nullopt;
1336 } 1336 }
1337 } 1337 }
1338 1338
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index a4c5b8f74..2ccca1993 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -32,20 +32,6 @@ namespace OpenGL {
32 32
33namespace { 33namespace {
34 34
35constexpr std::size_t SWAP_CHAIN_SIZE = 3;
36
37struct Frame {
38 u32 width{}; /// Width of the frame (to detect resize)
39 u32 height{}; /// Height of the frame
40 bool color_reloaded{}; /// Texture attachment was recreated (ie: resized)
41 OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
42 OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
43 OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
44 GLsync render_fence{}; /// Fence created on the render thread
45 GLsync present_fence{}; /// Fence created on the presentation thread
46 bool is_srgb{}; /// Framebuffer is sRGB or RGB
47};
48
49constexpr GLint PositionLocation = 0; 35constexpr GLint PositionLocation = 0;
50constexpr GLint TexCoordLocation = 1; 36constexpr GLint TexCoordLocation = 1;
51constexpr GLint ModelViewMatrixLocation = 0; 37constexpr GLint ModelViewMatrixLocation = 0;
@@ -58,24 +44,6 @@ struct ScreenRectVertex {
58 std::array<GLfloat, 2> tex_coord; 44 std::array<GLfloat, 2> tex_coord;
59}; 45};
60 46
61/// Returns true if any debug tool is attached
62bool HasDebugTool() {
63 const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
64 if (nsight) {
65 return true;
66 }
67
68 GLint num_extensions;
69 glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
70 for (GLuint index = 0; index < static_cast<GLuint>(num_extensions); ++index) {
71 const auto name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, index));
72 if (!std::strcmp(name, "GL_EXT_debug_tool")) {
73 return true;
74 }
75 }
76 return false;
77}
78
79/** 47/**
80 * Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left 48 * Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left
81 * corner and (width, height) on the lower-bottom. 49 * corner and (width, height) on the lower-bottom.
@@ -159,135 +127,15 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit
159 127
160} // Anonymous namespace 128} // Anonymous namespace
161 129
162/**
163 * For smooth Vsync rendering, we want to always present the latest frame that the core generates,
164 * but also make sure that rendering happens at the pace that the frontend dictates. This is a
165 * helper class that the renderer uses to sync frames between the render thread and the presentation
166 * thread
167 */
168class FrameMailbox {
169public:
170 std::mutex swap_chain_lock;
171 std::condition_variable present_cv;
172 std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{};
173 std::queue<Frame*> free_queue;
174 std::deque<Frame*> present_queue;
175 Frame* previous_frame{};
176
177 FrameMailbox() {
178 for (auto& frame : swap_chain) {
179 free_queue.push(&frame);
180 }
181 }
182
183 ~FrameMailbox() {
184 // lock the mutex and clear out the present and free_queues and notify any people who are
185 // blocked to prevent deadlock on shutdown
186 std::scoped_lock lock{swap_chain_lock};
187 std::queue<Frame*>().swap(free_queue);
188 present_queue.clear();
189 present_cv.notify_all();
190 }
191
192 void ReloadPresentFrame(Frame* frame, u32 height, u32 width) {
193 frame->present.Release();
194 frame->present.Create();
195 GLint previous_draw_fbo{};
196 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
197 glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
198 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
199 frame->color.handle);
200 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
201 LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
202 }
203 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
204 frame->color_reloaded = false;
205 }
206
207 void ReloadRenderFrame(Frame* frame, u32 width, u32 height) {
208 // Recreate the color texture attachment
209 frame->color.Release();
210 frame->color.Create();
211 const GLenum internal_format = frame->is_srgb ? GL_SRGB8 : GL_RGB8;
212 glNamedRenderbufferStorage(frame->color.handle, internal_format, width, height);
213
214 // Recreate the FBO for the render target
215 frame->render.Release();
216 frame->render.Create();
217 glBindFramebuffer(GL_FRAMEBUFFER, frame->render.handle);
218 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
219 frame->color.handle);
220 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
221 LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
222 }
223
224 frame->width = width;
225 frame->height = height;
226 frame->color_reloaded = true;
227 }
228
229 Frame* GetRenderFrame() {
230 std::unique_lock lock{swap_chain_lock};
231
232 // If theres no free frames, we will reuse the oldest render frame
233 if (free_queue.empty()) {
234 auto frame = present_queue.back();
235 present_queue.pop_back();
236 return frame;
237 }
238
239 Frame* frame = free_queue.front();
240 free_queue.pop();
241 return frame;
242 }
243
244 void ReleaseRenderFrame(Frame* frame) {
245 std::unique_lock lock{swap_chain_lock};
246 present_queue.push_front(frame);
247 present_cv.notify_one();
248 }
249
250 Frame* TryGetPresentFrame(int timeout_ms) {
251 std::unique_lock lock{swap_chain_lock};
252 // wait for new entries in the present_queue
253 present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
254 [&] { return !present_queue.empty(); });
255 if (present_queue.empty()) {
256 // timed out waiting for a frame to draw so return the previous frame
257 return previous_frame;
258 }
259
260 // free the previous frame and add it back to the free queue
261 if (previous_frame) {
262 free_queue.push(previous_frame);
263 }
264
265 // the newest entries are pushed to the front of the queue
266 Frame* frame = present_queue.front();
267 present_queue.pop_front();
268 // remove all old entries from the present queue and move them back to the free_queue
269 for (auto f : present_queue) {
270 free_queue.push(f);
271 }
272 present_queue.clear();
273 previous_frame = frame;
274 return frame;
275 }
276};
277
278RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_, 130RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
279 Core::Frontend::EmuWindow& emu_window_, 131 Core::Frontend::EmuWindow& emu_window_,
280 Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, 132 Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
281 std::unique_ptr<Core::Frontend::GraphicsContext> context) 133 std::unique_ptr<Core::Frontend::GraphicsContext> context)
282 : RendererBase{emu_window_, std::move(context)}, telemetry_session{telemetry_session_}, 134 : RendererBase{emu_window_, std::move(context)}, telemetry_session{telemetry_session_},
283 emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, program_manager{device}, 135 emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, program_manager{device} {}
284 has_debug_tool{HasDebugTool()} {}
285 136
286RendererOpenGL::~RendererOpenGL() = default; 137RendererOpenGL::~RendererOpenGL() = default;
287 138
288MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
289MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
290
291void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { 139void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
292 if (!framebuffer) { 140 if (!framebuffer) {
293 return; 141 return;
@@ -296,79 +144,34 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
296 PrepareRendertarget(framebuffer); 144 PrepareRendertarget(framebuffer);
297 RenderScreenshot(); 145 RenderScreenshot();
298 146
299 Frame* frame; 147 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
300 { 148 DrawScreen(emu_window.GetFramebufferLayout());
301 MICROPROFILE_SCOPE(OpenGL_WaitPresent);
302
303 frame = frame_mailbox->GetRenderFrame();
304
305 // Clean up sync objects before drawing
306
307 // INTEL driver workaround. We can't delete the previous render sync object until we are
308 // sure that the presentation is done
309 if (frame->present_fence) {
310 glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
311 }
312
313 // delete the draw fence if the frame wasn't presented
314 if (frame->render_fence) {
315 glDeleteSync(frame->render_fence);
316 frame->render_fence = 0;
317 }
318
319 // wait for the presentation to be done
320 if (frame->present_fence) {
321 glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
322 glDeleteSync(frame->present_fence);
323 frame->present_fence = 0;
324 }
325 }
326 149
327 { 150 ++m_current_frame;
328 MICROPROFILE_SCOPE(OpenGL_RenderFrame);
329 const auto& layout = render_window.GetFramebufferLayout();
330 151
331 // Recreate the frame if the size of the window has changed 152 rasterizer->TickFrame();
332 if (layout.width != frame->width || layout.height != frame->height ||
333 screen_info.display_srgb != frame->is_srgb) {
334 LOG_DEBUG(Render_OpenGL, "Reloading render frame");
335 frame->is_srgb = screen_info.display_srgb;
336 frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
337 }
338 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame->render.handle);
339 DrawScreen(layout);
340 // Create a fence for the frontend to wait on and swap this frame to OffTex
341 frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
342 glFlush();
343 frame_mailbox->ReleaseRenderFrame(frame);
344 m_current_frame++;
345 rasterizer->TickFrame();
346 }
347 153
348 render_window.PollEvents(); 154 render_window.PollEvents();
349 if (has_debug_tool) { 155 context->SwapBuffers();
350 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
351 Present(0);
352 context->SwapBuffers();
353 }
354} 156}
355 157
356void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) { 158void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) {
357 if (framebuffer) { 159 if (!framebuffer) {
358 // If framebuffer is provided, reload it from memory to a texture 160 return;
359 if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || 161 }
360 screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || 162 // If framebuffer is provided, reload it from memory to a texture
361 screen_info.texture.pixel_format != framebuffer->pixel_format || 163 if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
362 gl_framebuffer_data.empty()) { 164 screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
363 // Reallocate texture if the framebuffer size has changed. 165 screen_info.texture.pixel_format != framebuffer->pixel_format ||
364 // This is expected to not happen very often and hence should not be a 166 gl_framebuffer_data.empty()) {
365 // performance problem. 167 // Reallocate texture if the framebuffer size has changed.
366 ConfigureFramebufferTexture(screen_info.texture, *framebuffer); 168 // This is expected to not happen very often and hence should not be a
367 } 169 // performance problem.
368 170 ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
369 // Load the framebuffer from memory, draw it to the screen, and swap buffers
370 LoadFBToScreenInfo(*framebuffer);
371 } 171 }
172
173 // Load the framebuffer from memory, draw it to the screen, and swap buffers
174 LoadFBToScreenInfo(*framebuffer);
372} 175}
373 176
374void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { 177void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
@@ -418,8 +221,6 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
418} 221}
419 222
420void RendererOpenGL::InitOpenGLObjects() { 223void RendererOpenGL::InitOpenGLObjects() {
421 frame_mailbox = std::make_unique<FrameMailbox>();
422
423 glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(), 224 glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
424 Settings::values.bg_blue.GetValue(), 0.0f); 225 Settings::values.bg_blue.GetValue(), 0.0f);
425 226
@@ -647,51 +448,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
647 program_manager.RestoreGuestPipeline(); 448 program_manager.RestoreGuestPipeline();
648} 449}
649 450
650bool RendererOpenGL::TryPresent(int timeout_ms) {
651 if (has_debug_tool) {
652 LOG_DEBUG(Render_OpenGL,
653 "Skipping presentation because we are presenting on the main context");
654 return false;
655 }
656 return Present(timeout_ms);
657}
658
659bool RendererOpenGL::Present(int timeout_ms) {
660 const auto& layout = render_window.GetFramebufferLayout();
661 auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms);
662 if (!frame) {
663 LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
664 return false;
665 }
666
667 // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
668 // readback since we won't be doing any blending
669 glClear(GL_COLOR_BUFFER_BIT);
670
671 // Recreate the presentation FBO if the color attachment was changed
672 if (frame->color_reloaded) {
673 LOG_DEBUG(Render_OpenGL, "Reloading present frame");
674 frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
675 }
676 glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
677 // INTEL workaround.
678 // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
679 // it on the emulation thread without too much penalty
680 // glDeleteSync(frame.render_sync);
681 // frame.render_sync = 0;
682
683 glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
684 glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height,
685 GL_COLOR_BUFFER_BIT, GL_LINEAR);
686
687 // Insert fence for the main thread to block on
688 frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
689 glFlush();
690
691 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
692 return true;
693}
694
695void RendererOpenGL::RenderScreenshot() { 451void RendererOpenGL::RenderScreenshot() {
696 if (!renderer_settings.screenshot_requested) { 452 if (!renderer_settings.screenshot_requested) {
697 return; 453 return;
@@ -706,7 +462,7 @@ void RendererOpenGL::RenderScreenshot() {
706 screenshot_framebuffer.Create(); 462 screenshot_framebuffer.Create();
707 glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle); 463 glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle);
708 464
709 Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; 465 const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
710 466
711 GLuint renderbuffer; 467 GLuint renderbuffer;
712 glGenRenderbuffers(1, &renderbuffer); 468 glGenRenderbuffers(1, &renderbuffer);
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 5329577fb..9ef181f95 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -55,14 +55,6 @@ struct ScreenInfo {
55 TextureInfo texture; 55 TextureInfo texture;
56}; 56};
57 57
58struct PresentationTexture {
59 u32 width = 0;
60 u32 height = 0;
61 OGLTexture texture;
62};
63
64class FrameMailbox;
65
66class RendererOpenGL final : public VideoCore::RendererBase { 58class RendererOpenGL final : public VideoCore::RendererBase {
67public: 59public:
68 explicit RendererOpenGL(Core::TelemetrySession& telemetry_session, 60 explicit RendererOpenGL(Core::TelemetrySession& telemetry_session,
@@ -74,7 +66,6 @@ public:
74 bool Init() override; 66 bool Init() override;
75 void ShutDown() override; 67 void ShutDown() override;
76 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; 68 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
77 bool TryPresent(int timeout_ms) override;
78 69
79private: 70private:
80 /// Initializes the OpenGL state and creates persistent objects. 71 /// Initializes the OpenGL state and creates persistent objects.
@@ -102,8 +93,6 @@ private:
102 93
103 void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer); 94 void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer);
104 95
105 bool Present(int timeout_ms);
106
107 Core::TelemetrySession& telemetry_session; 96 Core::TelemetrySession& telemetry_session;
108 Core::Frontend::EmuWindow& emu_window; 97 Core::Frontend::EmuWindow& emu_window;
109 Core::Memory::Memory& cpu_memory; 98 Core::Memory::Memory& cpu_memory;
@@ -134,11 +123,6 @@ private:
134 /// Used for transforming the framebuffer orientation 123 /// Used for transforming the framebuffer orientation
135 Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{}; 124 Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{};
136 Common::Rectangle<int> framebuffer_crop_rect; 125 Common::Rectangle<int> framebuffer_crop_rect;
137
138 /// Frame presentation mailbox
139 std::unique_ptr<FrameMailbox> frame_mailbox;
140
141 bool has_debug_tool = false;
142}; 126};
143 127
144} // namespace OpenGL 128} // namespace OpenGL
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 4b6674f28..715182b3b 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -283,11 +283,6 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
283 render_window.PollEvents(); 283 render_window.PollEvents();
284} 284}
285 285
286bool RendererVulkan::TryPresent(int /*timeout_ms*/) {
287 // TODO (bunnei): ImplementMe
288 return true;
289}
290
291bool RendererVulkan::Init() { 286bool RendererVulkan::Init() {
292 library = OpenVulkanLibrary(); 287 library = OpenVulkanLibrary();
293 instance = CreateInstance(library, dld, render_window.GetWindowInfo().type, 288 instance = CreateInstance(library, dld, render_window.GetWindowInfo().type,
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index f9de865f6..49a4141ec 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -53,7 +53,6 @@ public:
53 bool Init() override; 53 bool Init() override;
54 void ShutDown() override; 54 void ShutDown() override;
55 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; 55 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
56 bool TryPresent(int timeout_ms) override;
57 56
58 static std::vector<std::string> EnumerateDevices(); 57 static std::vector<std::string> EnumerateDevices();
59 58
diff --git a/src/video_core/shader/ast.h b/src/video_core/shader/ast.h
index cca13bcde..8e5a22ab3 100644
--- a/src/video_core/shader/ast.h
+++ b/src/video_core/shader/ast.h
@@ -199,55 +199,48 @@ public:
199 } 199 }
200 200
201 std::optional<u32> GetGotoLabel() const { 201 std::optional<u32> GetGotoLabel() const {
202 auto inner = std::get_if<ASTGoto>(&data); 202 if (const auto* inner = std::get_if<ASTGoto>(&data)) {
203 if (inner) {
204 return {inner->label}; 203 return {inner->label};
205 } 204 }
206 return {}; 205 return std::nullopt;
207 } 206 }
208 207
209 Expr GetGotoCondition() const { 208 Expr GetGotoCondition() const {
210 auto inner = std::get_if<ASTGoto>(&data); 209 if (const auto* inner = std::get_if<ASTGoto>(&data)) {
211 if (inner) {
212 return inner->condition; 210 return inner->condition;
213 } 211 }
214 return nullptr; 212 return nullptr;
215 } 213 }
216 214
217 void MarkLabelUnused() { 215 void MarkLabelUnused() {
218 auto inner = std::get_if<ASTLabel>(&data); 216 if (auto* inner = std::get_if<ASTLabel>(&data)) {
219 if (inner) {
220 inner->unused = true; 217 inner->unused = true;
221 } 218 }
222 } 219 }
223 220
224 bool IsLabelUnused() const { 221 bool IsLabelUnused() const {
225 auto inner = std::get_if<ASTLabel>(&data); 222 if (const auto* inner = std::get_if<ASTLabel>(&data)) {
226 if (inner) {
227 return inner->unused; 223 return inner->unused;
228 } 224 }
229 return true; 225 return true;
230 } 226 }
231 227
232 std::optional<u32> GetLabelIndex() const { 228 std::optional<u32> GetLabelIndex() const {
233 auto inner = std::get_if<ASTLabel>(&data); 229 if (const auto* inner = std::get_if<ASTLabel>(&data)) {
234 if (inner) {
235 return {inner->index}; 230 return {inner->index};
236 } 231 }
237 return {}; 232 return std::nullopt;
238 } 233 }
239 234
240 Expr GetIfCondition() const { 235 Expr GetIfCondition() const {
241 auto inner = std::get_if<ASTIfThen>(&data); 236 if (const auto* inner = std::get_if<ASTIfThen>(&data)) {
242 if (inner) {
243 return inner->condition; 237 return inner->condition;
244 } 238 }
245 return nullptr; 239 return nullptr;
246 } 240 }
247 241
248 void SetGotoCondition(Expr new_condition) { 242 void SetGotoCondition(Expr new_condition) {
249 auto inner = std::get_if<ASTGoto>(&data); 243 if (auto* inner = std::get_if<ASTGoto>(&data)) {
250 if (inner) {
251 inner->condition = std::move(new_condition); 244 inner->condition = std::move(new_condition);
252 } 245 }
253 } 246 }
diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp
index 336397cdb..4c8971615 100644
--- a/src/video_core/shader/control_flow.cpp
+++ b/src/video_core/shader/control_flow.cpp
@@ -547,13 +547,13 @@ bool TryQuery(CFGRebuildState& state) {
547 gather_labels(q2.ssy_stack, state.ssy_labels, block); 547 gather_labels(q2.ssy_stack, state.ssy_labels, block);
548 gather_labels(q2.pbk_stack, state.pbk_labels, block); 548 gather_labels(q2.pbk_stack, state.pbk_labels, block);
549 if (std::holds_alternative<SingleBranch>(*block.branch)) { 549 if (std::holds_alternative<SingleBranch>(*block.branch)) {
550 const auto branch = std::get_if<SingleBranch>(block.branch.get()); 550 auto* branch = std::get_if<SingleBranch>(block.branch.get());
551 if (!branch->condition.IsUnconditional()) { 551 if (!branch->condition.IsUnconditional()) {
552 q2.address = block.end + 1; 552 q2.address = block.end + 1;
553 state.queries.push_back(q2); 553 state.queries.push_back(q2);
554 } 554 }
555 555
556 Query conditional_query{q2}; 556 auto& conditional_query = state.queries.emplace_back(q2);
557 if (branch->is_sync) { 557 if (branch->is_sync) {
558 if (branch->address == unassigned_branch) { 558 if (branch->address == unassigned_branch) {
559 branch->address = conditional_query.ssy_stack.top(); 559 branch->address = conditional_query.ssy_stack.top();
@@ -567,21 +567,21 @@ bool TryQuery(CFGRebuildState& state) {
567 conditional_query.pbk_stack.pop(); 567 conditional_query.pbk_stack.pop();
568 } 568 }
569 conditional_query.address = branch->address; 569 conditional_query.address = branch->address;
570 state.queries.push_back(std::move(conditional_query));
571 return true; 570 return true;
572 } 571 }
573 const auto multi_branch = std::get_if<MultiBranch>(block.branch.get()); 572
573 const auto* multi_branch = std::get_if<MultiBranch>(block.branch.get());
574 for (const auto& branch_case : multi_branch->branches) { 574 for (const auto& branch_case : multi_branch->branches) {
575 Query conditional_query{q2}; 575 auto& conditional_query = state.queries.emplace_back(q2);
576 conditional_query.address = branch_case.address; 576 conditional_query.address = branch_case.address;
577 state.queries.push_back(std::move(conditional_query));
578 } 577 }
578
579 return true; 579 return true;
580} 580}
581 581
582void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) { 582void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
583 const auto get_expr = ([&](const Condition& cond) -> Expr { 583 const auto get_expr = [](const Condition& cond) -> Expr {
584 Expr result{}; 584 Expr result;
585 if (cond.cc != ConditionCode::T) { 585 if (cond.cc != ConditionCode::T) {
586 result = MakeExpr<ExprCondCode>(cond.cc); 586 result = MakeExpr<ExprCondCode>(cond.cc);
587 } 587 }
@@ -594,10 +594,10 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
594 } 594 }
595 Expr extra = MakeExpr<ExprPredicate>(pred); 595 Expr extra = MakeExpr<ExprPredicate>(pred);
596 if (negate) { 596 if (negate) {
597 extra = MakeExpr<ExprNot>(extra); 597 extra = MakeExpr<ExprNot>(std::move(extra));
598 } 598 }
599 if (result) { 599 if (result) {
600 return MakeExpr<ExprAnd>(extra, result); 600 return MakeExpr<ExprAnd>(std::move(extra), std::move(result));
601 } 601 }
602 return extra; 602 return extra;
603 } 603 }
@@ -605,9 +605,10 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
605 return result; 605 return result;
606 } 606 }
607 return MakeExpr<ExprBoolean>(true); 607 return MakeExpr<ExprBoolean>(true);
608 }); 608 };
609
609 if (std::holds_alternative<SingleBranch>(*branch_info)) { 610 if (std::holds_alternative<SingleBranch>(*branch_info)) {
610 const auto branch = std::get_if<SingleBranch>(branch_info.get()); 611 const auto* branch = std::get_if<SingleBranch>(branch_info.get());
611 if (branch->address < 0) { 612 if (branch->address < 0) {
612 if (branch->kill) { 613 if (branch->kill) {
613 mm.InsertReturn(get_expr(branch->condition), true); 614 mm.InsertReturn(get_expr(branch->condition), true);
@@ -619,7 +620,7 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
619 mm.InsertGoto(get_expr(branch->condition), branch->address); 620 mm.InsertGoto(get_expr(branch->condition), branch->address);
620 return; 621 return;
621 } 622 }
622 const auto multi_branch = std::get_if<MultiBranch>(branch_info.get()); 623 const auto* multi_branch = std::get_if<MultiBranch>(branch_info.get());
623 for (const auto& branch_case : multi_branch->branches) { 624 for (const auto& branch_case : multi_branch->branches) {
624 mm.InsertGoto(MakeExpr<ExprGprEqual>(multi_branch->gpr, branch_case.cmp_value), 625 mm.InsertGoto(MakeExpr<ExprGprEqual>(multi_branch->gpr, branch_case.cmp_value),
625 branch_case.address); 626 branch_case.address);
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp
index cd424aa91..618d309d2 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -470,6 +470,7 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
470 default: 470 default:
471 break; 471 break;
472 } 472 }
473 break;
473 default: 474 default:
474 break; 475 break;
475 } 476 }
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index 29ebf65ba..a03b50e39 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -763,7 +763,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
763 763
764Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) { 764Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
765 const auto texture_type{instr.tld.texture_type}; 765 const auto texture_type{instr.tld.texture_type};
766 const bool is_array{instr.tld.is_array}; 766 const bool is_array{instr.tld.is_array != 0};
767 const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL}; 767 const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL};
768 const std::size_t coord_count{GetCoordCount(texture_type)}; 768 const std::size_t coord_count{GetCoordCount(texture_type)};
769 769
diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp
index d5ed81442..6be3ea92b 100644
--- a/src/video_core/shader/track.cpp
+++ b/src/video_core/shader/track.cpp
@@ -205,12 +205,12 @@ std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code,
205 const auto result = TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1); 205 const auto result = TrackRegister(&std::get<GprNode>(*tracked), code, cursor - 1);
206 const auto& found = result.first; 206 const auto& found = result.first;
207 if (!found) { 207 if (!found) {
208 return {}; 208 return std::nullopt;
209 } 209 }
210 if (const auto immediate = std::get_if<ImmediateNode>(&*found)) { 210 if (const auto immediate = std::get_if<ImmediateNode>(&*found)) {
211 return immediate->GetValue(); 211 return immediate->GetValue();
212 } 212 }
213 return {}; 213 return std::nullopt;
214} 214}
215 215
216std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code, 216std::pair<Node, s64> ShaderIR::TrackRegister(const GprNode* tracked, const NodeBlock& code,
diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp
index dfcf36e0b..b44c09d71 100644
--- a/src/video_core/texture_cache/surface_base.cpp
+++ b/src/video_core/texture_cache/surface_base.cpp
@@ -115,20 +115,24 @@ std::optional<std::pair<u32, u32>> SurfaceBaseImpl::GetLayerMipmap(
115 if (gpu_addr == candidate_gpu_addr) { 115 if (gpu_addr == candidate_gpu_addr) {
116 return {{0, 0}}; 116 return {{0, 0}};
117 } 117 }
118
118 if (candidate_gpu_addr < gpu_addr) { 119 if (candidate_gpu_addr < gpu_addr) {
119 return {}; 120 return std::nullopt;
120 } 121 }
122
121 const auto relative_address{static_cast<GPUVAddr>(candidate_gpu_addr - gpu_addr)}; 123 const auto relative_address{static_cast<GPUVAddr>(candidate_gpu_addr - gpu_addr)};
122 const auto layer{static_cast<u32>(relative_address / layer_size)}; 124 const auto layer{static_cast<u32>(relative_address / layer_size)};
123 if (layer >= params.depth) { 125 if (layer >= params.depth) {
124 return {}; 126 return std::nullopt;
125 } 127 }
128
126 const GPUVAddr mipmap_address = relative_address - layer_size * layer; 129 const GPUVAddr mipmap_address = relative_address - layer_size * layer;
127 const auto mipmap_it = 130 const auto mipmap_it =
128 Common::BinaryFind(mipmap_offsets.begin(), mipmap_offsets.end(), mipmap_address); 131 Common::BinaryFind(mipmap_offsets.begin(), mipmap_offsets.end(), mipmap_address);
129 if (mipmap_it == mipmap_offsets.end()) { 132 if (mipmap_it == mipmap_offsets.end()) {
130 return {}; 133 return std::nullopt;
131 } 134 }
135
132 const auto level{static_cast<u32>(std::distance(mipmap_offsets.begin(), mipmap_it))}; 136 const auto level{static_cast<u32>(std::distance(mipmap_offsets.begin(), mipmap_it))};
133 return std::make_pair(layer, level); 137 return std::make_pair(layer, level);
134} 138}
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index caa2d06d3..408eac2b7 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -218,15 +218,6 @@ public:
218 218
219 virtual ~RenderWidget() = default; 219 virtual ~RenderWidget() = default;
220 220
221 /// Called on the UI thread when this Widget is ready to draw
222 /// Dervied classes can override this to draw the latest frame.
223 virtual void Present() {}
224
225 void paintEvent(QPaintEvent* event) override {
226 Present();
227 update();
228 }
229
230 QPaintEngine* paintEngine() const override { 221 QPaintEngine* paintEngine() const override {
231 return nullptr; 222 return nullptr;
232 } 223 }
@@ -245,20 +236,8 @@ public:
245 context = std::move(context_); 236 context = std::move(context_);
246 } 237 }
247 238
248 void Present() override {
249 if (!isVisible()) {
250 return;
251 }
252
253 context->MakeCurrent();
254 if (Core::System::GetInstance().Renderer().TryPresent(100)) {
255 context->SwapBuffers();
256 glFinish();
257 }
258 }
259
260private: 239private:
261 std::unique_ptr<Core::Frontend::GraphicsContext> context{}; 240 std::unique_ptr<Core::Frontend::GraphicsContext> context;
262}; 241};
263 242
264#ifdef HAS_VULKAN 243#ifdef HAS_VULKAN
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 2bc55a26a..d2913d613 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -36,6 +36,11 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button
36 Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V, 36 Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V,
37}; 37};
38 38
39const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = {
40 Qt::Key_7,
41 Qt::Key_8,
42};
43
39const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ 44const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
40 { 45 {
41 Qt::Key_Up, 46 Qt::Key_Up,
@@ -284,6 +289,22 @@ void Config::ReadPlayerValues() {
284 } 289 }
285 } 290 }
286 291
292 for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
293 const std::string default_param =
294 InputCommon::GenerateKeyboardParam(default_motions[i]);
295 auto& player_motions = player.motions[i];
296
297 player_motions = qt_config
298 ->value(QStringLiteral("player_%1_").arg(p) +
299 QString::fromUtf8(Settings::NativeMotion::mapping[i]),
300 QString::fromStdString(default_param))
301 .toString()
302 .toStdString();
303 if (player_motions.empty()) {
304 player_motions = default_param;
305 }
306 }
307
287 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 308 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
288 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 309 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
289 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 310 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
@@ -424,6 +445,7 @@ void Config::ReadControlValues() {
424 445
425 Settings::values.vibration_enabled = 446 Settings::values.vibration_enabled =
426 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); 447 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
448 Settings::values.motion_enabled = ReadSetting(QStringLiteral("motion_enabled"), true).toBool();
427 Settings::values.use_docked_mode = 449 Settings::values.use_docked_mode =
428 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); 450 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
429 451
@@ -922,6 +944,14 @@ void Config::SavePlayerValues() {
922 QString::fromStdString(player.buttons[i]), 944 QString::fromStdString(player.buttons[i]),
923 QString::fromStdString(default_param)); 945 QString::fromStdString(default_param));
924 } 946 }
947 for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
948 const std::string default_param =
949 InputCommon::GenerateKeyboardParam(default_motions[i]);
950 WriteSetting(QStringLiteral("player_%1_").arg(p) +
951 QString::fromStdString(Settings::NativeMotion::mapping[i]),
952 QString::fromStdString(player.motions[i]),
953 QString::fromStdString(default_param));
954 }
925 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 955 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
926 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 956 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
927 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 957 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
@@ -1062,6 +1092,7 @@ void Config::SaveControlValues() {
1062 SaveMotionTouchValues(); 1092 SaveMotionTouchValues();
1063 1093
1064 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); 1094 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
1095 WriteSetting(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);
1065 WriteSetting(QStringLiteral("motion_device"), 1096 WriteSetting(QStringLiteral("motion_device"),
1066 QString::fromStdString(Settings::values.motion_device), 1097 QString::fromStdString(Settings::values.motion_device),
1067 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); 1098 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ca0d29c6c..5d8e45d78 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -23,6 +23,7 @@ public:
23 void Save(); 23 void Save();
24 24
25 static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; 25 static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
26 static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
26 static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; 27 static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
27 static const std::array<int, 2> default_stick_mod; 28 static const std::array<int, 2> default_stick_mod;
28 static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> 29 static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 7ea17a4db..2725fcb2b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -146,6 +146,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
146 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); 146 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
147 }); 147 });
148 148
149 connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
150 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
151 });
152
149 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); 153 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
150 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); 154 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
151 155
@@ -172,6 +176,7 @@ void ConfigureInput::ApplyConfiguration() {
172 OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); 176 OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
173 177
174 Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); 178 Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
179 Settings::values.motion_enabled = ui->motionGroup->isChecked();
175} 180}
176 181
177void ConfigureInput::changeEvent(QEvent* event) { 182void ConfigureInput::changeEvent(QEvent* event) {
@@ -191,6 +196,7 @@ void ConfigureInput::LoadConfiguration() {
191 UpdateDockedState(Settings::values.players[8].connected); 196 UpdateDockedState(Settings::values.players[8].connected);
192 197
193 ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); 198 ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
199 ui->motionGroup->setChecked(Settings::values.motion_enabled);
194} 200}
195 201
196void ConfigureInput::LoadPlayerControllerIndices() { 202void ConfigureInput::LoadPlayerControllerIndices() {
@@ -217,6 +223,7 @@ void ConfigureInput::RestoreDefaults() {
217 ui->radioDocked->setChecked(true); 223 ui->radioDocked->setChecked(true);
218 ui->radioUndocked->setChecked(false); 224 ui->radioUndocked->setChecked(false);
219 ui->vibrationGroup->setChecked(true); 225 ui->vibrationGroup->setChecked(true);
226 ui->motionGroup->setChecked(true);
220} 227}
221 228
222void ConfigureInput::UpdateDockedState(bool is_handheld) { 229void ConfigureInput::UpdateDockedState(bool is_handheld) {
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index a53578837..698cb1940 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -18,6 +18,7 @@
18#include "core/hle/service/sm/sm.h" 18#include "core/hle/service/sm/sm.h"
19#include "input_common/gcadapter/gc_poller.h" 19#include "input_common/gcadapter/gc_poller.h"
20#include "input_common/main.h" 20#include "input_common/main.h"
21#include "input_common/udp/udp.h"
21#include "ui_configure_input_player.h" 22#include "ui_configure_input_player.h"
22#include "yuzu/configuration/config.h" 23#include "yuzu/configuration/config.h"
23#include "yuzu/configuration/configure_input_player.h" 24#include "yuzu/configuration/configure_input_player.h"
@@ -149,6 +150,14 @@ QString ButtonToText(const Common::ParamPackage& param) {
149 return GetKeyName(param.Get("code", 0)); 150 return GetKeyName(param.Get("code", 0));
150 } 151 }
151 152
153 if (param.Get("engine", "") == "cemuhookudp") {
154 if (param.Has("pad_index")) {
155 const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
156 return QObject::tr("Motion %1").arg(motion_str);
157 }
158 return GetKeyName(param.Get("code", 0));
159 }
160
152 if (param.Get("engine", "") == "sdl") { 161 if (param.Get("engine", "") == "sdl") {
153 if (param.Has("hat")) { 162 if (param.Has("hat")) {
154 const QString hat_str = QString::fromStdString(param.Get("hat", "")); 163 const QString hat_str = QString::fromStdString(param.Get("hat", ""));
@@ -247,6 +256,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
247 ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, 256 ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
248 }; 257 };
249 258
259 mod_buttons = {
260 ui->buttonLStickMod,
261 ui->buttonRStickMod,
262 };
263
250 analog_map_buttons = {{ 264 analog_map_buttons = {{
251 { 265 {
252 ui->buttonLStickUp, 266 ui->buttonLStickUp,
@@ -262,6 +276,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
262 }, 276 },
263 }}; 277 }};
264 278
279 motion_map = {
280 ui->buttonMotionLeft,
281 ui->buttonMotionRight,
282 };
283
265 analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; 284 analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
266 analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; 285 analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
267 analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup}; 286 analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup};
@@ -271,7 +290,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
271 analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange}; 290 analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
272 291
273 const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param, 292 const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param,
274 int default_val) { 293 int default_val, InputCommon::Polling::DeviceType type) {
275 connect(button, &QPushButton::clicked, [=, this] { 294 connect(button, &QPushButton::clicked, [=, this] {
276 HandleClick( 295 HandleClick(
277 button, 296 button,
@@ -291,22 +310,56 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
291 } 310 }
292 *param = std::move(params); 311 *param = std::move(params);
293 }, 312 },
294 InputCommon::Polling::DeviceType::Button); 313 type);
295 }); 314 });
296 }; 315 };
297 316
298 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { 317 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
299 auto* const button = button_map[button_id]; 318 auto* const button = button_map[button_id];
319
300 if (button == nullptr) { 320 if (button == nullptr) {
301 continue; 321 continue;
302 } 322 }
323
303 ConfigureButtonClick(button_map[button_id], &buttons_param[button_id], 324 ConfigureButtonClick(button_map[button_id], &buttons_param[button_id],
304 Config::default_buttons[button_id]); 325 Config::default_buttons[button_id],
326 InputCommon::Polling::DeviceType::Button);
327
328 button->setContextMenuPolicy(Qt::CustomContextMenu);
329
330 connect(button, &QPushButton::customContextMenuRequested,
331 [=, this](const QPoint& menu_location) {
332 QMenu context_menu;
333 context_menu.addAction(tr("Clear"), [&] {
334 buttons_param[button_id].Clear();
335 button_map[button_id]->setText(tr("[not set]"));
336 });
337 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
338 });
305 } 339 }
306 340
307 // Handle clicks for the modifier buttons as well. 341 for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
308 ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]); 342 auto* const button = motion_map[motion_id];
309 ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]); 343 if (button == nullptr) {
344 continue;
345 }
346
347 ConfigureButtonClick(motion_map[motion_id], &motions_param[motion_id],
348 Config::default_motions[motion_id],
349 InputCommon::Polling::DeviceType::Motion);
350
351 button->setContextMenuPolicy(Qt::CustomContextMenu);
352
353 connect(button, &QPushButton::customContextMenuRequested,
354 [=, this](const QPoint& menu_location) {
355 QMenu context_menu;
356 context_menu.addAction(tr("Clear"), [&] {
357 motions_param[motion_id].Clear();
358 motion_map[motion_id]->setText(tr("[not set]"));
359 });
360 context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
361 });
362 }
310 363
311 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 364 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
312 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 365 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
@@ -325,8 +378,38 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
325 }, 378 },
326 InputCommon::Polling::DeviceType::AnalogPreferred); 379 InputCommon::Polling::DeviceType::AnalogPreferred);
327 }); 380 });
381
382 analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
383
384 connect(analog_button, &QPushButton::customContextMenuRequested,
385 [=, this](const QPoint& menu_location) {
386 QMenu context_menu;
387 context_menu.addAction(tr("Clear"), [&] {
388 analogs_param[analog_id].Clear();
389 analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
390 });
391 context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
392 menu_location));
393 });
328 } 394 }
329 395
396 // Handle clicks for the modifier buttons as well.
397 ConfigureButtonClick(mod_buttons[analog_id], &stick_mod_param[analog_id],
398 Config::default_stick_mod[analog_id],
399 InputCommon::Polling::DeviceType::Button);
400
401 mod_buttons[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu);
402
403 connect(mod_buttons[analog_id], &QPushButton::customContextMenuRequested,
404 [=, this](const QPoint& menu_location) {
405 QMenu context_menu;
406 context_menu.addAction(tr("Clear"), [&] {
407 stick_mod_param[analog_id].Clear();
408 mod_buttons[analog_id]->setText(tr("[not set]"));
409 });
410 context_menu.exec(mod_buttons[analog_id]->mapToGlobal(menu_location));
411 });
412
330 connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged), 413 connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
331 [=, this] { 414 [=, this] {
332 const auto spinbox_value = analog_map_range_spinbox[analog_id]->value(); 415 const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
@@ -385,9 +468,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
385 468
386 UpdateControllerIcon(); 469 UpdateControllerIcon();
387 UpdateControllerAvailableButtons(); 470 UpdateControllerAvailableButtons();
471 UpdateMotionButtons();
388 connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { 472 connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
389 UpdateControllerIcon(); 473 UpdateControllerIcon();
390 UpdateControllerAvailableButtons(); 474 UpdateControllerAvailableButtons();
475 UpdateMotionButtons();
391 }); 476 });
392 477
393 connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, 478 connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this,
@@ -417,6 +502,13 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
417 return; 502 return;
418 } 503 }
419 } 504 }
505 if (input_subsystem->GetUDPMotions()->IsPolling()) {
506 params = input_subsystem->GetUDPMotions()->GetNextInput();
507 if (params.Has("engine")) {
508 SetPollingResult(params, false);
509 return;
510 }
511 }
420 for (auto& poller : device_pollers) { 512 for (auto& poller : device_pollers) {
421 params = poller->GetNextInput(); 513 params = poller->GetNextInput();
422 if (params.Has("engine")) { 514 if (params.Has("engine")) {
@@ -448,6 +540,10 @@ void ConfigureInputPlayer::ApplyConfiguration() {
448 return; 540 return;
449 } 541 }
450 542
543 auto& motions = player.motions;
544 std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
545 [](const Common::ParamPackage& param) { return param.Serialize(); });
546
451 player.controller_type = 547 player.controller_type =
452 static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex()); 548 static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex());
453 player.connected = ui->groupConnectedController->isChecked(); 549 player.connected = ui->groupConnectedController->isChecked();
@@ -501,6 +597,8 @@ void ConfigureInputPlayer::LoadConfiguration() {
501 [](const std::string& str) { return Common::ParamPackage(str); }); 597 [](const std::string& str) { return Common::ParamPackage(str); });
502 std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(), 598 std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),
503 [](const std::string& str) { return Common::ParamPackage(str); }); 599 [](const std::string& str) { return Common::ParamPackage(str); });
600 std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(),
601 [](const std::string& str) { return Common::ParamPackage(str); });
504 } 602 }
505 603
506 UpdateUI(); 604 UpdateUI();
@@ -530,20 +628,23 @@ void ConfigureInputPlayer::RestoreDefaults() {
530 InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; 628 InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
531 } 629 }
532 630
533 // Reset Modifier Buttons 631 // Reset Analogs and Modifier Buttons
534 lstick_mod =
535 Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[0]));
536 rstick_mod =
537 Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[1]));
538
539 // Reset Analogs
540 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 632 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
541 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 633 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
542 Common::ParamPackage params{InputCommon::GenerateKeyboardParam( 634 Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
543 Config::default_analogs[analog_id][sub_button_id])}; 635 Config::default_analogs[analog_id][sub_button_id])};
544 SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); 636 SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
545 } 637 }
638
639 stick_mod_param[analog_id] = Common::ParamPackage(
640 InputCommon::GenerateKeyboardParam(Config::default_stick_mod[analog_id]));
641 }
642
643 for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
644 motions_param[motion_id] = Common::ParamPackage{
645 InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
546 } 646 }
647
547 UpdateUI(); 648 UpdateUI();
548 UpdateInputDevices(); 649 UpdateInputDevices();
549 ui->comboControllerType->setCurrentIndex(0); 650 ui->comboControllerType->setCurrentIndex(0);
@@ -552,25 +653,33 @@ void ConfigureInputPlayer::RestoreDefaults() {
552void ConfigureInputPlayer::ClearAll() { 653void ConfigureInputPlayer::ClearAll() {
553 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { 654 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
554 const auto* const button = button_map[button_id]; 655 const auto* const button = button_map[button_id];
555 if (button == nullptr || !button->isEnabled()) { 656 if (button == nullptr) {
556 continue; 657 continue;
557 } 658 }
558 659
559 buttons_param[button_id].Clear(); 660 buttons_param[button_id].Clear();
560 } 661 }
561 662
562 lstick_mod.Clear();
563 rstick_mod.Clear();
564
565 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 663 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
566 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 664 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
567 const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; 665 const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
568 if (analog_button == nullptr || !analog_button->isEnabled()) { 666 if (analog_button == nullptr) {
569 continue; 667 continue;
570 } 668 }
571 669
572 analogs_param[analog_id].Clear(); 670 analogs_param[analog_id].Clear();
573 } 671 }
672
673 stick_mod_param[analog_id].Clear();
674 }
675
676 for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
677 const auto* const motion_button = motion_map[motion_id];
678 if (motion_button == nullptr) {
679 continue;
680 }
681
682 motions_param[motion_id].Clear();
574 } 683 }
575 684
576 UpdateUI(); 685 UpdateUI();
@@ -582,8 +691,9 @@ void ConfigureInputPlayer::UpdateUI() {
582 button_map[button]->setText(ButtonToText(buttons_param[button])); 691 button_map[button]->setText(ButtonToText(buttons_param[button]));
583 } 692 }
584 693
585 ui->buttonLStickMod->setText(ButtonToText(lstick_mod)); 694 for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
586 ui->buttonRStickMod->setText(ButtonToText(rstick_mod)); 695 motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id]));
696 }
587 697
588 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 698 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
589 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 699 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
@@ -597,6 +707,8 @@ void ConfigureInputPlayer::UpdateUI() {
597 AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); 707 AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
598 } 708 }
599 709
710 mod_buttons[analog_id]->setText(ButtonToText(stick_mod_param[analog_id]));
711
600 const auto deadzone_label = analog_map_deadzone_label[analog_id]; 712 const auto deadzone_label = analog_map_deadzone_label[analog_id];
601 const auto deadzone_slider = analog_map_deadzone_slider[analog_id]; 713 const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
602 const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id]; 714 const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id];
@@ -659,7 +771,11 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
659void ConfigureInputPlayer::HandleClick( 771void ConfigureInputPlayer::HandleClick(
660 QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, 772 QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
661 InputCommon::Polling::DeviceType type) { 773 InputCommon::Polling::DeviceType type) {
662 button->setText(tr("[waiting]")); 774 if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
775 button->setText(tr("Shake!"));
776 } else {
777 button->setText(tr("[waiting]"));
778 }
663 button->setFocus(); 779 button->setFocus();
664 780
665 // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a 781 // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
@@ -683,6 +799,10 @@ void ConfigureInputPlayer::HandleClick(
683 input_subsystem->GetGCAnalogs()->BeginConfiguration(); 799 input_subsystem->GetGCAnalogs()->BeginConfiguration();
684 } 800 }
685 801
802 if (type == InputCommon::Polling::DeviceType::Motion) {
803 input_subsystem->GetUDPMotions()->BeginConfiguration();
804 }
805
686 timeout_timer->start(2500); // Cancel after 2.5 seconds 806 timeout_timer->start(2500); // Cancel after 2.5 seconds
687 poll_timer->start(50); // Check for new inputs every 50ms 807 poll_timer->start(50); // Check for new inputs every 50ms
688} 808}
@@ -700,6 +820,8 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
700 input_subsystem->GetGCButtons()->EndConfiguration(); 820 input_subsystem->GetGCButtons()->EndConfiguration();
701 input_subsystem->GetGCAnalogs()->EndConfiguration(); 821 input_subsystem->GetGCAnalogs()->EndConfiguration();
702 822
823 input_subsystem->GetUDPMotions()->EndConfiguration();
824
703 if (!abort) { 825 if (!abort) {
704 (*input_setter)(params); 826 (*input_setter)(params);
705 } 827 }
@@ -832,6 +954,37 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
832 } 954 }
833} 955}
834 956
957void ConfigureInputPlayer::UpdateMotionButtons() {
958 if (debug) {
959 // Motion isn't used with the debug controller, hide both groupboxes.
960 ui->buttonMotionLeftGroup->hide();
961 ui->buttonMotionRightGroup->hide();
962 return;
963 }
964
965 // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
966 switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
967 case Settings::ControllerType::ProController:
968 case Settings::ControllerType::LeftJoycon:
969 case Settings::ControllerType::Handheld:
970 // Show "Motion 1" and hide "Motion 2".
971 ui->buttonMotionLeftGroup->show();
972 ui->buttonMotionRightGroup->hide();
973 break;
974 case Settings::ControllerType::RightJoycon:
975 // Show "Motion 2" and hide "Motion 1".
976 ui->buttonMotionLeftGroup->hide();
977 ui->buttonMotionRightGroup->show();
978 break;
979 case Settings::ControllerType::DualJoyconDetached:
980 default:
981 // Show both "Motion 1/2".
982 ui->buttonMotionLeftGroup->show();
983 ui->buttonMotionRightGroup->show();
984 break;
985 }
986}
987
835void ConfigureInputPlayer::showEvent(QShowEvent* event) { 988void ConfigureInputPlayer::showEvent(QShowEvent* event) {
836 if (bottom_row == nullptr) { 989 if (bottom_row == nullptr) {
837 return; 990 return;
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index a25bc3bd9..ce443dec5 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -107,6 +107,9 @@ private:
107 /// Hides and disables controller settings based on the current controller type. 107 /// Hides and disables controller settings based on the current controller type.
108 void UpdateControllerAvailableButtons(); 108 void UpdateControllerAvailableButtons();
109 109
110 /// Shows or hides motion groupboxes based on the current controller type.
111 void UpdateMotionButtons();
112
110 /// Gets the default controller mapping for this device and auto configures the input to match. 113 /// Gets the default controller mapping for this device and auto configures the input to match.
111 void UpdateMappingWithDefaults(); 114 void UpdateMappingWithDefaults();
112 115
@@ -128,14 +131,17 @@ private:
128 131
129 std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; 132 std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
130 std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; 133 std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
134 std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> stick_mod_param;
135 std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param;
131 136
132 static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; 137 static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;
133 138
134 /// Each button input is represented by a QPushButton. 139 /// Each button input is represented by a QPushButton.
135 std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; 140 std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
141 /// Each motion input is represented by a QPushButton.
142 std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map;
136 /// Extra buttons for the modifiers. 143 /// Extra buttons for the modifiers.
137 Common::ParamPackage lstick_mod; 144 std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> mod_buttons;
138 Common::ParamPackage rstick_mod;
139 145
140 /// A group of four QPushButtons represent one analog input. The buttons each represent up, 146 /// A group of four QPushButtons represent one analog input. The buttons each represent up,
141 /// down, left, right, respectively. 147 /// down, left, right, respectively.
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index 9bc681894..e03461d9d 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -1983,6 +1983,9 @@
1983 <property name="spacing"> 1983 <property name="spacing">
1984 <number>3</number> 1984 <number>3</number>
1985 </property> 1985 </property>
1986 <property name="topMargin">
1987 <number>0</number>
1988 </property>
1986 <item> 1989 <item>
1987 <spacer name="horizontalSpacerMiscButtons1"> 1990 <spacer name="horizontalSpacerMiscButtons1">
1988 <property name="orientation"> 1991 <property name="orientation">
@@ -1990,21 +1993,119 @@
1990 </property> 1993 </property>
1991 <property name="sizeHint" stdset="0"> 1994 <property name="sizeHint" stdset="0">
1992 <size> 1995 <size>
1993 <width>40</width> 1996 <width>20</width>
1994 <height>0</height> 1997 <height>20</height>
1995 </size> 1998 </size>
1996 </property> 1999 </property>
1997 </spacer> 2000 </spacer>
1998 </item> 2001 </item>
1999 <item> 2002 <item>
2003 <widget class="QGroupBox" name="buttonMotionLeftGroup">
2004 <property name="title">
2005 <string>Motion 1</string>
2006 </property>
2007 <property name="alignment">
2008 <set>Qt::AlignCenter</set>
2009 </property>
2010 <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2">
2011 <property name="spacing">
2012 <number>3</number>
2013 </property>
2014 <property name="leftMargin">
2015 <number>3</number>
2016 </property>
2017 <property name="topMargin">
2018 <number>3</number>
2019 </property>
2020 <property name="rightMargin">
2021 <number>3</number>
2022 </property>
2023 <property name="bottomMargin">
2024 <number>3</number>
2025 </property>
2026 <item>
2027 <widget class="QPushButton" name="buttonMotionLeft">
2028 <property name="minimumSize">
2029 <size>
2030 <width>57</width>
2031 <height>0</height>
2032 </size>
2033 </property>
2034 <property name="maximumSize">
2035 <size>
2036 <width>55</width>
2037 <height>16777215</height>
2038 </size>
2039 </property>
2040 <property name="styleSheet">
2041 <string notr="true">min-width: 55px;</string>
2042 </property>
2043 <property name="text">
2044 <string>Left</string>
2045 </property>
2046 </widget>
2047 </item>
2048 </layout>
2049 </widget>
2050 </item>
2051 <item>
2052 <widget class="QGroupBox" name="buttonMotionRightGroup">
2053 <property name="title">
2054 <string>Motion 2</string>
2055 </property>
2056 <property name="alignment">
2057 <set>Qt::AlignCenter</set>
2058 </property>
2059 <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2">
2060 <property name="spacing">
2061 <number>3</number>
2062 </property>
2063 <property name="leftMargin">
2064 <number>3</number>
2065 </property>
2066 <property name="topMargin">
2067 <number>3</number>
2068 </property>
2069 <property name="rightMargin">
2070 <number>3</number>
2071 </property>
2072 <property name="bottomMargin">
2073 <number>3</number>
2074 </property>
2075 <item>
2076 <widget class="QPushButton" name="buttonMotionRight">
2077 <property name="minimumSize">
2078 <size>
2079 <width>57</width>
2080 <height>0</height>
2081 </size>
2082 </property>
2083 <property name="maximumSize">
2084 <size>
2085 <width>55</width>
2086 <height>16777215</height>
2087 </size>
2088 </property>
2089 <property name="styleSheet">
2090 <string notr="true">min-width: 55px;</string>
2091 </property>
2092 <property name="text">
2093 <string>Right</string>
2094 </property>
2095 </widget>
2096 </item>
2097 </layout>
2098 </widget>
2099 </item>
2100 <item>
2000 <spacer name="horizontalSpacerMiscButtons4"> 2101 <spacer name="horizontalSpacerMiscButtons4">
2001 <property name="orientation"> 2102 <property name="orientation">
2002 <enum>Qt::Horizontal</enum> 2103 <enum>Qt::Horizontal</enum>
2003 </property> 2104 </property>
2004 <property name="sizeHint" stdset="0"> 2105 <property name="sizeHint" stdset="0">
2005 <size> 2106 <size>
2006 <width>40</width> 2107 <width>20</width>
2007 <height>0</height> 2108 <height>20</height>
2008 </size> 2109 </size>
2009 </property> 2110 </property>
2010 </spacer> 2111 </spacer>
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 6a71d9644..a9738e298 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -56,7 +56,7 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
56 case Qt::Key_Return: 56 case Qt::Key_Return:
57 case Qt::Key_Enter: { 57 case Qt::Key_Enter: {
58 if (gamelist->search_field->visible == 1) { 58 if (gamelist->search_field->visible == 1) {
59 QString file_path = gamelist->getLastFilterResultItem(); 59 const QString file_path = gamelist->GetLastFilterResultItem();
60 60
61 // To avoid loading error dialog loops while confirming them using enter 61 // To avoid loading error dialog loops while confirming them using enter
62 // Also users usually want to run a different game after closing one 62 // Also users usually want to run a different game after closing one
@@ -83,22 +83,25 @@ void GameListSearchField::setFilterResult(int visible, int total) {
83 label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); 83 label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
84} 84}
85 85
86QString GameList::getLastFilterResultItem() const { 86QString GameList::GetLastFilterResultItem() const {
87 QStandardItem* folder;
88 QStandardItem* child;
89 QString file_path; 87 QString file_path;
90 const int folder_count = item_model->rowCount(); 88 const int folder_count = item_model->rowCount();
89
91 for (int i = 0; i < folder_count; ++i) { 90 for (int i = 0; i < folder_count; ++i) {
92 folder = item_model->item(i, 0); 91 const QStandardItem* folder = item_model->item(i, 0);
93 const QModelIndex folder_index = folder->index(); 92 const QModelIndex folder_index = folder->index();
94 const int children_count = folder->rowCount(); 93 const int children_count = folder->rowCount();
94
95 for (int j = 0; j < children_count; ++j) { 95 for (int j = 0; j < children_count; ++j) {
96 if (!tree_view->isRowHidden(j, folder_index)) { 96 if (tree_view->isRowHidden(j, folder_index)) {
97 child = folder->child(j, 0); 97 continue;
98 file_path = child->data(GameListItemPath::FullPathRole).toString();
99 } 98 }
99
100 const QStandardItem* child = folder->child(j, 0);
101 file_path = child->data(GameListItemPath::FullPathRole).toString();
100 } 102 }
101 } 103 }
104
102 return file_path; 105 return file_path;
103} 106}
104 107
@@ -123,7 +126,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
123 edit_filter->setPlaceholderText(tr("Enter pattern to filter")); 126 edit_filter->setPlaceholderText(tr("Enter pattern to filter"));
124 edit_filter->installEventFilter(key_release_eater); 127 edit_filter->installEventFilter(key_release_eater);
125 edit_filter->setClearButtonEnabled(true); 128 edit_filter->setClearButtonEnabled(true);
126 connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::onTextChanged); 129 connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged);
127 label_filter_result = new QLabel; 130 label_filter_result = new QLabel;
128 button_filter_close = new QToolButton(this); 131 button_filter_close = new QToolButton(this);
129 button_filter_close->setText(QStringLiteral("X")); 132 button_filter_close->setText(QStringLiteral("X"));
@@ -133,7 +136,7 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
133 "#000000; font-weight: bold; background: #F0F0F0; }" 136 "#000000; font-weight: bold; background: #F0F0F0; }"
134 "QToolButton:hover{ border: none; padding: 0px; color: " 137 "QToolButton:hover{ border: none; padding: 0px; color: "
135 "#EEEEEE; font-weight: bold; background: #E81123}")); 138 "#EEEEEE; font-weight: bold; background: #E81123}"));
136 connect(button_filter_close, &QToolButton::clicked, parent, &GameList::onFilterCloseClicked); 139 connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked);
137 layout_filter->setSpacing(10); 140 layout_filter->setSpacing(10);
138 layout_filter->addWidget(label_filter); 141 layout_filter->addWidget(label_filter);
139 layout_filter->addWidget(edit_filter); 142 layout_filter->addWidget(edit_filter);
@@ -159,16 +162,22 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput)
159} 162}
160 163
161// Syncs the expanded state of Game Directories with settings to persist across sessions 164// Syncs the expanded state of Game Directories with settings to persist across sessions
162void GameList::onItemExpanded(const QModelIndex& item) { 165void GameList::OnItemExpanded(const QModelIndex& item) {
163 const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>(); 166 const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>();
164 if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || 167 const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
165 type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir) 168 type == GameListItemType::UserNandDir ||
166 item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded = 169 type == GameListItemType::SysNandDir;
167 tree_view->isExpanded(item); 170
171 if (!is_dir) {
172 return;
173 }
174
175 auto* game_dir = item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
176 game_dir->expanded = tree_view->isExpanded(item);
168} 177}
169 178
170// Event in order to filter the gamelist after editing the searchfield 179// Event in order to filter the gamelist after editing the searchfield
171void GameList::onTextChanged(const QString& new_text) { 180void GameList::OnTextChanged(const QString& new_text) {
172 const int folder_count = tree_view->model()->rowCount(); 181 const int folder_count = tree_view->model()->rowCount();
173 QString edit_filter_text = new_text.toLower(); 182 QString edit_filter_text = new_text.toLower();
174 QStandardItem* folder; 183 QStandardItem* folder;
@@ -224,7 +233,7 @@ void GameList::onTextChanged(const QString& new_text) {
224 } 233 }
225} 234}
226 235
227void GameList::onUpdateThemedIcons() { 236void GameList::OnUpdateThemedIcons() {
228 for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { 237 for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
229 QStandardItem* child = item_model->invisibleRootItem()->child(i); 238 QStandardItem* child = item_model->invisibleRootItem()->child(i);
230 239
@@ -276,7 +285,7 @@ void GameList::onUpdateThemedIcons() {
276 } 285 }
277} 286}
278 287
279void GameList::onFilterCloseClicked() { 288void GameList::OnFilterCloseClicked() {
280 main_window->filterBarSetChecked(false); 289 main_window->filterBarSetChecked(false);
281} 290}
282 291
@@ -317,11 +326,11 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
317 } 326 }
318 item_model->setSortRole(GameListItemPath::SortRole); 327 item_model->setSortRole(GameListItemPath::SortRole);
319 328
320 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons); 329 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
321 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); 330 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
322 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); 331 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
323 connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded); 332 connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
324 connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded); 333 connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
325 334
326 // We must register all custom types with the Qt Automoc system so that we are able to use 335 // We must register all custom types with the Qt Automoc system so that we are able to use
327 // it with signals/slots. In this case, QList falls under the umbrells of custom types. 336 // it with signals/slots. In this case, QList falls under the umbrells of custom types.
@@ -338,17 +347,17 @@ GameList::~GameList() {
338 emit ShouldCancelWorker(); 347 emit ShouldCancelWorker();
339} 348}
340 349
341void GameList::setFilterFocus() { 350void GameList::SetFilterFocus() {
342 if (tree_view->model()->rowCount() > 0) { 351 if (tree_view->model()->rowCount() > 0) {
343 search_field->setFocus(); 352 search_field->setFocus();
344 } 353 }
345} 354}
346 355
347void GameList::setFilterVisible(bool visibility) { 356void GameList::SetFilterVisible(bool visibility) {
348 search_field->setVisible(visibility); 357 search_field->setVisible(visibility);
349} 358}
350 359
351void GameList::clearFilter() { 360void GameList::ClearFilter() {
352 search_field->clear(); 361 search_field->clear();
353} 362}
354 363
@@ -397,10 +406,11 @@ void GameList::ValidateEntry(const QModelIndex& item) {
397 } 406 }
398} 407}
399 408
400bool GameList::isEmpty() const { 409bool GameList::IsEmpty() const {
401 for (int i = 0; i < item_model->rowCount(); i++) { 410 for (int i = 0; i < item_model->rowCount(); i++) {
402 const QStandardItem* child = item_model->invisibleRootItem()->child(i); 411 const QStandardItem* child = item_model->invisibleRootItem()->child(i);
403 const auto type = static_cast<GameListItemType>(child->type()); 412 const auto type = static_cast<GameListItemType>(child->type());
413
404 if (!child->hasChildren() && 414 if (!child->hasChildren() &&
405 (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir || 415 (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir ||
406 type == GameListItemType::SysNandDir)) { 416 type == GameListItemType::SysNandDir)) {
@@ -408,11 +418,12 @@ bool GameList::isEmpty() const {
408 i--; 418 i--;
409 } 419 }
410 } 420 }
421
411 return !item_model->invisibleRootItem()->hasChildren(); 422 return !item_model->invisibleRootItem()->hasChildren();
412} 423}
413 424
414void GameList::DonePopulating(QStringList watch_list) { 425void GameList::DonePopulating(const QStringList& watch_list) {
415 emit ShowList(!isEmpty()); 426 emit ShowList(!IsEmpty());
416 427
417 item_model->invisibleRootItem()->appendRow(new GameListAddDir()); 428 item_model->invisibleRootItem()->appendRow(new GameListAddDir());
418 429
@@ -472,7 +483,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
472 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); 483 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
473} 484}
474 485
475void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) { 486void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) {
476 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); 487 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
477 QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); 488 QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location"));
478 QAction* open_transferable_shader_cache = 489 QAction* open_transferable_shader_cache =
@@ -690,12 +701,15 @@ void GameList::SaveInterfaceLayout() {
690} 701}
691 702
692void GameList::LoadInterfaceLayout() { 703void GameList::LoadInterfaceLayout() {
693 auto header = tree_view->header(); 704 auto* header = tree_view->header();
694 if (!header->restoreState(UISettings::values.gamelist_header_state)) { 705
695 // We are using the name column to display icons and titles 706 if (header->restoreState(UISettings::values.gamelist_header_state)) {
696 // so make it as large as possible as default. 707 return;
697 header->resizeSection(COLUMN_NAME, header->width());
698 } 708 }
709
710 // We are using the name column to display icons and titles
711 // so make it as large as possible as default.
712 header->resizeSection(COLUMN_NAME, header->width());
699} 713}
700 714
701const QStringList GameList::supported_file_extensions = { 715const QStringList GameList::supported_file_extensions = {
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 78e2ba169..58059a3c4 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -67,11 +67,11 @@ public:
67 FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr); 67 FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
68 ~GameList() override; 68 ~GameList() override;
69 69
70 QString getLastFilterResultItem() const; 70 QString GetLastFilterResultItem() const;
71 void clearFilter(); 71 void ClearFilter();
72 void setFilterFocus(); 72 void SetFilterFocus();
73 void setFilterVisible(bool visibility); 73 void SetFilterVisible(bool visibility);
74 bool isEmpty() const; 74 bool IsEmpty() const;
75 75
76 void LoadCompatibilityList(); 76 void LoadCompatibilityList();
77 void PopulateAsync(QVector<UISettings::GameDir>& game_dirs); 77 void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
@@ -82,7 +82,7 @@ public:
82 static const QStringList supported_file_extensions; 82 static const QStringList supported_file_extensions;
83 83
84signals: 84signals:
85 void GameChosen(QString game_path); 85 void GameChosen(const QString& game_path);
86 void ShouldCancelWorker(); 86 void ShouldCancelWorker();
87 void OpenFolderRequested(u64 program_id, GameListOpenTarget target, 87 void OpenFolderRequested(u64 program_id, GameListOpenTarget target,
88 const std::string& game_path); 88 const std::string& game_path);
@@ -99,21 +99,21 @@ signals:
99 void ShowList(bool show); 99 void ShowList(bool show);
100 100
101private slots: 101private slots:
102 void onItemExpanded(const QModelIndex& item); 102 void OnItemExpanded(const QModelIndex& item);
103 void onTextChanged(const QString& new_text); 103 void OnTextChanged(const QString& new_text);
104 void onFilterCloseClicked(); 104 void OnFilterCloseClicked();
105 void onUpdateThemedIcons(); 105 void OnUpdateThemedIcons();
106 106
107private: 107private:
108 void AddDirEntry(GameListDir* entry_items); 108 void AddDirEntry(GameListDir* entry_items);
109 void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); 109 void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
110 void ValidateEntry(const QModelIndex& item); 110 void ValidateEntry(const QModelIndex& item);
111 void DonePopulating(QStringList watch_list); 111 void DonePopulating(const QStringList& watch_list);
112 112
113 void RefreshGameDirectory(); 113 void RefreshGameDirectory();
114 114
115 void PopupContextMenu(const QPoint& menu_location); 115 void PopupContextMenu(const QPoint& menu_location);
116 void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path); 116 void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path);
117 void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); 117 void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
118 void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); 118 void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
119 119
diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h
index e4aba1b06..68e03fe4e 100644
--- a/src/yuzu/install_dialog.h
+++ b/src/yuzu/install_dialog.h
@@ -20,9 +20,8 @@ public:
20 explicit InstallDialog(QWidget* parent, const QStringList& files); 20 explicit InstallDialog(QWidget* parent, const QStringList& files);
21 ~InstallDialog() override; 21 ~InstallDialog() override;
22 22
23 QStringList GetFiles() const; 23 [[nodiscard]] QStringList GetFiles() const;
24 bool ShouldOverwriteFiles() const; 24 [[nodiscard]] int GetMinimumWidth() const;
25 int GetMinimumWidth() const;
26 25
27private: 26private:
28 QListWidget* file_list; 27 QListWidget* file_list;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index bb3a08ac7..6a2a88dd8 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -838,7 +838,7 @@ void GMainWindow::RestoreUIState() {
838 OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); 838 OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked());
839 839
840 ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar); 840 ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar);
841 game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); 841 game_list->SetFilterVisible(ui.action_Show_Filter_Bar->isChecked());
842 842
843 ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); 843 ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar);
844 statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); 844 statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked());
@@ -1199,11 +1199,12 @@ void GMainWindow::ShutdownGame() {
1199 render_window->hide(); 1199 render_window->hide();
1200 loading_screen->hide(); 1200 loading_screen->hide();
1201 loading_screen->Clear(); 1201 loading_screen->Clear();
1202 if (game_list->isEmpty()) 1202 if (game_list->IsEmpty()) {
1203 game_list_placeholder->show(); 1203 game_list_placeholder->show();
1204 else 1204 } else {
1205 game_list->show(); 1205 game_list->show();
1206 game_list->setFilterFocus(); 1206 }
1207 game_list->SetFilterFocus();
1207 1208
1208 setMouseTracking(false); 1209 setMouseTracking(false);
1209 ui.centralwidget->setMouseTracking(false); 1210 ui.centralwidget->setMouseTracking(false);
@@ -2361,11 +2362,11 @@ void GMainWindow::OnAbout() {
2361} 2362}
2362 2363
2363void GMainWindow::OnToggleFilterBar() { 2364void GMainWindow::OnToggleFilterBar() {
2364 game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); 2365 game_list->SetFilterVisible(ui.action_Show_Filter_Bar->isChecked());
2365 if (ui.action_Show_Filter_Bar->isChecked()) { 2366 if (ui.action_Show_Filter_Bar->isChecked()) {
2366 game_list->setFilterFocus(); 2367 game_list->SetFilterFocus();
2367 } else { 2368 } else {
2368 game_list->clearFilter(); 2369 game_list->ClearFilter();
2369 } 2370 }
2370} 2371}
2371 2372
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index e9f1c6500..23448e747 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -290,6 +290,8 @@ void Config::ReadValues() {
290 290
291 Settings::values.vibration_enabled = 291 Settings::values.vibration_enabled =
292 sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true); 292 sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true);
293 Settings::values.motion_enabled =
294 sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true);
293 Settings::values.touchscreen.enabled = 295 Settings::values.touchscreen.enabled =
294 sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); 296 sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
295 Settings::values.touchscreen.device = 297 Settings::values.touchscreen.device =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index a804d5185..521209622 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -13,9 +13,8 @@
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 15
16EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen, 16EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_)
17 InputCommon::InputSubsystem* input_subsystem_) 17 : input_subsystem{input_subsystem_} {
18 : system{system}, input_subsystem{input_subsystem_} {
19 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { 18 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
20 LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); 19 LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
21 exit(1); 20 exit(1);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index 82750ffec..53d756c3c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -20,8 +20,7 @@ class InputSubsystem;
20 20
21class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { 21class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
22public: 22public:
23 explicit EmuWindow_SDL2(Core::System& system, bool fullscreen, 23 explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem);
24 InputCommon::InputSubsystem* input_subsystem);
25 ~EmuWindow_SDL2(); 24 ~EmuWindow_SDL2();
26 25
27 /// Polls window events 26 /// Polls window events
@@ -33,9 +32,6 @@ public:
33 /// Returns if window is shown (not minimized) 32 /// Returns if window is shown (not minimized)
34 bool IsShown() const override; 33 bool IsShown() const override;
35 34
36 /// Presents the next frame
37 virtual void Present() = 0;
38
39protected: 35protected:
40 /// Called by PollEvents when a key is pressed or released. 36 /// Called by PollEvents when a key is pressed or released.
41 void OnKeyEvent(int key, u8 state); 37 void OnKeyEvent(int key, u8 state);
@@ -67,9 +63,6 @@ protected:
67 /// Called when a configuration change affects the minimal size of the window 63 /// Called when a configuration change affects the minimal size of the window
68 void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override; 64 void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override;
69 65
70 /// Instance of the system, used to access renderer for the presentation thread
71 Core::System& system;
72
73 /// Is the window still open? 66 /// Is the window still open?
74 bool is_open = true; 67 bool is_open = true;
75 68
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 881b67a76..5f35233b5 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -87,9 +87,8 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
87 return unsupported_ext.empty(); 87 return unsupported_ext.empty();
88} 88}
89 89
90EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen, 90EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen)
91 InputCommon::InputSubsystem* input_subsystem) 91 : EmuWindow_SDL2{input_subsystem} {
92 : EmuWindow_SDL2{system, fullscreen, input_subsystem} {
93 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); 92 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
94 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); 93 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
95 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); 94 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
@@ -163,13 +162,3 @@ EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
163std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { 162std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
164 return std::make_unique<SDLGLContext>(); 163 return std::make_unique<SDLGLContext>();
165} 164}
166
167void EmuWindow_SDL2_GL::Present() {
168 SDL_GL_MakeCurrent(render_window, window_context);
169 SDL_GL_SetSwapInterval(Settings::values.use_vsync.GetValue() ? 1 : 0);
170 while (IsOpen()) {
171 system.Renderer().TryPresent(100);
172 SDL_GL_SwapWindow(render_window);
173 }
174 SDL_GL_MakeCurrent(render_window, nullptr);
175}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index 732a64edd..dba5c293c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -14,12 +14,9 @@ class InputSubsystem;
14 14
15class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { 15class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
16public: 16public:
17 explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen, 17 explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen);
18 InputCommon::InputSubsystem* input_subsystem);
19 ~EmuWindow_SDL2_GL(); 18 ~EmuWindow_SDL2_GL();
20 19
21 void Present() override;
22
23 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; 20 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
24 21
25private: 22private:
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 53491f86e..3ba657c00 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -19,9 +19,8 @@
19#include <SDL.h> 19#include <SDL.h>
20#include <SDL_syswm.h> 20#include <SDL_syswm.h>
21 21
22EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen, 22EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem)
23 InputCommon::InputSubsystem* input_subsystem) 23 : EmuWindow_SDL2{input_subsystem} {
24 : EmuWindow_SDL2{system, fullscreen, input_subsystem} {
25 const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name, 24 const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
26 Common::g_scm_branch, Common::g_scm_desc); 25 Common::g_scm_branch, Common::g_scm_desc);
27 render_window = 26 render_window =
@@ -74,7 +73,3 @@ EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default;
74std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const { 73std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_VK::CreateSharedContext() const {
75 return std::make_unique<DummyContext>(); 74 return std::make_unique<DummyContext>();
76} 75}
77
78void EmuWindow_SDL2_VK::Present() {
79 // TODO (bunnei): ImplementMe
80}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
index f99704d4c..bdfdc3c6f 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -19,11 +19,8 @@ class InputSubsystem;
19 19
20class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { 20class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
21public: 21public:
22 explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen, 22 explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem);
23 InputCommon::InputSubsystem* input_subsystem); 23 ~EmuWindow_SDL2_VK() override;
24 ~EmuWindow_SDL2_VK();
25
26 void Present() override;
27 24
28 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; 25 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
29}; 26};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index e960b5413..3a76c785f 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -185,11 +185,11 @@ int main(int argc, char** argv) {
185 std::unique_ptr<EmuWindow_SDL2> emu_window; 185 std::unique_ptr<EmuWindow_SDL2> emu_window;
186 switch (Settings::values.renderer_backend.GetValue()) { 186 switch (Settings::values.renderer_backend.GetValue()) {
187 case Settings::RendererBackend::OpenGL: 187 case Settings::RendererBackend::OpenGL:
188 emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, &input_subsystem); 188 emu_window = std::make_unique<EmuWindow_SDL2_GL>(&input_subsystem, fullscreen);
189 break; 189 break;
190 case Settings::RendererBackend::Vulkan: 190 case Settings::RendererBackend::Vulkan:
191#ifdef HAS_VULKAN 191#ifdef HAS_VULKAN
192 emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, &input_subsystem); 192 emu_window = std::make_unique<EmuWindow_SDL2_VK>(&input_subsystem);
193 break; 193 break;
194#else 194#else
195 LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!"); 195 LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
@@ -240,14 +240,11 @@ int main(int argc, char** argv) {
240 system.CurrentProcess()->GetTitleID(), false, 240 system.CurrentProcess()->GetTitleID(), false,
241 [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); 241 [](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
242 242
243 std::thread render_thread([&emu_window] { emu_window->Present(); });
244 system.Run(); 243 system.Run();
245 while (emu_window->IsOpen()) { 244 while (emu_window->IsOpen()) {
246 std::this_thread::sleep_for(std::chrono::milliseconds(1)); 245 std::this_thread::sleep_for(std::chrono::milliseconds(1));
247 } 246 }
248 system.Pause(); 247 system.Pause();
249 render_thread.join();
250
251 system.Shutdown(); 248 system.Shutdown();
252 249
253 detached_tasks.WaitForAllTasks(); 250 detached_tasks.WaitForAllTasks();
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index aaf59129a..bc273fb51 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -76,6 +76,7 @@ void Config::ReadValues() {
76 } 76 }
77 77
78 Settings::values.vibration_enabled = true; 78 Settings::values.vibration_enabled = true;
79 Settings::values.motion_enabled = true;
79 Settings::values.touchscreen.enabled = ""; 80 Settings::values.touchscreen.enabled = "";
80 Settings::values.touchscreen.device = ""; 81 Settings::values.touchscreen.device = "";
81 Settings::values.touchscreen.finger = 0; 82 Settings::values.touchscreen.finger = 0;