summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/CMakeLists.txt18
-rw-r--r--src/core/core.cpp3
-rw-r--r--src/core/file_sys/card_image.cpp10
-rw-r--r--src/core/file_sys/card_image.h2
-rw-r--r--src/core/file_sys/savedata_factory.cpp13
-rw-r--r--src/core/hle/kernel/svc.cpp21
-rw-r--r--src/core/hle/kernel/svc.h4
-rw-r--r--src/core/hle/kernel/vm_manager.cpp35
-rw-r--r--src/core/hle/kernel/vm_manager.h12
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.cpp28
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.h45
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp42
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h55
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp43
-rw-r--r--src/core/hle/service/hid/controllers/gesture.h62
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp43
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.h49
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp43
-rw-r--r--src/core/hle/service/hid/controllers/mouse.h49
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp429
-rw-r--r--src/core/hle/service/hid/controllers/npad.h288
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.cpp39
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.h33
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp65
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h62
-rw-r--r--src/core/hle/service/hid/controllers/xpad.cpp45
-rw-r--r--src/core/hle/service/hid/controllers/xpad.h60
-rw-r--r--src/core/hle/service/hid/hid.cpp466
-rw-r--r--src/core/hle/service/hid/hid.h402
-rw-r--r--src/core/hle/service/mm/mm_u.cpp50
-rw-r--r--src/core/hle/service/nfp/nfp.cpp2
-rw-r--r--src/core/loader/xci.cpp3
-rw-r--r--src/core/settings.h2
-rw-r--r--src/core/telemetry_session.cpp4
-rw-r--r--src/video_core/engines/maxwell_3d.h5
-rw-r--r--src/video_core/memory_manager.cpp10
-rw-r--r--src/video_core/memory_manager.h1
-rw-r--r--src/video_core/rasterizer_cache.h127
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h9
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp28
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp328
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h84
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h11
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp76
-rw-r--r--src/yuzu/configuration/config.cpp6
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp4
-rw-r--r--src/yuzu/configuration/configure_graphics.ui4
-rw-r--r--src/yuzu_cmd/config.cpp4
-rw-r--r--src/yuzu_cmd/default_ini.h4
49 files changed, 2329 insertions, 899 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 78986deb5..4755ec822 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -236,6 +236,24 @@ add_library(core STATIC
hle/service/hid/irs.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
+ hle/service/hid/controllers/controller_base.cpp
+ hle/service/hid/controllers/controller_base.h
+ hle/service/hid/controllers/debug_pad.cpp
+ hle/service/hid/controllers/debug_pad.h
+ hle/service/hid/controllers/gesture.cpp
+ hle/service/hid/controllers/gesture.h
+ hle/service/hid/controllers/keyboard.cpp
+ hle/service/hid/controllers/keyboard.h
+ hle/service/hid/controllers/mouse.cpp
+ hle/service/hid/controllers/mouse.h
+ hle/service/hid/controllers/npad.cpp
+ hle/service/hid/controllers/npad.h
+ hle/service/hid/controllers/stubbed.cpp
+ hle/service/hid/controllers/stubbed.h
+ hle/service/hid/controllers/touchscreen.cpp
+ hle/service/hid/controllers/touchscreen.h
+ hle/service/hid/controllers/xpad.cpp
+ hle/service/hid/controllers/xpad.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
hle/service/ldn/ldn.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3c57a62ec..7cb86ed92 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -375,8 +375,7 @@ const Kernel::Process* System::CurrentProcess() const {
}
ARM_Interface& System::ArmInterface(std::size_t core_index) {
- ASSERT(core_index < NUM_CPU_CORES);
- return impl->cpu_cores[core_index]->ArmInterface();
+ return CpuCore(core_index).ArmInterface();
}
Cpu& System::CpuCore(std::size_t core_index) {
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 8f5142a07..ecdd7505b 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -122,14 +122,16 @@ u64 XCI::GetProgramTitleID() const {
return secure_partition->GetProgramTitleID();
}
-std::shared_ptr<NCA> XCI::GetProgramNCA() const {
- return program;
+bool XCI::HasProgramNCA() const {
+ return program != nullptr;
}
VirtualFile XCI::GetProgramNCAFile() const {
- if (GetProgramNCA() == nullptr)
+ if (!HasProgramNCA()) {
return nullptr;
- return GetProgramNCA()->GetBaseFile();
+ }
+
+ return program->GetBaseFile();
}
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index ce514dfa0..48cbef666 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -80,7 +80,7 @@ public:
u64 GetProgramTitleID() const;
- std::shared_ptr<NCA> GetProgramNCA() const;
+ bool HasProgramNCA() const;
VirtualFile GetProgramNCAFile() const;
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 47f2ab9e0..ef1aaebbb 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -51,6 +51,13 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
meta.title_id);
}
+ if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) {
+ LOG_WARNING(Service_FS,
+ "Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is "
+ "non-zero ({:016X}{:016X})",
+ meta.user_id[1], meta.user_id[0]);
+ }
+
std::string save_directory =
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -92,6 +99,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataSpaceId::NandUser:
out = "/user/";
break;
+ case SaveDataSpaceId::TemporaryStorage:
+ out = "/temp/";
+ break;
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
}
@@ -100,10 +110,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataType::SystemSaveData:
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
case SaveDataType::SaveData:
+ case SaveDataType::DeviceSaveData:
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
case SaveDataType::TemporaryStorage:
- return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
+ return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 3e5f11f2b..d08b84bde 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -448,25 +448,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
case GetInfoType::RandomEntropy:
*result = 0;
break;
- case GetInfoType::AddressSpaceBaseAddr:
- *result = vm_manager.GetCodeRegionBaseAddress();
+ case GetInfoType::ASLRRegionBaseAddr:
+ *result = vm_manager.GetASLRRegionBaseAddress();
break;
- case GetInfoType::AddressSpaceSize: {
- const u64 width = vm_manager.GetAddressSpaceWidth();
-
- switch (width) {
- case 32:
- *result = 0xFFE00000;
- break;
- case 36:
- *result = 0xFF8000000;
- break;
- case 39:
- *result = 0x7FF8000000;
- break;
- }
+ case GetInfoType::ASLRRegionSize:
+ *result = vm_manager.GetASLRRegionSize();
break;
- }
case GetInfoType::NewMapRegionBaseAddr:
*result = vm_manager.GetNewMapRegionBaseAddress();
break;
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index 70148c4fe..554a5e328 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -41,8 +41,8 @@ enum class GetInfoType : u64 {
RandomEntropy = 11,
PerformanceCounter = 0xF0000002,
// 2.0.0+
- AddressSpaceBaseAddr = 12,
- AddressSpaceSize = 13,
+ ASLRRegionBaseAddr = 12,
+ ASLRRegionSize = 13,
NewMapRegionBaseAddr = 14,
NewMapRegionSize = 15,
// 3.0.0+
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index e412309fd..1e28ccbda 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -393,30 +393,35 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
switch (type) {
case FileSys::ProgramAddressSpaceType::Is32Bit:
+ case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
address_space_width = 32;
code_region_base = 0x200000;
code_region_end = code_region_base + 0x3FE00000;
- map_region_size = 0x40000000;
- heap_region_size = 0x40000000;
+ aslr_region_base = 0x200000;
+ aslr_region_end = aslr_region_base + 0xFFE00000;
+ if (type == FileSys::ProgramAddressSpaceType::Is32Bit) {
+ map_region_size = 0x40000000;
+ heap_region_size = 0x40000000;
+ } else {
+ map_region_size = 0;
+ heap_region_size = 0x80000000;
+ }
break;
case FileSys::ProgramAddressSpaceType::Is36Bit:
address_space_width = 36;
code_region_base = 0x8000000;
code_region_end = code_region_base + 0x78000000;
+ aslr_region_base = 0x8000000;
+ aslr_region_end = aslr_region_base + 0xFF8000000;
map_region_size = 0x180000000;
heap_region_size = 0x180000000;
break;
- case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
- address_space_width = 32;
- code_region_base = 0x200000;
- code_region_end = code_region_base + 0x3FE00000;
- map_region_size = 0;
- heap_region_size = 0x80000000;
- break;
case FileSys::ProgramAddressSpaceType::Is39Bit:
address_space_width = 39;
code_region_base = 0x8000000;
code_region_end = code_region_base + 0x80000000;
+ aslr_region_base = 0x8000000;
+ aslr_region_end = aslr_region_base + 0x7FF8000000;
map_region_size = 0x1000000000;
heap_region_size = 0x180000000;
new_map_region_size = 0x80000000;
@@ -490,6 +495,18 @@ u64 VMManager::GetAddressSpaceWidth() const {
return address_space_width;
}
+VAddr VMManager::GetASLRRegionBaseAddress() const {
+ return aslr_region_base;
+}
+
+VAddr VMManager::GetASLRRegionEndAddress() const {
+ return aslr_region_end;
+}
+
+u64 VMManager::GetASLRRegionSize() const {
+ return aslr_region_end - aslr_region_base;
+}
+
VAddr VMManager::GetCodeRegionBaseAddress() const {
return code_region_base;
}
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 015559a64..4accde6b3 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -205,6 +205,15 @@ public:
/// Gets the address space width in bits.
u64 GetAddressSpaceWidth() const;
+ /// Gets the base address of the ASLR region.
+ VAddr GetASLRRegionBaseAddress() const;
+
+ /// Gets the end address of the ASLR region.
+ VAddr GetASLRRegionEndAddress() const;
+
+ /// Gets the size of the ASLR region
+ u64 GetASLRRegionSize() const;
+
/// Gets the base address of the code region.
VAddr GetCodeRegionBaseAddress() const;
@@ -306,6 +315,9 @@ private:
VAddr address_space_base = 0;
VAddr address_space_end = 0;
+ VAddr aslr_region_base = 0;
+ VAddr aslr_region_end = 0;
+
VAddr code_region_base = 0;
VAddr code_region_end = 0;
diff --git a/src/core/hle/service/hid/controllers/controller_base.cpp b/src/core/hle/service/hid/controllers/controller_base.cpp
new file mode 100644
index 000000000..1625e9c3d
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/controller_base.cpp
@@ -0,0 +1,28 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+ControllerBase::~ControllerBase() = default;
+
+void ControllerBase::ActivateController() {
+ if (is_activated) {
+ OnRelease();
+ }
+ is_activated = true;
+ OnInit();
+}
+
+void ControllerBase::DeactivateController() {
+ if (is_activated) {
+ OnRelease();
+ }
+ is_activated = false;
+}
+
+bool ControllerBase::IsControllerActivated() const {
+ return is_activated;
+}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h
new file mode 100644
index 000000000..fa98e2354
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/controller_base.h
@@ -0,0 +1,45 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace Service::HID {
+class ControllerBase {
+public:
+ ControllerBase() = default;
+ virtual ~ControllerBase() = 0;
+
+ // Called when the controller is initialized
+ virtual void OnInit() = 0;
+
+ // When the controller is released
+ virtual void OnRelease() = 0;
+
+ // When the controller is requesting an update for the shared memory
+ virtual void OnUpdate(u8* data, std::size_t size) = 0;
+
+ // Called when input devices should be loaded
+ virtual void OnLoadInputDevices() = 0;
+
+ void ActivateController();
+
+ void DeactivateController();
+
+ bool IsControllerActivated() const;
+
+protected:
+ bool is_activated{false};
+
+ struct CommonHeader {
+ s64_le timestamp;
+ s64_le total_entry_count;
+ s64_le last_entry_index;
+ s64_le entry_count;
+ };
+ static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
new file mode 100644
index 000000000..6f8ef6e3f
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/debug_pad.cpp
@@ -0,0 +1,42 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/debug_pad.h"
+
+namespace Service::HID {
+
+Controller_DebugPad::Controller_DebugPad() = default;
+
+void Controller_DebugPad::OnInit() {}
+
+void Controller_DebugPad::OnRelease() {}
+
+void Controller_DebugPad::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ // TODO(ogniK): Update debug pad states
+
+ std::memcpy(data, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_DebugPad::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h
new file mode 100644
index 000000000..e35675fa1
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -0,0 +1,55 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_DebugPad final : public ControllerBase {
+public:
+ Controller_DebugPad();
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct AnalogStick {
+ s32_le x;
+ s32_le y;
+ };
+ static_assert(sizeof(AnalogStick) == 0x8);
+
+ struct PadStates {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+ u32_le attribute;
+ u32_le button_state;
+ AnalogStick r_stick;
+ AnalogStick l_stick;
+ };
+ static_assert(sizeof(PadStates) == 0x28, "PadStates is an invalid state");
+
+ struct SharedMemory {
+ CommonHeader header;
+ std::array<PadStates, 17> pad_states;
+ INSERT_PADDING_BYTES(0x138);
+ };
+ static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
new file mode 100644
index 000000000..b473b9e2b
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -0,0 +1,43 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/gesture.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00;
+
+Controller_Gesture::Controller_Gesture() = default;
+
+void Controller_Gesture::OnInit() {}
+
+void Controller_Gesture::OnRelease() {}
+
+void Controller_Gesture::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ // TODO(ogniK): Update gesture states
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_Gesture::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h
new file mode 100644
index 000000000..0ced50dfd
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/gesture.h
@@ -0,0 +1,62 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Gesture final : public ControllerBase {
+public:
+ Controller_Gesture();
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct Locations {
+ s32_le x;
+ s32_le y;
+ };
+
+ struct GestureState {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+
+ s64_le detection_count;
+ s32_le type;
+ s32_le dir;
+ s32_le x;
+ s32_le y;
+ s32_le delta_x;
+ s32_le delta_y;
+ f32 vel_x;
+ f32 vel_y;
+ s32_le attributes;
+ f32 scale;
+ f32 rotation;
+ s32_le location_count;
+ std::array<Locations, 4> locations;
+ };
+ static_assert(sizeof(GestureState) == 0x68, "GestureState is an invalid size");
+
+ struct SharedMemory {
+ CommonHeader header;
+ std::array<GestureState, 17> gesture_states;
+ };
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
new file mode 100644
index 000000000..089c02ac4
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -0,0 +1,43 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/keyboard.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
+
+Controller_Keyboard::Controller_Keyboard() = default;
+
+void Controller_Keyboard::OnInit() {}
+
+void Controller_Keyboard::OnRelease() {}
+
+void Controller_Keyboard::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ // TODO(ogniK): Update keyboard states
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_Keyboard::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h
new file mode 100644
index 000000000..778e14f7e
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/keyboard.h
@@ -0,0 +1,49 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Keyboard final : public ControllerBase {
+public:
+ Controller_Keyboard();
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct KeyboardState {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+
+ s32_le modifier;
+ s32_le attribute;
+ std::array<u8, 32> key;
+ };
+ static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size");
+
+ struct SharedMemory {
+ CommonHeader header;
+ std::array<KeyboardState, 17> pad_states;
+ INSERT_PADDING_BYTES(0x28);
+ };
+ static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp
new file mode 100644
index 000000000..78e9b5e9e
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/mouse.cpp
@@ -0,0 +1,43 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/mouse.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
+
+Controller_Mouse::Controller_Mouse() = default;
+
+void Controller_Mouse::OnInit() {}
+
+void Controller_Mouse::OnRelease() {}
+
+void Controller_Mouse::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ auto& last_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ // TODO(ogniK): Update mouse states
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_Mouse::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h
new file mode 100644
index 000000000..05358a4f5
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/mouse.h
@@ -0,0 +1,49 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Mouse final : public ControllerBase {
+public:
+ Controller_Mouse();
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct MouseState {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+ s32_le x;
+ s32_le y;
+ s32_le delta_x;
+ s32_le delta_y;
+ s32_le mouse_wheel;
+ s32_le button;
+ s32_le attribute;
+ };
+ static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size");
+
+ struct SharedMemory {
+ CommonHeader header;
+ std::array<MouseState, 17> mouse_states;
+ };
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
new file mode 100644
index 000000000..d17e64b2a
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -0,0 +1,429 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/swap.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/frontend/input.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/settings.h"
+
+namespace Service::HID {
+constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
+constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
+constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
+constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
+constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
+constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
+constexpr std::size_t NPAD_OFFSET = 0x9A00;
+constexpr u32 BATTERY_FULL = 2;
+enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
+
+Controller_NPad::Controller_NPad() = default;
+
+void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
+ const auto controller_type = connected_controllers[controller_idx].type;
+ auto& controller = shared_memory_entries[controller_idx];
+ if (controller_type == NPadControllerType::None) {
+ return;
+ }
+ controller.joy_styles.raw = 0; // Zero out
+ controller.device_type.raw = 0;
+ switch (controller_type) {
+ case NPadControllerType::Handheld:
+ controller.joy_styles.handheld.Assign(1);
+ controller.device_type.handheld.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
+ break;
+ case NPadControllerType::JoyDual:
+ controller.joy_styles.joycon_dual.Assign(1);
+ controller.device_type.joycon_left.Assign(1);
+ controller.device_type.joycon_right.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
+ break;
+ case NPadControllerType::JoyLeft:
+ controller.joy_styles.joycon_left.Assign(1);
+ controller.device_type.joycon_left.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
+ break;
+ case NPadControllerType::JoyRight:
+ controller.joy_styles.joycon_right.Assign(1);
+ controller.device_type.joycon_right.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
+ break;
+ case NPadControllerType::Pokeball:
+ controller.joy_styles.pokeball.Assign(1);
+ controller.device_type.pokeball.Assign(1);
+ controller.pad_assignment = NPadAssignments::Single;
+ break;
+ case NPadControllerType::ProController:
+ controller.joy_styles.pro_controller.Assign(1);
+ controller.device_type.pro_controller.Assign(1);
+ controller.pad_assignment = NPadAssignments::Single;
+ break;
+ }
+
+ controller.single_color_error = ColorReadError::ReadOk;
+ controller.single_color.body_color = 0;
+ controller.single_color.button_color = 0;
+
+ controller.dual_color_error = ColorReadError::ReadOk;
+ controller.left_color.body_color = JOYCON_BODY_NEON_BLUE;
+ controller.left_color.button_color = JOYCON_BUTTONS_NEON_BLUE;
+ controller.right_color.body_color = JOYCON_BODY_NEON_RED;
+ controller.right_color.button_color = JOYCON_BUTTONS_NEON_RED;
+
+ controller.properties.is_vertical.Assign(1); // TODO(ogniK): Swap joycons orientations
+ controller.properties.use_plus.Assign(1);
+ controller.properties.use_minus.Assign(1);
+ controller.battery_level[0] = BATTERY_FULL;
+ controller.battery_level[1] = BATTERY_FULL;
+ controller.battery_level[2] = BATTERY_FULL;
+}
+
+void Controller_NPad::OnInit() {
+ auto& kernel = Core::System::GetInstance().Kernel();
+ styleset_changed_event =
+ Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged");
+
+ if (!IsControllerActivated())
+ return;
+ std::size_t controller{};
+ if (style.raw == 0) {
+ // We want to support all controllers
+ style.handheld.Assign(1);
+ style.joycon_left.Assign(1);
+ style.joycon_right.Assign(1);
+ style.joycon_dual.Assign(1);
+ style.pro_controller.Assign(1);
+ style.pokeball.Assign(1);
+ }
+ if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
+ [](const ControllerHolder& controller) { return controller.is_connected; })) {
+ supported_npad_id_types.resize(npad_id_list.size());
+ std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
+ npad_id_list.size() * sizeof(u32));
+ AddNewController(NPadControllerType::JoyDual);
+ }
+}
+
+void Controller_NPad::OnLoadInputDevices() {
+ std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+ Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
+ buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
+ std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
+ Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
+ sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
+}
+
+void Controller_NPad::OnRelease() {}
+
+void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
+ if (!IsControllerActivated())
+ return;
+ for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
+ auto& npad = shared_memory_entries[i];
+ const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
+ &npad.handheld_states,
+ &npad.dual_states,
+ &npad.left_joy_states,
+ &npad.right_joy_states,
+ &npad.pokeball_states,
+ &npad.libnx};
+
+ for (auto* main_controller : controller_npads) {
+ main_controller->common.entry_count = 16;
+ main_controller->common.total_entry_count = 17;
+
+ const auto& last_entry =
+ main_controller->npad[main_controller->common.last_entry_index];
+
+ main_controller->common.timestamp = CoreTiming::GetTicks();
+ main_controller->common.last_entry_index =
+ (main_controller->common.last_entry_index + 1) % 17;
+
+ auto& cur_entry = main_controller->npad[main_controller->common.last_entry_index];
+
+ cur_entry.timestamp = last_entry.timestamp + 1;
+ cur_entry.timestamp2 = cur_entry.timestamp;
+ }
+
+ const auto& controller_type = connected_controllers[i].type;
+
+ if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
+ continue;
+ }
+
+ // Pad states
+ ControllerPadState pad_state{};
+ using namespace Settings::NativeButton;
+ pad_state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.l_stick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.r_stick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
+
+ AnalogPosition lstick_entry{};
+ AnalogPosition rstick_entry{};
+
+ const auto [stick_l_x_f, stick_l_y_f] =
+ sticks[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
+ const auto [stick_r_x_f, stick_r_y_f] =
+ sticks[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
+ lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
+ lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
+ rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
+ rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+
+ auto& main_controller =
+ npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index];
+ auto& handheld_entry =
+ npad.handheld_states.npad[npad.handheld_states.common.last_entry_index];
+ auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index];
+ auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index];
+ auto& right_entry =
+ npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index];
+ auto& pokeball_entry =
+ npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index];
+ auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index];
+
+ if (hold_type == NpadHoldType::Horizontal) {
+ // TODO(ogniK): Remap buttons for different orientations
+ }
+ libnx_entry.connection_status.raw = 0;
+
+ switch (controller_type) {
+ case NPadControllerType::Handheld:
+ handheld_entry.connection_status.raw = 0;
+ handheld_entry.connection_status.IsConnected.Assign(1);
+ if (!Settings::values.use_docked_mode) {
+ handheld_entry.connection_status.IsWired.Assign(1);
+ }
+ handheld_entry.pad_states.raw = pad_state.raw;
+ handheld_entry.l_stick = lstick_entry;
+ handheld_entry.r_stick = rstick_entry;
+ break;
+ case NPadControllerType::JoyDual:
+ dual_entry.connection_status.raw = 0;
+
+ dual_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ dual_entry.connection_status.IsRightJoyConnected.Assign(1);
+ dual_entry.connection_status.IsConnected.Assign(1);
+
+ libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsConnected.Assign(1);
+
+ dual_entry.pad_states.raw = pad_state.raw;
+ dual_entry.l_stick = lstick_entry;
+ dual_entry.r_stick = rstick_entry;
+ case NPadControllerType::JoyLeft:
+ left_entry.connection_status.raw = 0;
+
+ left_entry.connection_status.IsConnected.Assign(1);
+ left_entry.pad_states.raw = pad_state.raw;
+ left_entry.l_stick = lstick_entry;
+ left_entry.r_stick = rstick_entry;
+ break;
+ case NPadControllerType::JoyRight:
+ right_entry.connection_status.raw = 0;
+
+ right_entry.connection_status.IsConnected.Assign(1);
+ right_entry.pad_states.raw = pad_state.raw;
+ right_entry.l_stick = lstick_entry;
+ right_entry.r_stick = rstick_entry;
+ break;
+ case NPadControllerType::Pokeball:
+ pokeball_entry.connection_status.raw = 0;
+
+ pokeball_entry.connection_status.IsConnected.Assign(1);
+ pokeball_entry.connection_status.IsWired.Assign(1);
+
+ pokeball_entry.pad_states.raw = pad_state.raw;
+ pokeball_entry.l_stick = lstick_entry;
+ pokeball_entry.r_stick = rstick_entry;
+ break;
+ case NPadControllerType::ProController:
+ main_controller.connection_status.raw = 0;
+
+ main_controller.connection_status.IsConnected.Assign(1);
+ main_controller.connection_status.IsWired.Assign(1);
+ main_controller.pad_states.raw = pad_state.raw;
+ main_controller.l_stick = lstick_entry;
+ main_controller.r_stick = rstick_entry;
+ break;
+ }
+
+ // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate
+ // any controllers.
+ libnx_entry.pad_states.raw = pad_state.raw;
+ libnx_entry.l_stick = lstick_entry;
+ libnx_entry.r_stick = rstick_entry;
+ }
+ std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
+ shared_memory_entries.size() * sizeof(NPadEntry));
+} // namespace Service::HID
+
+void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
+ style.raw = style_set.raw;
+}
+
+Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const {
+ return style;
+}
+
+void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
+ ASSERT(length > 0 && (length % sizeof(u32)) == 0);
+ supported_npad_id_types.clear();
+ supported_npad_id_types.resize(length / sizeof(u32));
+ std::memcpy(supported_npad_id_types.data(), data, length);
+}
+
+const void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) {
+ ASSERT(max_length < supported_npad_id_types.size());
+ std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size());
+}
+
+std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
+ return supported_npad_id_types.size();
+}
+
+void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) {
+ hold_type = joy_hold_type;
+}
+Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
+ return hold_type;
+}
+
+void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
+ ASSERT(npad_id < shared_memory_entries.size());
+ shared_memory_entries[npad_id].pad_assignment = assignment_mode;
+}
+
+void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
+ const std::vector<Vibration>& vibrations) {
+ if (!can_controllers_vibrate) {
+ return;
+ }
+ for (std::size_t i = 0; i < controller_ids.size(); i++) {
+ std::size_t controller_pos = i;
+ // Handheld controller conversion
+ if (controller_pos == 32) {
+ controller_pos = 8;
+ }
+ // Unknown controller conversion
+ if (controller_pos == 16) {
+ controller_pos = 9;
+ }
+ if (connected_controllers[controller_pos].is_connected) {
+ // TODO(ogniK): Vibrate the physical controller
+ }
+ }
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ last_processed_vibration = vibrations.back();
+}
+
+Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const {
+ return styleset_changed_event;
+}
+
+Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
+ return last_processed_vibration;
+}
+void Controller_NPad::AddNewController(NPadControllerType controller) {
+ if (controller == NPadControllerType::Handheld) {
+ connected_controllers[8] = {controller, true};
+ InitNewlyAddedControler(8);
+ return;
+ }
+ const auto pos =
+ std::find_if(connected_controllers.begin(), connected_controllers.end() - 2,
+ [](const ControllerHolder& holder) { return !holder.is_connected; });
+ if (pos == connected_controllers.end() - 2) {
+ LOG_ERROR(Service_HID, "Cannot connect any more controllers!");
+ return;
+ }
+ const auto controller_id = std::distance(connected_controllers.begin(), pos);
+ connected_controllers[controller_id] = {controller, true};
+ InitNewlyAddedControler(controller_id);
+}
+
+void Controller_NPad::ConnectNPad(u32 npad_id) {
+ if (npad_id >= connected_controllers.size())
+ return;
+ connected_controllers[npad_id].is_connected = true;
+}
+
+void Controller_NPad::DisconnectNPad(u32 npad_id) {
+ if (npad_id >= connected_controllers.size())
+ return;
+ connected_controllers[npad_id].is_connected = false;
+}
+
+Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
+ if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) {
+ // These are controllers without led patterns
+ return LedPattern{0, 0, 0, 0};
+ }
+ switch (npad_id) {
+ case 0:
+ return LedPattern{1, 0, 0, 0};
+ case 1:
+ return LedPattern{0, 1, 0, 0};
+ case 2:
+ return LedPattern{0, 0, 1, 0};
+ case 3:
+ return LedPattern{0, 0, 0, 1};
+ case 4:
+ return LedPattern{1, 0, 0, 1};
+ case 5:
+ return LedPattern{1, 0, 1, 0};
+ case 6:
+ return LedPattern{1, 0, 1, 1};
+ case 7:
+ return LedPattern{0, 1, 1, 0};
+ default:
+ UNIMPLEMENTED_MSG("Unhandled npad_id {}", npad_id);
+ return LedPattern{0, 0, 0, 0};
+ };
+}
+void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
+ can_controllers_vibrate = can_vibrate;
+}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
new file mode 100644
index 000000000..9d07d258d
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -0,0 +1,288 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "core/frontend/input.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/settings.h"
+
+namespace Service::HID {
+
+class Controller_NPad final : public ControllerBase {
+public:
+ Controller_NPad();
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+ struct NPadType {
+ union {
+ u32_le raw{};
+
+ BitField<0, 1, u32_le> pro_controller;
+ BitField<1, 1, u32_le> handheld;
+ BitField<2, 1, u32_le> joycon_dual;
+ BitField<3, 1, u32_le> joycon_left;
+ BitField<4, 1, u32_le> joycon_right;
+
+ BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible
+ };
+ };
+ static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
+
+ struct Vibration {
+ f32 amp_low;
+ f32 freq_low;
+ f32 amp_high;
+ f32 freq_high;
+ };
+ static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size");
+
+ enum class NpadHoldType : u64 {
+ Vertical = 0,
+ Horizontal = 1,
+ };
+
+ enum class NPadAssignments : u32_le {
+ Dual = 0,
+ Single = 1,
+ };
+
+ enum class NPadControllerType {
+ None,
+ ProController,
+ Handheld,
+ JoyDual,
+ JoyLeft,
+ JoyRight,
+ Pokeball,
+ };
+
+ struct LedPattern {
+ explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
+ position1.Assign(light1);
+ position1.Assign(light2);
+ position1.Assign(light3);
+ position1.Assign(light4);
+ };
+ union {
+ u64 raw{};
+ BitField<0, 1, u64> position1;
+ BitField<1, 1, u64> position2;
+ BitField<2, 1, u64> position3;
+ BitField<3, 1, u64> position4;
+ };
+ };
+
+ void SetSupportedStyleSet(NPadType style_set);
+ NPadType GetSupportedStyleSet() const;
+
+ void SetSupportedNPadIdTypes(u8* data, std::size_t length);
+ const void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
+ std::size_t GetSupportedNPadIdTypesSize() const;
+
+ void SetHoldType(NpadHoldType joy_hold_type);
+ NpadHoldType GetHoldType() const;
+
+ void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
+
+ void VibrateController(const std::vector<u32>& controller_ids,
+ const std::vector<Vibration>& vibrations);
+
+ Kernel::SharedPtr<Kernel::Event> GetStyleSetChangedEvent() const;
+ Vibration GetLastVibration() const;
+
+ void AddNewController(NPadControllerType controller);
+
+ void ConnectNPad(u32 npad_id);
+ void DisconnectNPad(u32 npad_id);
+ LedPattern GetLedPattern(u32 npad_id);
+ void SetVibrationEnabled(bool can_vibrate);
+
+private:
+ struct CommonHeader {
+ s64_le timestamp;
+ s64_le total_entry_count;
+ s64_le last_entry_index;
+ s64_le entry_count;
+ };
+ static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
+
+ struct ControllerColor {
+ u32_le body_color;
+ u32_le button_color;
+ };
+ static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size");
+
+ struct ControllerPadState {
+ union {
+ u64_le raw{};
+ // Button states
+ BitField<0, 1, u64_le> a;
+ BitField<1, 1, u64_le> b;
+ BitField<2, 1, u64_le> x;
+ BitField<3, 1, u64_le> y;
+ BitField<4, 1, u64_le> l_stick;
+ BitField<5, 1, u64_le> r_stick;
+ BitField<6, 1, u64_le> l;
+ BitField<7, 1, u64_le> r;
+ BitField<8, 1, u64_le> zl;
+ BitField<9, 1, u64_le> zr;
+ BitField<10, 1, u64_le> plus;
+ BitField<11, 1, u64_le> minus;
+
+ // D-Pad
+ BitField<12, 1, u64_le> d_left;
+ BitField<13, 1, u64_le> d_up;
+ BitField<14, 1, u64_le> d_right;
+ BitField<15, 1, u64_le> d_down;
+
+ // Left JoyStick
+ BitField<16, 1, u64_le> l_stick_left;
+ BitField<17, 1, u64_le> l_stick_up;
+ BitField<18, 1, u64_le> l_stick_right;
+ BitField<19, 1, u64_le> l_stick_down;
+
+ // Right JoyStick
+ BitField<20, 1, u64_le> r_stick_left;
+ BitField<21, 1, u64_le> r_stick_up;
+ BitField<22, 1, u64_le> r_stick_right;
+ BitField<23, 1, u64_le> r_stick_down;
+
+ // Not always active?
+ BitField<24, 1, u64_le> sl;
+ BitField<25, 1, u64_le> sr;
+ };
+ };
+ static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
+
+ struct AnalogPosition {
+ s32_le x;
+ s32_le y;
+ };
+ static_assert(sizeof(AnalogPosition) == 8, "AnalogPosition is an invalid size");
+
+ struct ConnectionState {
+ union {
+ u32_le raw{};
+ BitField<0, 1, u32_le> IsConnected;
+ BitField<1, 1, u32_le> IsWired;
+ BitField<2, 1, u32_le> IsLeftJoyConnected;
+ BitField<3, 1, u32_le> IsLeftJoyWired;
+ BitField<4, 1, u32_le> IsRightJoyConnected;
+ BitField<5, 1, u32_le> IsRightJoyWired;
+ };
+ };
+ static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
+
+ struct GenericStates {
+ s64_le timestamp;
+ s64_le timestamp2;
+ ControllerPadState pad_states;
+ AnalogPosition l_stick;
+ AnalogPosition r_stick;
+ ConnectionState connection_status;
+ };
+ static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size");
+
+ struct NPadGeneric {
+ CommonHeader common;
+ std::array<GenericStates, 17> npad;
+ };
+ static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
+
+ enum class ColorReadError : u32_le {
+ ReadOk = 0,
+ ColorDoesntExist = 1,
+ NoController = 2,
+ };
+
+ struct NPadProperties {
+ union {
+ s64_le raw{};
+ BitField<11, 1, s64_le> is_vertical;
+ BitField<12, 1, s64_le> is_horizontal;
+ BitField<13, 1, s64_le> use_plus;
+ BitField<14, 1, s64_le> use_minus;
+ };
+ };
+
+ struct NPadDevice {
+ union {
+ u32_le raw{};
+ BitField<0, 1, s32_le> pro_controller;
+ BitField<1, 1, s32_le> handheld;
+ BitField<2, 1, s32_le> handheld_left;
+ BitField<3, 1, s32_le> handheld_right;
+ BitField<4, 1, s32_le> joycon_left;
+ BitField<5, 1, s32_le> joycon_right;
+ BitField<6, 1, s32_le> pokeball;
+ };
+ };
+
+ struct NPadEntry {
+ NPadType joy_styles;
+ NPadAssignments pad_assignment;
+
+ ColorReadError single_color_error;
+ ControllerColor single_color;
+
+ ColorReadError dual_color_error;
+ ControllerColor left_color;
+ ControllerColor right_color;
+
+ NPadGeneric main_controller_states;
+ NPadGeneric handheld_states;
+ NPadGeneric dual_states;
+ NPadGeneric left_joy_states;
+ NPadGeneric right_joy_states;
+ NPadGeneric pokeball_states;
+ NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
+ // relying on this for the time being
+ INSERT_PADDING_BYTES(
+ 0x708 *
+ 6); // TODO(ogniK): SixAxis states, require more information before implementation
+ NPadDevice device_type;
+ NPadProperties properties;
+ INSERT_PADDING_WORDS(1);
+ std::array<u32, 3> battery_level;
+ INSERT_PADDING_BYTES(0x5c);
+ INSERT_PADDING_BYTES(0xdf8);
+ };
+ static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
+
+ struct ControllerHolder {
+ Controller_NPad::NPadControllerType type;
+ bool is_connected;
+ };
+
+ NPadType style{};
+ std::array<NPadEntry, 10> shared_memory_entries{};
+ std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
+ buttons;
+ std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
+ std::vector<u32> supported_npad_id_types{};
+ NpadHoldType hold_type{NpadHoldType::Vertical};
+ Kernel::SharedPtr<Kernel::Event> styleset_changed_event;
+ std::size_t dump_idx{};
+ Vibration last_processed_vibration{};
+ static constexpr std::array<u32, 10> npad_id_list{0, 1, 2, 3, 4, 5, 6, 7, 32, 16};
+ std::array<ControllerHolder, 10> connected_controllers{};
+ bool can_controllers_vibrate{true};
+
+ void InitNewlyAddedControler(std::size_t controller_idx);
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp
new file mode 100644
index 000000000..3a13d5991
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/stubbed.cpp
@@ -0,0 +1,39 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/stubbed.h"
+
+namespace Service::HID {
+
+Controller_Stubbed::Controller_Stubbed() = default;
+
+void Controller_Stubbed::OnInit() {}
+
+void Controller_Stubbed::OnRelease() {}
+
+void Controller_Stubbed::OnUpdate(u8* data, std::size_t size) {
+ if (!smart_update) {
+ return;
+ }
+
+ CommonHeader header{};
+ header.timestamp = CoreTiming::GetTicks();
+ header.total_entry_count = 17;
+ header.entry_count = 0;
+ header.last_entry_index = 0;
+
+ std::memcpy(data + common_offset, &header, sizeof(CommonHeader));
+}
+
+void Controller_Stubbed::OnLoadInputDevices() {}
+
+void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) {
+ common_offset = off;
+ smart_update = true;
+}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h
new file mode 100644
index 000000000..9c1b57f83
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/stubbed.h
@@ -0,0 +1,33 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Stubbed final : public ControllerBase {
+public:
+ Controller_Stubbed();
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+ void SetCommonHeaderOffset(std::size_t off);
+
+private:
+ bool smart_update{};
+ std::size_t common_offset{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
new file mode 100644
index 000000000..e97f84ea1
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -0,0 +1,65 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/core_timing.h"
+#include "core/frontend/emu_window.h"
+#include "core/frontend/input.h"
+#include "core/hle/service/hid/controllers/touchscreen.h"
+#include "core/settings.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
+
+Controller_Touchscreen::Controller_Touchscreen() = default;
+
+void Controller_Touchscreen::OnInit() {}
+
+void Controller_Touchscreen::OnRelease() {}
+
+void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ const auto& last_entry =
+ shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+
+ const auto [x, y, pressed] = touch_device->GetStatus();
+ auto& touch_entry = cur_entry.states[0];
+ if (pressed) {
+ touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
+ touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
+ touch_entry.diameter_x = 15;
+ touch_entry.diameter_y = 15;
+ touch_entry.rotation_angle = 0;
+ const u64 tick = CoreTiming::GetTicks();
+ touch_entry.delta_time = tick - last_touch;
+ last_touch = tick;
+ touch_entry.finger = 0;
+ cur_entry.entry_count = 1;
+ } else {
+ cur_entry.entry_count = 0;
+ }
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory));
+}
+
+void Controller_Touchscreen::OnLoadInputDevices() {
+ touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
+}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
new file mode 100644
index 000000000..1d97b6c2a
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -0,0 +1,62 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/frontend/input.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Touchscreen final : public ControllerBase {
+public:
+ Controller_Touchscreen();
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct TouchState {
+ u64_le delta_time;
+ u32_le attribute;
+ u32_le finger;
+ u32_le x;
+ u32_le y;
+ u32_le diameter_x;
+ u32_le diameter_y;
+ u32_le rotation_angle;
+ };
+ static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
+
+ struct TouchScreenEntry {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+ s32_le entry_count;
+ std::array<TouchState, 16> states;
+ };
+ static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size");
+
+ struct TouchScreenSharedMemory {
+ CommonHeader header;
+ std::array<TouchScreenEntry, 17> shared_memory_entries{};
+ INSERT_PADDING_BYTES(0x3c8);
+ };
+ static_assert(sizeof(TouchScreenSharedMemory) == 0x3000,
+ "TouchScreenSharedMemory is an invalid size");
+ TouchScreenSharedMemory shared_memory{};
+ std::unique_ptr<Input::TouchDevice> touch_device;
+ s64_le last_touch{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp
new file mode 100644
index 000000000..df0b48451
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/xpad.cpp
@@ -0,0 +1,45 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/xpad.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
+
+Controller_XPad::Controller_XPad() = default;
+
+void Controller_XPad::OnInit() {}
+
+void Controller_XPad::OnRelease() {}
+
+void Controller_XPad::OnUpdate(u8* data, std::size_t size) {
+ for (auto& xpad_entry : shared_memory.shared_memory_entries) {
+ xpad_entry.header.timestamp = CoreTiming::GetTicks();
+ xpad_entry.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ xpad_entry.header.entry_count = 0;
+ xpad_entry.header.last_entry_index = 0;
+ return;
+ }
+ xpad_entry.header.entry_count = 16;
+
+ const auto& last_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
+ xpad_entry.header.last_entry_index = (xpad_entry.header.last_entry_index + 1) % 17;
+ auto& cur_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ }
+ // TODO(ogniK): Update xpad states
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_XPad::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h
new file mode 100644
index 000000000..e2007183d
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/xpad.h
@@ -0,0 +1,60 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/frontend/input.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_XPad final : public ControllerBase {
+public:
+ Controller_XPad();
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct AnalogStick {
+ s32_le x;
+ s32_le y;
+ };
+ static_assert(sizeof(AnalogStick) == 0x8, "AnalogStick is an invalid size");
+
+ struct XPadState {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+ s32_le attributes;
+ u32_le pad_states;
+ AnalogStick x_stick;
+ AnalogStick y_stick;
+ };
+ static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size");
+
+ struct XPadEntry {
+ CommonHeader header;
+ std::array<XPadState, 17> pad_states{};
+ INSERT_PADDING_BYTES(0x138);
+ };
+ static_assert(sizeof(XPadEntry) == 0x400, "XPadEntry is an invalid size");
+
+ struct SharedMemory {
+ std::array<XPadEntry, 4> shared_memory_entries{};
+ };
+ static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 7c6b0a4e6..8aca0f197 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -19,6 +21,16 @@
#include "core/hle/service/service.h"
#include "core/settings.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/hid/controllers/debug_pad.h"
+#include "core/hle/service/hid/controllers/gesture.h"
+#include "core/hle/service/hid/controllers/keyboard.h"
+#include "core/hle/service/hid/controllers/mouse.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/stubbed.h"
+#include "core/hle/service/hid/controllers/touchscreen.h"
+#include "core/hle/service/hid/controllers/xpad.h"
+
namespace Service::HID {
// Updating period for each HID device.
@@ -26,6 +38,22 @@ namespace Service::HID {
constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
+constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
+enum class HidController : std::size_t {
+ DebugPad,
+ Touchscreen,
+ Mouse,
+ Keyboard,
+ XPad,
+ Unknown1,
+ Unknown2,
+ Unknown3,
+ SixAxisSensor,
+ NPad,
+ Gesture,
+
+ MaxControllers,
+};
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
@@ -37,19 +65,57 @@ public:
auto& kernel = Core::System::GetInstance().Kernel();
shared_mem = Kernel::SharedMemory::Create(
- kernel, nullptr, 0x40000, Kernel::MemoryPermission::ReadWrite,
+ kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
+ MakeController<Controller_DebugPad>(HidController::DebugPad);
+ MakeController<Controller_Touchscreen>(HidController::Touchscreen);
+ MakeController<Controller_Mouse>(HidController::Mouse);
+ MakeController<Controller_Keyboard>(HidController::Keyboard);
+ MakeController<Controller_XPad>(HidController::XPad);
+ MakeController<Controller_Stubbed>(HidController::Unknown1);
+ MakeController<Controller_Stubbed>(HidController::Unknown2);
+ MakeController<Controller_Stubbed>(HidController::Unknown3);
+ MakeController<Controller_Stubbed>(HidController::SixAxisSensor);
+ MakeController<Controller_NPad>(HidController::NPad);
+ MakeController<Controller_Gesture>(HidController::Gesture);
+
+ // Homebrew doesn't try to activate some controllers, so we activate them by default
+ GetController<Controller_NPad>(HidController::NPad).ActivateController();
+ GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController();
+
+ GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00);
+ GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00);
+ GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
+
// Register update callbacks
pad_update_event = CoreTiming::RegisterEvent(
"HID::UpdatePadCallback",
- [this](u64 userdata, int cycles_late) { UpdatePadCallback(userdata, cycles_late); });
+ [this](u64 userdata, int cycles_late) { UpdateControllers(userdata, cycles_late); });
// TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
}
+ void ActivateController(HidController controller) {
+ controllers[static_cast<size_t>(controller)]->ActivateController();
+ }
+
+ void DeactivateController(HidController controller) {
+ controllers[static_cast<size_t>(controller)]->DeactivateController();
+ }
+
+ template <typename T>
+ void MakeController(HidController controller) {
+ controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
+ }
+
+ template <typename T>
+ T& GetController(HidController controller) {
+ return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
+ }
+
~IAppletResource() {
CoreTiming::UnscheduleEvent(pad_update_event, 0);
}
@@ -62,200 +128,15 @@ private:
LOG_DEBUG(Service_HID, "called");
}
- void LoadInputDevices() {
- std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
- Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
- buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
- std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
- Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
- sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
- touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
- // TODO(shinyquagsire23): gyro, mouse, keyboard
- }
-
- void UpdatePadCallback(u64 userdata, int cycles_late) {
- SharedMemory mem{};
- std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory));
-
- if (Settings::values.is_device_reload_pending.exchange(false))
- LoadInputDevices();
-
- // Set up controllers as neon red+blue Joy-Con attached to console
- ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header;
- controller_header.type = ControllerType_Handheld;
- controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent;
- controller_header.right_color_body = JOYCON_BODY_NEON_RED;
- controller_header.right_color_buttons = JOYCON_BUTTONS_NEON_RED;
- controller_header.left_color_body = JOYCON_BODY_NEON_BLUE;
- controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE;
-
- for (std::size_t controller = 0; controller < mem.controllers.size(); controller++) {
- for (auto& layout : mem.controllers[controller].layouts) {
- layout.header.num_entries = HID_NUM_ENTRIES;
- layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
-
- // HID shared memory stores the state of the past 17 samples in a circlular buffer,
- // each with a timestamp in number of samples since boot.
- const ControllerInputEntry& last_entry = layout.entries[layout.header.latest_entry];
-
- layout.header.timestamp_ticks = CoreTiming::GetTicks();
- layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
-
- ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
- entry.timestamp = last_entry.timestamp + 1;
- // TODO(shinyquagsire23): Is this always identical to timestamp?
- entry.timestamp_2 = entry.timestamp;
-
- // TODO(shinyquagsire23): More than just handheld input
- if (controller != Controller_Handheld)
- continue;
-
- entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;
-
- // TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?
- // For now everything is just the default handheld layout, but split Joy-Con will
- // rotate the face buttons and directions for certain layouts.
- ControllerPadState& state = entry.buttons;
- using namespace Settings::NativeButton;
- state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
- state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
- state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
- state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
- state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
- state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
- state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
- state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
- state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
- state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
-
- state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
- state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
- state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
- state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
-
- state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
-
- state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
-
- state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
- state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
-
- const auto [stick_l_x_f, stick_l_y_f] = sticks[Joystick_Left]->GetStatus();
- const auto [stick_r_x_f, stick_r_y_f] = sticks[Joystick_Right]->GetStatus();
- entry.joystick_left_x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
- entry.joystick_left_y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
- entry.joystick_right_x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
- entry.joystick_right_y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ void UpdateControllers(u64 userdata, int cycles_late) {
+ const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
+ for (const auto& controller : controllers) {
+ if (should_reload) {
+ controller->OnLoadInputDevices();
}
+ controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
}
- TouchScreen& touchscreen = mem.touchscreen;
- const u64 last_entry = touchscreen.header.latest_entry;
- const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size();
- const u64 timestamp = CoreTiming::GetTicks();
- const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1;
- touchscreen.header.timestamp_ticks = timestamp;
- touchscreen.header.num_entries = touchscreen.entries.size();
- touchscreen.header.latest_entry = curr_entry;
- touchscreen.header.max_entry_index = touchscreen.entries.size();
- touchscreen.header.timestamp = timestamp;
- touchscreen.entries[curr_entry].header.timestamp = sample_counter;
-
- TouchScreenEntryTouch touch_entry{};
- auto [x, y, pressed] = touch_device->GetStatus();
- touch_entry.timestamp = timestamp;
- touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
- touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
- touch_entry.touch_index = 0;
-
- // TODO(DarkLordZach): Maybe try to derive these from EmuWindow?
- touch_entry.diameter_x = 15;
- touch_entry.diameter_y = 15;
- touch_entry.angle = 0;
-
- // TODO(DarkLordZach): Implement multi-touch support
- if (pressed) {
- touchscreen.entries[curr_entry].header.num_touches = 1;
- touchscreen.entries[curr_entry].touches[0] = touch_entry;
- } else {
- touchscreen.entries[curr_entry].header.num_touches = 0;
- }
-
- // TODO(shinyquagsire23): Properly implement mouse
- Mouse& mouse = mem.mouse;
- const u64 last_mouse_entry = mouse.header.latest_entry;
- const u64 curr_mouse_entry = (mouse.header.latest_entry + 1) % mouse.entries.size();
- const u64 mouse_sample_counter = mouse.entries[last_mouse_entry].timestamp + 1;
- mouse.header.timestamp_ticks = timestamp;
- mouse.header.num_entries = mouse.entries.size();
- mouse.header.max_entry_index = mouse.entries.size();
- mouse.header.latest_entry = curr_mouse_entry;
-
- mouse.entries[curr_mouse_entry].timestamp = mouse_sample_counter;
- mouse.entries[curr_mouse_entry].timestamp_2 = mouse_sample_counter;
-
- // TODO(shinyquagsire23): Properly implement keyboard
- Keyboard& keyboard = mem.keyboard;
- const u64 last_keyboard_entry = keyboard.header.latest_entry;
- const u64 curr_keyboard_entry =
- (keyboard.header.latest_entry + 1) % keyboard.entries.size();
- const u64 keyboard_sample_counter = keyboard.entries[last_keyboard_entry].timestamp + 1;
- keyboard.header.timestamp_ticks = timestamp;
- keyboard.header.num_entries = keyboard.entries.size();
- keyboard.header.latest_entry = last_keyboard_entry;
- keyboard.header.max_entry_index = keyboard.entries.size();
-
- keyboard.entries[curr_keyboard_entry].timestamp = keyboard_sample_counter;
- keyboard.entries[curr_keyboard_entry].timestamp_2 = keyboard_sample_counter;
-
- // TODO(shinyquagsire23): Figure out what any of these are
- for (auto& input : mem.unk_input_1) {
- const u64 last_input_entry = input.header.latest_entry;
- const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
- const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
-
- input.header.timestamp_ticks = timestamp;
- input.header.num_entries = input.entries.size();
- input.header.latest_entry = last_input_entry;
- input.header.max_entry_index = input.entries.size();
-
- input.entries[curr_input_entry].timestamp = input_sample_counter;
- input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
- }
-
- for (auto& input : mem.unk_input_2) {
- input.header.timestamp_ticks = timestamp;
- input.header.num_entries = 17;
- input.header.latest_entry = 0;
- input.header.max_entry_index = 0;
- }
-
- UnkInput3& input = mem.unk_input_3;
- const u64 last_input_entry = input.header.latest_entry;
- const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
- const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
-
- input.header.timestamp_ticks = timestamp;
- input.header.num_entries = input.entries.size();
- input.header.latest_entry = last_input_entry;
- input.header.max_entry_index = input.entries.size();
-
- input.entries[curr_input_entry].timestamp = input_sample_counter;
- input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
-
- // TODO(shinyquagsire23): Signal events
-
- std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory));
-
- // Reschedule recurrent event
CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
}
@@ -265,11 +146,8 @@ private:
// CoreTiming update events
CoreTiming::EventType* pad_update_event;
- // Stored input state info
- std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
- buttons;
- std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
- std::unique_ptr<Input::TouchDevice> touch_device;
+ std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
+ controllers{};
};
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
@@ -301,7 +179,7 @@ public:
{31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
{40, nullptr, "AcquireXpadIdEventHandle"},
{41, nullptr, "ReleaseXpadIdEventHandle"},
- {51, nullptr, "ActivateXpad"},
+ {51, &Hid::ActivateXpad, "ActivateXpad"},
{55, nullptr, "GetXpadIds"},
{56, nullptr, "ActivateJoyXpad"},
{58, nullptr, "GetJoyXpadLifoHandle"},
@@ -362,8 +240,8 @@ public:
{206, &Hid::SendVibrationValues, "SendVibrationValues"},
{207, nullptr, "SendVibrationGcErmCommand"},
{208, nullptr, "GetActualVibrationGcErmCommand"},
- {209, nullptr, "BeginPermitVibrationSession"},
- {210, nullptr, "EndPermitVibrationSession"},
+ {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
+ {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
{300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
{301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
{302, nullptr, "StopConsoleSixAxisSensor"},
@@ -401,16 +279,11 @@ public:
// clang-format on
RegisterHandlers(functions);
-
- auto& kernel = Core::System::GetInstance().Kernel();
- event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "hid:EventHandle");
}
~Hid() = default;
private:
std::shared_ptr<IAppletResource> applet_resource;
- u32 joy_hold_type{0};
- Kernel::SharedPtr<Kernel::Event> event;
void CreateAppletResource(Kernel::HLERequestContext& ctx) {
if (applet_resource == nullptr) {
@@ -423,31 +296,59 @@ private:
LOG_DEBUG(Service_HID, "called");
}
+ void ActivateXpad(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::XPad);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::DebugPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::Touchscreen);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void ActivateMouse(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::Mouse);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void ActivateKeyboard(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::Keyboard);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void ActivateGesture(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::Gesture);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
+ // Should have no effect with how our npad sets up the data
+ applet_resource->ActivateController(HidController::NPad);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
}
void StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto handle = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
@@ -468,84 +369,168 @@ private:
}
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto supported_styleset = rp.PopRaw<u32>();
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSupportedStyleSet({supported_styleset});
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ LOG_DEBUG(Service_HID, "called");
}
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.Push<u32>(controller.GetSupportedStyleSet().raw);
+ LOG_DEBUG(Service_HID, "called");
}
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void ActivateNpad(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ applet_resource->ActivateController(HidController::NPad);
+ LOG_DEBUG(Service_HID, "called");
}
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto npad_id = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(event);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetStyleSetChangedEvent());
+ LOG_DEBUG(Service_HID, "called");
}
void DisconnectNpad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto npad_id = rp.PopRaw<u32>();
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .DisconnectNPad(npad_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::RequestParser rp{ctx};
+ auto npad_id = rp.PopRaw<u32>();
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLedPattern(npad_id)
+ .raw);
+ LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ IPC::RequestParser rp{ctx};
+ const auto hold_type = rp.PopRaw<u64>();
+ controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 3};
+ const auto& controller =
+ applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push(joy_hold_type);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.Push<u64>(static_cast<u64>(controller.GetHoldType()));
+ LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto npad_id = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
+ void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetVibrationEnabled(true);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetVibrationEnabled(false);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
void SendVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto controller_id = rp.PopRaw<u32>();
+ const auto vibration_values = rp.PopRaw<Controller_NPad::Vibration>();
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .VibrateController({controller_id}, {vibration_values});
+ LOG_DEBUG(Service_HID, "called");
}
- void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
+ void SendVibrationValues(Kernel::HLERequestContext& ctx) {
+ const auto controllers = ctx.ReadBuffer(0);
+ const auto vibrations = ctx.ReadBuffer(1);
+
+ std::vector<u32> controller_list(controllers.size() / sizeof(u32));
+ std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() /
+ sizeof(Controller_NPad::Vibration));
+
+ std::memcpy(controller_list.data(), controllers.data(), controllers.size());
+ std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
+ std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
+ [](u32 controller_id) { return controller_id - 3; });
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .VibrateController(controller_list, vibration_list);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<Controller_NPad::Vibration>(
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLastVibration());
+ LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id = rp.PopRaw<u32>();
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
@@ -555,6 +540,8 @@ private:
}
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto mode = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
@@ -563,8 +550,9 @@ private:
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(0);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.Push<u32>(1);
+ rb.Push<u32>(0);
+ LOG_DEBUG(Service_HID, "called");
}
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
@@ -574,12 +562,6 @@ private:
LOG_DEBUG(Service_HID, "called");
}
- void SendVibrationValues(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
- }
-
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -597,18 +579,6 @@ private:
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
-
- void ActivateGesture(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
- }
-
- void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
- }
};
class HidDbg final : public ServiceFramework<HidDbg> {
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 88d926808..773035460 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -4,408 +4,12 @@
#pragma once
-#include <array>
-#include "common/bit_field.h"
-#include "common/common_types.h"
-#include "core/hle/service/service.h"
+namespace SM {
+class ServiceManager;
+}
namespace Service::HID {
-// Begin enums and output structs
-
-constexpr u32 HID_NUM_ENTRIES = 17;
-constexpr u32 HID_NUM_LAYOUTS = 7;
-constexpr s32 HID_JOYSTICK_MAX = 0x8000;
-constexpr s32 HID_JOYSTICK_MIN = -0x8000;
-
-constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
-constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
-constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
-constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
-
-enum ControllerType : u32 {
- ControllerType_ProController = 1 << 0,
- ControllerType_Handheld = 1 << 1,
- ControllerType_JoyconPair = 1 << 2,
- ControllerType_JoyconLeft = 1 << 3,
- ControllerType_JoyconRight = 1 << 4,
-};
-
-enum ControllerLayoutType : u32 {
- Layout_ProController = 0, // Pro Controller or HID gamepad
- Layout_Handheld = 1, // Two Joy-Con docked to rails
- Layout_Single = 2, // Horizontal single Joy-Con or pair of Joy-Con, adjusted for orientation
- Layout_Left = 3, // Only raw left Joy-Con state, no orientation adjustment
- Layout_Right = 4, // Only raw right Joy-Con state, no orientation adjustment
- Layout_DefaultDigital = 5, // Same as next, but sticks have 8-direction values only
- Layout_Default = 6, // Safe default, single Joy-Con have buttons/sticks rotated for orientation
-};
-
-enum ControllerColorDescription {
- ColorDesc_ColorsNonexistent = 1 << 1,
-};
-
-enum ControllerConnectionState {
- ConnectionState_Connected = 1 << 0,
- ConnectionState_Wired = 1 << 1,
-};
-
-enum ControllerJoystick {
- Joystick_Left = 0,
- Joystick_Right = 1,
-};
-
-enum ControllerID {
- Controller_Player1 = 0,
- Controller_Player2 = 1,
- Controller_Player3 = 2,
- Controller_Player4 = 3,
- Controller_Player5 = 4,
- Controller_Player6 = 5,
- Controller_Player7 = 6,
- Controller_Player8 = 7,
- Controller_Handheld = 8,
- Controller_Unknown = 9,
-};
-
-// End enums and output structs
-
-// Begin UnkInput3
-
-struct UnkInput3Header {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(UnkInput3Header) == 0x20, "HID UnkInput3 header structure has incorrect size");
-
-struct UnkInput3Entry {
- u64 timestamp;
- u64 timestamp_2;
- u64 unk_8;
- u64 unk_10;
- u64 unk_18;
-};
-static_assert(sizeof(UnkInput3Entry) == 0x28, "HID UnkInput3 entry structure has incorrect size");
-
-struct UnkInput3 {
- UnkInput3Header header;
- std::array<UnkInput3Entry, 17> entries;
- std::array<u8, 0x138> padding;
-};
-static_assert(sizeof(UnkInput3) == 0x400, "HID UnkInput3 structure has incorrect size");
-
-// End UnkInput3
-
-// Begin TouchScreen
-
-struct TouchScreenHeader {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
- u64 timestamp;
-};
-static_assert(sizeof(TouchScreenHeader) == 0x28,
- "HID touch screen header structure has incorrect size");
-
-struct TouchScreenEntryHeader {
- u64 timestamp;
- u64 num_touches;
-};
-static_assert(sizeof(TouchScreenEntryHeader) == 0x10,
- "HID touch screen entry header structure has incorrect size");
-
-struct TouchScreenEntryTouch {
- u64 timestamp;
- u32 padding;
- u32 touch_index;
- u32 x;
- u32 y;
- u32 diameter_x;
- u32 diameter_y;
- u32 angle;
- u32 padding_2;
-};
-static_assert(sizeof(TouchScreenEntryTouch) == 0x28,
- "HID touch screen touch structure has incorrect size");
-
-struct TouchScreenEntry {
- TouchScreenEntryHeader header;
- std::array<TouchScreenEntryTouch, 16> touches;
- u64 unk;
-};
-static_assert(sizeof(TouchScreenEntry) == 0x298,
- "HID touch screen entry structure has incorrect size");
-
-struct TouchScreen {
- TouchScreenHeader header;
- std::array<TouchScreenEntry, 17> entries;
- std::array<u8, 0x3c0> padding;
-};
-static_assert(sizeof(TouchScreen) == 0x3000, "HID touch screen structure has incorrect size");
-
-// End TouchScreen
-
-// Begin Mouse
-
-struct MouseHeader {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(MouseHeader) == 0x20, "HID mouse header structure has incorrect size");
-
-struct MouseButtonState {
- union {
- u64 hex{};
-
- // Buttons
- BitField<0, 1, u64> left;
- BitField<1, 1, u64> right;
- BitField<2, 1, u64> middle;
- BitField<3, 1, u64> forward;
- BitField<4, 1, u64> back;
- };
-};
-
-struct MouseEntry {
- u64 timestamp;
- u64 timestamp_2;
- u32 x;
- u32 y;
- u32 velocity_x;
- u32 velocity_y;
- u32 scroll_velocity_x;
- u32 scroll_velocity_y;
- MouseButtonState buttons;
-};
-static_assert(sizeof(MouseEntry) == 0x30, "HID mouse entry structure has incorrect size");
-
-struct Mouse {
- MouseHeader header;
- std::array<MouseEntry, 17> entries;
- std::array<u8, 0xB0> padding;
-};
-static_assert(sizeof(Mouse) == 0x400, "HID mouse structure has incorrect size");
-
-// End Mouse
-
-// Begin Keyboard
-
-struct KeyboardHeader {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(KeyboardHeader) == 0x20, "HID keyboard header structure has incorrect size");
-
-struct KeyboardModifierKeyState {
- union {
- u64 hex{};
-
- // Buttons
- BitField<0, 1, u64> lctrl;
- BitField<1, 1, u64> lshift;
- BitField<2, 1, u64> lalt;
- BitField<3, 1, u64> lmeta;
- BitField<4, 1, u64> rctrl;
- BitField<5, 1, u64> rshift;
- BitField<6, 1, u64> ralt;
- BitField<7, 1, u64> rmeta;
- BitField<8, 1, u64> capslock;
- BitField<9, 1, u64> scrolllock;
- BitField<10, 1, u64> numlock;
- };
-};
-
-struct KeyboardEntry {
- u64 timestamp;
- u64 timestamp_2;
- KeyboardModifierKeyState modifier;
- u32 keys[8];
-};
-static_assert(sizeof(KeyboardEntry) == 0x38, "HID keyboard entry structure has incorrect size");
-
-struct Keyboard {
- KeyboardHeader header;
- std::array<KeyboardEntry, 17> entries;
- std::array<u8, 0x28> padding;
-};
-static_assert(sizeof(Keyboard) == 0x400, "HID keyboard structure has incorrect size");
-
-// End Keyboard
-
-// Begin UnkInput1
-
-struct UnkInput1Header {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(UnkInput1Header) == 0x20, "HID UnkInput1 header structure has incorrect size");
-
-struct UnkInput1Entry {
- u64 timestamp;
- u64 timestamp_2;
- u64 unk_8;
- u64 unk_10;
- u64 unk_18;
-};
-static_assert(sizeof(UnkInput1Entry) == 0x28, "HID UnkInput1 entry structure has incorrect size");
-
-struct UnkInput1 {
- UnkInput1Header header;
- std::array<UnkInput1Entry, 17> entries;
- std::array<u8, 0x138> padding;
-};
-static_assert(sizeof(UnkInput1) == 0x400, "HID UnkInput1 structure has incorrect size");
-
-// End UnkInput1
-
-// Begin UnkInput2
-
-struct UnkInput2Header {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(UnkInput2Header) == 0x20, "HID UnkInput2 header structure has incorrect size");
-
-struct UnkInput2 {
- UnkInput2Header header;
- std::array<u8, 0x1E0> padding;
-};
-static_assert(sizeof(UnkInput2) == 0x200, "HID UnkInput2 structure has incorrect size");
-
-// End UnkInput2
-
-// Begin Controller
-
-struct ControllerMAC {
- u64 timestamp;
- std::array<u8, 0x8> mac;
- u64 unk;
- u64 timestamp_2;
-};
-static_assert(sizeof(ControllerMAC) == 0x20, "HID controller MAC structure has incorrect size");
-
-struct ControllerHeader {
- u32 type;
- u32 is_half;
- u32 single_colors_descriptor;
- u32 single_color_body;
- u32 single_color_buttons;
- u32 split_colors_descriptor;
- u32 left_color_body;
- u32 left_color_buttons;
- u32 right_color_body;
- u32 right_color_buttons;
-};
-static_assert(sizeof(ControllerHeader) == 0x28,
- "HID controller header structure has incorrect size");
-
-struct ControllerLayoutHeader {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(ControllerLayoutHeader) == 0x20,
- "HID controller layout header structure has incorrect size");
-
-struct ControllerPadState {
- union {
- u64 hex{};
-
- // Buttons
- BitField<0, 1, u64> a;
- BitField<1, 1, u64> b;
- BitField<2, 1, u64> x;
- BitField<3, 1, u64> y;
- BitField<4, 1, u64> lstick;
- BitField<5, 1, u64> rstick;
- BitField<6, 1, u64> l;
- BitField<7, 1, u64> r;
- BitField<8, 1, u64> zl;
- BitField<9, 1, u64> zr;
- BitField<10, 1, u64> plus;
- BitField<11, 1, u64> minus;
-
- // D-pad buttons
- BitField<12, 1, u64> dleft;
- BitField<13, 1, u64> dup;
- BitField<14, 1, u64> dright;
- BitField<15, 1, u64> ddown;
-
- // Left stick directions
- BitField<16, 1, u64> lstick_left;
- BitField<17, 1, u64> lstick_up;
- BitField<18, 1, u64> lstick_right;
- BitField<19, 1, u64> lstick_down;
-
- // Right stick directions
- BitField<20, 1, u64> rstick_left;
- BitField<21, 1, u64> rstick_up;
- BitField<22, 1, u64> rstick_right;
- BitField<23, 1, u64> rstick_down;
-
- BitField<24, 1, u64> sl;
- BitField<25, 1, u64> sr;
- };
-};
-
-struct ControllerInputEntry {
- u64 timestamp;
- u64 timestamp_2;
- ControllerPadState buttons;
- s32 joystick_left_x;
- s32 joystick_left_y;
- s32 joystick_right_x;
- s32 joystick_right_y;
- u64 connection_state;
-};
-static_assert(sizeof(ControllerInputEntry) == 0x30,
- "HID controller input entry structure has incorrect size");
-
-struct ControllerLayout {
- ControllerLayoutHeader header;
- std::array<ControllerInputEntry, 17> entries;
-};
-static_assert(sizeof(ControllerLayout) == 0x350,
- "HID controller layout structure has incorrect size");
-
-struct Controller {
- ControllerHeader header;
- std::array<ControllerLayout, HID_NUM_LAYOUTS> layouts;
- std::array<u8, 0x2a70> unk_1;
- ControllerMAC mac_left;
- ControllerMAC mac_right;
- std::array<u8, 0xdf8> unk_2;
-};
-static_assert(sizeof(Controller) == 0x5000, "HID controller structure has incorrect size");
-
-// End Controller
-
-struct SharedMemory {
- UnkInput3 unk_input_3;
- TouchScreen touchscreen;
- Mouse mouse;
- Keyboard keyboard;
- std::array<UnkInput1, 4> unk_input_1;
- std::array<UnkInput2, 3> unk_input_2;
- std::array<u8, 0x800> unk_section_8;
- std::array<u8, 0x4000> controller_serials;
- std::array<Controller, 10> controllers;
- std::array<u8, 0x4600> unk_section_9;
-};
-static_assert(sizeof(SharedMemory) == 0x40000, "HID Shared Memory structure has incorrect size");
-
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();
diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp
index 7b91bb258..e1f17a926 100644
--- a/src/core/hle/service/mm/mm_u.cpp
+++ b/src/core/hle/service/mm/mm_u.cpp
@@ -14,14 +14,14 @@ public:
explicit MM_U() : ServiceFramework{"mm:u"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &MM_U::Initialize, "InitializeOld"},
- {1, &MM_U::Finalize, "FinalizeOld"},
- {2, &MM_U::SetAndWait, "SetAndWaitOld"},
- {3, &MM_U::Get, "GetOld"},
- {4, &MM_U::Initialize, "Initialize"},
- {5, &MM_U::Finalize, "Finalize"},
- {6, &MM_U::SetAndWait, "SetAndWait"},
- {7, &MM_U::Get, "Get"},
+ {0, &MM_U::Initialize, "Initialize"},
+ {1, &MM_U::Finalize, "Finalize"},
+ {2, &MM_U::SetAndWait, "SetAndWait"},
+ {3, &MM_U::Get, "Get"},
+ {4, &MM_U::InitializeWithId, "InitializeWithId"},
+ {5, &MM_U::FinalizeWithId, "FinalizeWithId"},
+ {6, &MM_U::SetAndWaitWithId, "SetAndWaitWithId"},
+ {7, &MM_U::GetWithId, "GetWithId"},
};
// clang-format on
@@ -59,9 +59,43 @@ private:
rb.Push(current);
}
+ void InitializeWithId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_MM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(id); // Any non zero value
+ }
+
+ void FinalizeWithId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_MM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetAndWaitWithId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u32 input_id = rp.Pop<u32>();
+ min = rp.Pop<u32>();
+ max = rp.Pop<u32>();
+ current = min;
+
+ LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}",
+ input_id, min, max);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetWithId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_MM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(current);
+ }
+
u32 min{0};
u32 max{0};
u32 current{0};
+ u32 id{1};
};
void InstallInterfaces(SM::ServiceManager& service_manager) {
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 8c07a05c2..39c0c1e63 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -144,7 +144,7 @@ private:
}
const u64 device_handle{0xDEAD};
- const HID::ControllerID npad_id{HID::Controller_Player1};
+ const u32 npad_id{0}; // This is the first player controller id
State state{State::NonInitialized};
DeviceState device_state{DeviceState::Initialized};
Kernel::SharedPtr<Kernel::Event> activate_event;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 7a619acb4..461607c95 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -59,8 +59,7 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) {
if (xci->GetProgramNCAStatus() != ResultStatus::Success)
return xci->GetProgramNCAStatus();
- const auto nca = xci->GetProgramNCA();
- if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
+ if (!xci->HasProgramNCA() && !Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile;
const auto result = nca_loader->Load(process);
diff --git a/src/core/settings.h b/src/core/settings.h
index 83b9a04c8..8f2da01c8 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -136,7 +136,7 @@ struct Values {
float resolution_factor;
bool use_frame_limit;
u16 frame_limit;
- bool use_accurate_framebuffers;
+ bool use_accurate_gpu_emulation;
float bg_red;
float bg_green;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 7b04792b5..0de13edd3 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -163,8 +163,8 @@ TelemetrySession::TelemetrySession() {
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit",
Settings::values.use_frame_limit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit);
- AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateFramebuffers",
- Settings::values.use_accurate_framebuffers);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateGpuEmulation",
+ Settings::values.use_accurate_gpu_emulation);
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
Settings::values.use_docked_mode);
}
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index c8d1b6478..c8af1c6b6 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -448,7 +448,10 @@ public:
BitField<8, 3, u32> block_depth;
BitField<12, 1, InvMemoryLayout> type;
} memory_layout;
- u32 array_mode;
+ union {
+ BitField<0, 16, u32> array_mode;
+ BitField<16, 1, u32> volume;
+ };
u32 layer_stride;
u32 base_layer;
INSERT_PADDING_WORDS(7);
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index ca923d17d..022d4ab74 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -87,6 +87,16 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
return gpu_addr;
}
+GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
+ for (const auto& region : mapped_regions) {
+ const GPUVAddr region_end{region.gpu_addr + region.size};
+ if (region_start >= region.gpu_addr && region_start < region_end) {
+ return region_end;
+ }
+ }
+ return {};
+}
+
boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
GPUVAddr gpu_addr = 0;
u64 free_space = 0;
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 86765e72a..caf80093f 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -26,6 +26,7 @@ public:
GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size);
GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
+ GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h
index 083b283b0..0a3b3951e 100644
--- a/src/video_core/rasterizer_cache.h
+++ b/src/video_core/rasterizer_cache.h
@@ -11,32 +11,77 @@
#include "common/common_types.h"
#include "core/core.h"
+#include "core/settings.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
+class RasterizerCacheObject {
+public:
+ /// Gets the address of the shader in guest memory, required for cache management
+ virtual VAddr GetAddr() const = 0;
+
+ /// Gets the size of the shader in guest memory, required for cache management
+ virtual std::size_t GetSizeInBytes() const = 0;
+
+ /// Wriets any cached resources back to memory
+ virtual void Flush() = 0;
+
+ /// Sets whether the cached object should be considered registered
+ void SetIsRegistered(bool registered) {
+ is_registered = registered;
+ }
+
+ /// Returns true if the cached object is registered
+ bool IsRegistered() const {
+ return is_registered;
+ }
+
+ /// Returns true if the cached object is dirty
+ bool IsDirty() const {
+ return is_dirty;
+ }
+
+ /// Returns ticks from when this cached object was last modified
+ u64 GetLastModifiedTicks() const {
+ return last_modified_ticks;
+ }
+
+ /// Marks an object as recently modified, used to specify whether it is clean or dirty
+ template <class T>
+ void MarkAsModified(bool dirty, T& cache) {
+ is_dirty = dirty;
+ last_modified_ticks = cache.GetModifiedTicks();
+ }
+
+private:
+ bool is_registered{}; ///< Whether the object is currently registered with the cache
+ bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
+ u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
+};
+
template <class T>
class RasterizerCache : NonCopyable {
+ friend class RasterizerCacheObject;
+
public:
+ /// Write any cached resources overlapping the specified region back to memory
+ void FlushRegion(Tegra::GPUVAddr addr, size_t size) {
+ const auto& objects{GetSortedObjectsFromRegion(addr, size)};
+ for (auto& object : objects) {
+ FlushObject(object);
+ }
+ }
+
/// Mark the specified region as being invalidated
void InvalidateRegion(VAddr addr, u64 size) {
- if (size == 0)
- return;
-
- const ObjectInterval interval{addr, addr + size};
- for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
- for (auto& cached_object : pair.second) {
- if (!cached_object)
- continue;
-
- remove_objects.emplace(cached_object);
+ const auto& objects{GetSortedObjectsFromRegion(addr, size)};
+ for (auto& object : objects) {
+ if (!object->IsRegistered()) {
+ // Skip duplicates
+ continue;
}
+ Unregister(object);
}
-
- for (auto& remove_object : remove_objects) {
- Unregister(remove_object);
- }
-
- remove_objects.clear();
}
/// Invalidates everything in the cache
@@ -62,6 +107,7 @@ protected:
/// Register an object into the cache
void Register(const T& object) {
+ object->SetIsRegistered(true);
object_cache.add({GetInterval(object), ObjectSet{object}});
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
@@ -69,12 +115,57 @@ protected:
/// Unregisters an object from the cache
void Unregister(const T& object) {
+ object->SetIsRegistered(false);
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
+
+ // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
+ if (Settings::values.use_accurate_gpu_emulation) {
+ FlushObject(object);
+ }
+
object_cache.subtract({GetInterval(object), ObjectSet{object}});
}
+ /// Returns a ticks counter used for tracking when cached objects were last modified
+ u64 GetModifiedTicks() {
+ return ++modified_ticks;
+ }
+
private:
+ /// Returns a list of cached objects from the specified memory region, ordered by access time
+ std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
+ if (size == 0) {
+ return {};
+ }
+
+ std::vector<T> objects;
+ const ObjectInterval interval{addr, addr + size};
+ for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
+ for (auto& cached_object : pair.second) {
+ if (!cached_object) {
+ continue;
+ }
+ objects.push_back(cached_object);
+ }
+ }
+
+ std::sort(objects.begin(), objects.end(), [](const T& a, const T& b) -> bool {
+ return a->GetLastModifiedTicks() < b->GetLastModifiedTicks();
+ });
+
+ return objects;
+ }
+
+ /// Flushes the specified object, updating appropriate cache state as needed
+ void FlushObject(const T& object) {
+ if (!object->IsDirty()) {
+ return;
+ }
+ object->Flush();
+ object->MarkAsModified(false, *this);
+ }
+
using ObjectSet = std::set<T>;
using ObjectCache = boost::icl::interval_map<VAddr, ObjectSet>;
using ObjectInterval = typename ObjectCache::interval_type;
@@ -84,6 +175,6 @@ private:
object->GetAddr() + object->GetSizeInBytes());
}
- ObjectCache object_cache;
- ObjectSet remove_objects;
+ ObjectCache object_cache; ///< Cache of objects
+ u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
};
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 965976334..be29dc8be 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -15,15 +15,18 @@
namespace OpenGL {
-struct CachedBufferEntry final {
- VAddr GetAddr() const {
+struct CachedBufferEntry final : public RasterizerCacheObject {
+ VAddr GetAddr() const override {
return addr;
}
- std::size_t GetSizeInBytes() const {
+ std::size_t GetSizeInBytes() const override {
return size;
}
+ // We do not have to flush this cache as things in it are never modified by us.
+ void Flush() override {}
+
VAddr addr;
std::size_t size;
GLintptr offset;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 8d5f277e2..468253033 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -424,6 +424,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
// Used when just a single color attachment is enabled, e.g. for clearing a color buffer
Surface color_surface =
res_cache.GetColorBufferSurface(*single_color_target, preserve_contents);
+
+ if (color_surface) {
+ // Assume that a surface will be written to if it is used as a framebuffer, even if
+ // the shader doesn't actually write to it.
+ color_surface->MarkAsModified(true, res_cache);
+ }
+
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D,
@@ -434,6 +441,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
std::array<GLenum, Maxwell::NumRenderTargets> buffers;
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
+
+ if (color_surface) {
+ // Assume that a surface will be written to if it is used as a framebuffer, even
+ // if the shader doesn't actually write to it.
+ color_surface->MarkAsModified(true, res_cache);
+ }
+
buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
@@ -453,6 +467,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
}
if (depth_surface) {
+ // Assume that a surface will be written to if it is used as a framebuffer, even if
+ // the shader doesn't actually write to it.
+ depth_surface->MarkAsModified(true, res_cache);
+
if (regs.stencil_enable) {
// Attach both depth and stencil
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
@@ -617,7 +635,14 @@ void RasterizerOpenGL::DrawArrays() {
void RasterizerOpenGL::FlushAll() {}
-void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {}
+void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+
+ if (Settings::values.use_accurate_gpu_emulation) {
+ // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
+ res_cache.FlushRegion(addr, size);
+ }
+}
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
@@ -627,6 +652,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
}
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+ FlushRegion(addr, size);
InvalidateRegion(addr, size);
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 801d45144..9c8925383 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -34,16 +34,53 @@ struct FormatTuple {
bool compressed;
};
-static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
- auto& gpu{Core::System::GetInstance().GPU()};
- const auto cpu_addr{gpu.MemoryManager().GpuToCpuAddress(gpu_addr)};
- return cpu_addr ? *cpu_addr : 0;
+static bool IsPixelFormatASTC(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::ASTC_2D_4X4:
+ case PixelFormat::ASTC_2D_5X4:
+ case PixelFormat::ASTC_2D_8X8:
+ case PixelFormat::ASTC_2D_8X5:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::ASTC_2D_4X4:
+ return {4, 4};
+ case PixelFormat::ASTC_2D_5X4:
+ return {5, 4};
+ case PixelFormat::ASTC_2D_8X8:
+ return {8, 8};
+ case PixelFormat::ASTC_2D_8X5:
+ return {8, 5};
+ default:
+ LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+}
+
+void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
+
+ addr = cpu_addr ? *cpu_addr : 0;
+ gpu_addr = gpu_addr_;
+ size_in_bytes = SizeInBytesRaw();
+
+ if (IsPixelFormatASTC(pixel_format)) {
+ // ASTC is uncompressed in software, in emulated as RGBA8
+ size_in_bytes_gl = width * height * depth * 4;
+ } else {
+ size_in_bytes_gl = SizeInBytesGL();
+ }
}
/*static*/ SurfaceParams SurfaceParams::CreateForTexture(
const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) {
SurfaceParams params{};
- params.addr = TryGetCpuAddr(config.tic.Address());
params.is_tiled = config.tic.IsTiled();
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
@@ -87,18 +124,18 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
break;
}
- params.size_in_bytes_total = params.SizeInBytesTotal();
- params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = config.tic.max_mip_level + 1;
params.rt = {};
+ params.InitCacheParameters(config.tic.Address());
+
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) {
const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]};
SurfaceParams params{};
- params.addr = TryGetCpuAddr(config.Address());
+
params.is_tiled =
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
params.block_width = 1 << config.memory_layout.block_width;
@@ -112,16 +149,17 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
- params.size_in_bytes_total = params.SizeInBytesTotal();
- params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
// Render target specific parameters, not used for caching
params.rt.index = static_cast<u32>(index);
params.rt.array_mode = config.array_mode;
params.rt.layer_stride = config.layer_stride;
+ params.rt.volume = config.volume;
params.rt.base_layer = config.base_layer;
+ params.InitCacheParameters(config.Address());
+
return params;
}
@@ -130,7 +168,7 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
u32 block_width, u32 block_height, u32 block_depth,
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
SurfaceParams params{};
- params.addr = TryGetCpuAddr(zeta_address);
+
params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
params.block_width = 1 << std::min(block_width, 5U);
params.block_height = 1 << std::min(block_height, 5U);
@@ -143,18 +181,18 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = zeta_height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
- params.size_in_bytes_total = params.SizeInBytesTotal();
- params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
params.rt = {};
+ params.InitCacheParameters(zeta_address);
+
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
SurfaceParams params{};
- params.addr = TryGetCpuAddr(config.Address());
+
params.is_tiled = !config.linear;
params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
@@ -167,11 +205,11 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
- params.size_in_bytes_total = params.SizeInBytesTotal();
- params.size_in_bytes_2d = params.SizeInBytes2D();
params.max_mip_level = 0;
params.rt = {};
+ params.InitCacheParameters(config.Address());
+
return params;
}
@@ -276,34 +314,6 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
return format;
}
-static bool IsPixelFormatASTC(PixelFormat format) {
- switch (format) {
- case PixelFormat::ASTC_2D_4X4:
- case PixelFormat::ASTC_2D_5X4:
- case PixelFormat::ASTC_2D_8X8:
- case PixelFormat::ASTC_2D_8X5:
- return true;
- default:
- return false;
- }
-}
-
-static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
- switch (format) {
- case PixelFormat::ASTC_2D_4X4:
- return {4, 4};
- case PixelFormat::ASTC_2D_5X4:
- return {5, 4};
- case PixelFormat::ASTC_2D_8X8:
- return {8, 8};
- case PixelFormat::ASTC_2D_8X5:
- return {8, 5};
- default:
- LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
- UNREACHABLE();
- }
-}
-
MathUtil::Rectangle<u32> SurfaceParams::GetRect() const {
u32 actual_height{unaligned_height};
if (IsPixelFormatASTC(pixel_format)) {
@@ -333,23 +343,21 @@ static bool IsFormatBCn(PixelFormat format) {
template <bool morton_to_gl, PixelFormat format>
void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u8* gl_buffer,
std::size_t gl_buffer_size, VAddr addr) {
- constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
- constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
+ constexpr u32 bytes_per_pixel = SurfaceParams::GetBytesPerPixel(format);
+
+ // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
+ // pixel values.
+ const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
if (morton_to_gl) {
- // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
- // pixel values.
- const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
addr, tile_size, bytes_per_pixel, stride, height, depth, block_height, block_depth);
const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
memcpy(gl_buffer, data.data(), size_to_copy);
} else {
- // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
- // check the configuration for this and perform more generic un/swizzle
- LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
- VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
- Memory::GetPointer(addr), gl_buffer, morton_to_gl);
+ Tegra::Texture::CopySwizzledData(stride / tile_size, height / tile_size, depth,
+ bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr),
+ gl_buffer, false, block_height, block_depth);
}
}
@@ -430,17 +438,16 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t,
MortonCopy<false, PixelFormat::RGBA16UI>,
MortonCopy<false, PixelFormat::R11FG11FB10F>,
MortonCopy<false, PixelFormat::RGBA32UI>,
- // TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/BC6H_UF16/BC6H_SF16/ASTC_2D_4X4
- // formats are not supported
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
+ MortonCopy<false, PixelFormat::DXT1>,
+ MortonCopy<false, PixelFormat::DXT23>,
+ MortonCopy<false, PixelFormat::DXT45>,
+ MortonCopy<false, PixelFormat::DXN1>,
+ MortonCopy<false, PixelFormat::DXN2UNORM>,
+ MortonCopy<false, PixelFormat::DXN2SNORM>,
+ MortonCopy<false, PixelFormat::BC7U>,
+ MortonCopy<false, PixelFormat::BC6H_UF16>,
+ MortonCopy<false, PixelFormat::BC6H_SF16>,
+ // TODO(Subv): Swizzling ASTC formats are not supported
nullptr,
MortonCopy<false, PixelFormat::G8R8U>,
MortonCopy<false, PixelFormat::G8R8S>,
@@ -626,22 +633,21 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type);
auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type);
- std::size_t buffer_size =
- std::max(src_params.size_in_bytes_total, dst_params.size_in_bytes_total);
+ std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes);
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle);
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
if (source_format.compressed) {
glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment,
- static_cast<GLsizei>(src_params.size_in_bytes_total), nullptr);
+ static_cast<GLsizei>(src_params.size_in_bytes), nullptr);
} else {
glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format,
- source_format.type, static_cast<GLsizei>(src_params.size_in_bytes_total),
+ source_format.type, static_cast<GLsizei>(src_params.size_in_bytes),
nullptr);
}
// If the new texture is bigger than the previous one, we need to fill in the rest with data
// from the CPU.
- if (src_params.size_in_bytes_total < dst_params.size_in_bytes_total) {
+ if (src_params.size_in_bytes < dst_params.size_in_bytes) {
// Upload the rest of the memory.
if (dst_params.is_tiled) {
// TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest
@@ -651,12 +657,12 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during "
"reinterpretation but the texture is tiled.");
}
- std::size_t remaining_size =
- dst_params.size_in_bytes_total - src_params.size_in_bytes_total;
+ std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
std::vector<u8> data(remaining_size);
- Memory::ReadBlock(dst_params.addr + src_params.size_in_bytes_total, data.data(),
- data.size());
- glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes_total, remaining_size,
+ std::memcpy(data.data(), Memory::GetPointer(dst_params.addr + src_params.size_in_bytes),
+ data.size());
+
+ glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
data.data());
}
@@ -702,7 +708,8 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
}
CachedSurface::CachedSurface(const SurfaceParams& params)
- : params(params), gl_target(SurfaceTargetToGL(params.target)) {
+ : params(params), gl_target(SurfaceTargetToGL(params.target)),
+ cached_size_in_bytes(params.size_in_bytes) {
texture.Create();
const auto& rect{params.GetRect()};
@@ -752,9 +759,21 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
SurfaceParams::SurfaceTargetName(params.target));
+
+ // Clamp size to mapped GPU memory region
+ // TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
+ // R32F render buffer. We do not yet know if this is a game bug or something else, but this
+ // check is necessary to prevent flushing from overwriting unmapped memory.
+
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr};
+ if (cached_size_in_bytes > max_size) {
+ LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
+ cached_size_in_bytes = max_size;
+ }
}
-static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
+static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height, bool reverse) {
union S8Z24 {
BitField<0, 24, u32> z24;
BitField<24, 8, u32> s8;
@@ -767,22 +786,29 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
};
static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size");
- S8Z24 input_pixel{};
- Z24S8 output_pixel{};
- constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)};
+ S8Z24 s8z24_pixel{};
+ Z24S8 z24s8_pixel{};
+ constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::S8Z24)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
- std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24));
- output_pixel.s8.Assign(input_pixel.s8);
- output_pixel.z24.Assign(input_pixel.z24);
- std::memcpy(&data[offset], &output_pixel, sizeof(Z24S8));
+ if (reverse) {
+ std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8));
+ s8z24_pixel.s8.Assign(z24s8_pixel.s8);
+ s8z24_pixel.z24.Assign(z24s8_pixel.z24);
+ std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24));
+ } else {
+ std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24));
+ z24s8_pixel.s8.Assign(s8z24_pixel.s8);
+ z24s8_pixel.z24.Assign(s8z24_pixel.z24);
+ std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8));
+ }
}
}
}
static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
- constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};
+ constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::G8R8U)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
@@ -814,7 +840,7 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
}
case PixelFormat::S8Z24:
// Convert the S8Z24 depth format to Z24S8, as OpenGL does not support S8Z24.
- ConvertS8Z24ToZ24S8(data, width, height);
+ ConvertS8Z24ToZ24S8(data, width, height, false);
break;
case PixelFormat::G8R8U:
@@ -825,22 +851,36 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
}
}
+/**
+ * Helper function to perform software conversion (as needed) when flushing a buffer from OpenGL to
+ * Switch memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or
+ * with typical desktop GPUs.
+ */
+static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
+ u32 width, u32 height) {
+ switch (pixel_format) {
+ case PixelFormat::G8R8U:
+ case PixelFormat::G8R8S:
+ case PixelFormat::ASTC_2D_4X4:
+ case PixelFormat::ASTC_2D_8X8: {
+ LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented",
+ static_cast<u32>(pixel_format));
+ UNREACHABLE();
+ break;
+ }
+ case PixelFormat::S8Z24:
+ // Convert the Z24S8 depth format to S8Z24, as OpenGL does not support S8Z24.
+ ConvertS8Z24ToZ24S8(data, width, height, true);
+ break;
+ }
+}
+
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
void CachedSurface::LoadGLBuffer() {
- ASSERT(params.type != SurfaceType::Fill);
-
- const u8* const texture_src_data = Memory::GetPointer(params.addr);
-
- ASSERT(texture_src_data);
-
- const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format);
- const u32 copy_size = params.width * params.height * bytes_per_pixel;
- const std::size_t total_size = copy_size * params.depth;
-
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
+ gl_buffer.resize(params.size_in_bytes_gl);
if (params.is_tiled) {
- gl_buffer.resize(total_size);
u32 depth = params.depth;
u32 block_depth = params.block_depth;
@@ -853,13 +893,12 @@ void CachedSurface::LoadGLBuffer() {
block_depth = 1U;
}
- const std::size_t size = copy_size * depth;
-
morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(),
- size, params.addr);
+ gl_buffer.size(), params.addr);
} else {
- const u8* const texture_src_data_end{texture_src_data + total_size};
+ const auto texture_src_data{Memory::GetPointer(params.addr)};
+ const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl};
gl_buffer.assign(texture_src_data, texture_src_data_end);
}
@@ -868,7 +907,44 @@ void CachedSurface::LoadGLBuffer() {
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
void CachedSurface::FlushGLBuffer() {
- ASSERT_MSG(false, "Unimplemented");
+ MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
+
+ ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented");
+
+ // OpenGL temporary buffer needs to be big enough to store raw texture size
+ gl_buffer.resize(GetSizeInBytes());
+
+ const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
+ // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
+ ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0);
+ glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
+ ASSERT(!tuple.compressed);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+ glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, gl_buffer.size(),
+ gl_buffer.data());
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
+ params.height);
+ ASSERT(params.type != SurfaceType::Fill);
+ const u8* const texture_src_data = Memory::GetPointer(params.addr);
+ ASSERT(texture_src_data);
+ if (params.is_tiled) {
+ u32 depth = params.depth;
+ u32 block_depth = params.block_depth;
+
+ ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
+ params.block_width, static_cast<u32>(params.target));
+
+ if (params.target == SurfaceParams::SurfaceTarget::Texture2D) {
+ // TODO(Blinkhawk): Eliminate this condition once all texture types are implemented.
+ depth = 1U;
+ }
+ gl_to_morton_fns[static_cast<size_t>(params.pixel_format)](
+ params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(),
+ gl_buffer.size(), GetAddr());
+ } else {
+ std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer.data(), GetSizeInBytes());
+ }
}
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
@@ -878,9 +954,6 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
MICROPROFILE_SCOPE(OpenGL_TextureUL);
- ASSERT(gl_buffer.size() == static_cast<std::size_t>(params.width) * params.height *
- GetGLBytesPerPixel(params.pixel_format) * params.depth);
-
const auto& rect{params.GetRect()};
// Load data from memory to the surface
@@ -889,7 +962,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
std::size_t buffer_offset =
static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.width +
static_cast<std::size_t>(x0)) *
- GetGLBytesPerPixel(params.pixel_format);
+ SurfaceParams::GetBytesPerPixel(params.pixel_format);
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
const GLuint target_tex = texture.handle;
@@ -905,7 +978,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
cur_state.Apply();
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
- ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0);
+ ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.width));
glActiveTexture(GL_TEXTURE0);
@@ -915,7 +988,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
glCompressedTexImage2D(
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0,
- static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
+ static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
break;
case SurfaceParams::SurfaceTarget::Texture3D:
case SurfaceParams::SurfaceTarget::Texture2DArray:
@@ -923,16 +996,16 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height),
static_cast<GLsizei>(params.depth), 0,
- static_cast<GLsizei>(params.size_in_bytes_total), &gl_buffer[buffer_offset]);
+ static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
break;
case SurfaceParams::SurfaceTarget::TextureCubemap:
for (std::size_t face = 0; face < params.depth; ++face) {
glCompressedTexImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face),
0, tuple.internal_format, static_cast<GLsizei>(params.width),
static_cast<GLsizei>(params.height), 0,
- static_cast<GLsizei>(params.size_in_bytes_2d),
+ static_cast<GLsizei>(params.SizeInBytesCubeFaceGL()),
&gl_buffer[buffer_offset]);
- buffer_offset += params.size_in_bytes_2d;
+ buffer_offset += params.SizeInBytesCubeFace();
}
break;
default:
@@ -942,7 +1015,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
glCompressedTexImage2D(
GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
static_cast<GLsizei>(params.height), 0,
- static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
+ static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
}
} else {
@@ -971,7 +1044,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
y0, static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
&gl_buffer[buffer_offset]);
- buffer_offset += params.size_in_bytes_2d;
+ buffer_offset += params.SizeInBytesCubeFace();
}
break;
default:
@@ -1033,10 +1106,7 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre
void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
surface->LoadGLBuffer();
surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle);
-}
-
-void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
- surface->FlushGLBuffer();
+ surface->MarkAsModified(false, *this);
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
@@ -1053,8 +1123,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
} else if (preserve_contents) {
// If surface parameters changed and we care about keeping the previous data, recreate
// the surface from the old one
- Unregister(surface);
Surface new_surface{RecreateSurface(surface, params)};
+ Unregister(surface);
Register(new_surface);
return new_surface;
} else {
@@ -1105,6 +1175,14 @@ void RasterizerCacheOpenGL::FermiCopySurface(
FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false));
}
+void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface,
+ const Surface& dst_surface) {
+ const auto& src_params{src_surface->GetSurfaceParams()};
+ const auto& dst_params{dst_surface->GetSurfaceParams()};
+ FlushRegion(src_params.addr, dst_params.size_in_bytes);
+ LoadSurface(dst_surface);
+}
+
Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
const SurfaceParams& new_params) {
// Verify surface is compatible for blitting
@@ -1113,6 +1191,12 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
// Get a new surface with the new parameters, and blit the previous surface to it
Surface new_surface{GetUncachedSurface(new_params)};
+ // With use_accurate_gpu_emulation enabled, do an accurate surface copy
+ if (Settings::values.use_accurate_gpu_emulation) {
+ AccurateCopySurface(old_surface, new_surface);
+ return new_surface;
+ }
+
// For compatible surfaces, we can just do fast glCopyImageSubData based copy
if (old_params.target == new_params.target && old_params.type == new_params.type &&
old_params.depth == new_params.depth && old_params.depth == 1 &&
@@ -1124,11 +1208,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
// If the format is the same, just do a framebuffer blit. This is significantly faster than
// using PBOs. The is also likely less accurate, as textures will be converted rather than
- // reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate
+ // reinterpreted. When use_accurate_gpu_emulation setting is enabled, perform a more accurate
// surface copy, where pixels are reinterpreted as a new format (without conversion). This
// code path uses OpenGL PBOs and is quite slow.
- const bool is_blit{old_params.pixel_format == new_params.pixel_format ||
- !Settings::values.use_accurate_framebuffers};
+ const bool is_blit{old_params.pixel_format == new_params.pixel_format};
switch (new_params.target) {
case SurfaceParams::SurfaceTarget::Texture2D:
@@ -1138,6 +1221,9 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
CopySurface(old_surface, new_surface, copy_pbo.handle);
}
break;
+ case SurfaceParams::SurfaceTarget::Texture3D:
+ AccurateCopySurface(old_surface, new_surface);
+ break;
case SurfaceParams::SurfaceTarget::TextureCubemap: {
if (old_params.rt.array_mode != 1) {
// TODO(bunnei): This is used by Breath of the Wild, I'm not sure how to implement this
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 0b8ae3eb4..0dd0d90a3 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -18,6 +18,7 @@
#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
+#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace OpenGL {
@@ -131,6 +132,8 @@ struct SurfaceParams {
case Tegra::Texture::TextureType::Texture2D:
case Tegra::Texture::TextureType::Texture2DNoMipmap:
return SurfaceTarget::Texture2D;
+ case Tegra::Texture::TextureType::Texture3D:
+ return SurfaceTarget::Texture3D;
case Tegra::Texture::TextureType::TextureCubemap:
return SurfaceTarget::TextureCubemap;
case Tegra::Texture::TextureType::Texture1DArray:
@@ -701,21 +704,42 @@ struct SurfaceParams {
return SurfaceType::Invalid;
}
+ /// Returns the sizer in bytes of the specified pixel format
+ static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) {
+ if (pixel_format == SurfaceParams::PixelFormat::Invalid) {
+ return 0;
+ }
+ return GetFormatBpp(pixel_format) / CHAR_BIT;
+ }
+
/// Returns the rectangle corresponding to this surface
MathUtil::Rectangle<u32> GetRect() const;
- /// Returns the size of this surface as a 2D texture in bytes, adjusted for compression
- std::size_t SizeInBytes2D() const {
+ /// Returns the total size of this surface in bytes, adjusted for compression
+ std::size_t SizeInBytesRaw(bool ignore_tiled = false) const {
const u32 compression_factor{GetCompressionFactor(pixel_format)};
- ASSERT(width % compression_factor == 0);
- ASSERT(height % compression_factor == 0);
- return (width / compression_factor) * (height / compression_factor) *
- GetFormatBpp(pixel_format) / CHAR_BIT;
+ const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
+ const size_t uncompressed_size{
+ Tegra::Texture::CalculateSize((ignore_tiled ? false : is_tiled), bytes_per_pixel, width,
+ height, depth, block_height, block_depth)};
+
+ // Divide by compression_factor^2, as height and width are factored by this
+ return uncompressed_size / (compression_factor * compression_factor);
}
- /// Returns the total size of this surface in bytes, adjusted for compression
- std::size_t SizeInBytesTotal() const {
- return SizeInBytes2D() * depth;
+ /// Returns the size of this surface as an OpenGL texture in bytes
+ std::size_t SizeInBytesGL() const {
+ return SizeInBytesRaw(true);
+ }
+
+ /// Returns the size of this surface as a cube face in bytes
+ std::size_t SizeInBytesCubeFace() const {
+ return size_in_bytes / 6;
+ }
+
+ /// Returns the size of this surface as an OpenGL cube face in bytes
+ std::size_t SizeInBytesCubeFaceGL() const {
+ return size_in_bytes_gl / 6;
}
/// Creates SurfaceParams from a texture configuration
@@ -742,7 +766,9 @@ struct SurfaceParams {
other.depth);
}
- VAddr addr;
+ /// Initializes parameters for caching, should be called after everything has been initialized
+ void InitCacheParameters(Tegra::GPUVAddr gpu_addr);
+
bool is_tiled;
u32 block_width;
u32 block_height;
@@ -754,15 +780,20 @@ struct SurfaceParams {
u32 height;
u32 depth;
u32 unaligned_height;
- std::size_t size_in_bytes_total;
- std::size_t size_in_bytes_2d;
SurfaceTarget target;
u32 max_mip_level;
+ // Parameters used for caching
+ VAddr addr;
+ Tegra::GPUVAddr gpu_addr;
+ std::size_t size_in_bytes;
+ std::size_t size_in_bytes_gl;
+
// Render target specific parameters, not used in caching
struct {
u32 index;
u32 array_mode;
+ u32 volume;
u32 layer_stride;
u32 base_layer;
} rt;
@@ -775,7 +806,8 @@ struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> {
static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) {
SurfaceReserveKey res;
res.state = params;
- res.state.rt = {}; // Ignore rt config in caching
+ res.state.gpu_addr = {}; // Ignore GPU vaddr in caching
+ res.state.rt = {}; // Ignore rt config in caching
return res;
}
};
@@ -790,16 +822,20 @@ struct hash<SurfaceReserveKey> {
namespace OpenGL {
-class CachedSurface final {
+class CachedSurface final : public RasterizerCacheObject {
public:
CachedSurface(const SurfaceParams& params);
- VAddr GetAddr() const {
+ VAddr GetAddr() const override {
return params.addr;
}
- std::size_t GetSizeInBytes() const {
- return params.size_in_bytes_total;
+ std::size_t GetSizeInBytes() const override {
+ return cached_size_in_bytes;
+ }
+
+ void Flush() override {
+ FlushGLBuffer();
}
const OGLTexture& Texture() const {
@@ -810,13 +846,6 @@ public:
return gl_target;
}
- static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) {
- if (format == SurfaceParams::PixelFormat::Invalid)
- return 0;
-
- return SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
- }
-
const SurfaceParams& GetSurfaceParams() const {
return params;
}
@@ -833,6 +862,7 @@ private:
std::vector<u8> gl_buffer;
SurfaceParams params;
GLenum gl_target;
+ std::size_t cached_size_in_bytes;
};
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
@@ -849,9 +879,6 @@ public:
/// Get the color surface based on the framebuffer configuration and the specified render target
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
- /// Flushes the surface to Switch memory
- void FlushSurface(const Surface& surface);
-
/// Tries to find a framebuffer using on the provided CPU address
Surface TryFindFramebufferSurface(VAddr addr) const;
@@ -875,6 +902,9 @@ private:
/// Tries to get a reserved surface for the specified parameters
Surface TryGetReservedSurface(const SurfaceParams& params);
+ /// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data
+ void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface);
+
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
/// previously been used. This is to prevent surfaces from being constantly created and
/// destroyed when used with different surface parameters.
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 7bb287f56..a210f1731 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -19,20 +19,21 @@ class CachedShader;
using Shader = std::shared_ptr<CachedShader>;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-class CachedShader final {
+class CachedShader final : public RasterizerCacheObject {
public:
CachedShader(VAddr addr, Maxwell::ShaderProgram program_type);
- /// Gets the address of the shader in guest memory, required for cache management
- VAddr GetAddr() const {
+ VAddr GetAddr() const override {
return addr;
}
- /// Gets the size of the shader in guest memory, required for cache management
- std::size_t GetSizeInBytes() const {
+ std::size_t GetSizeInBytes() const override {
return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64);
}
+ // We do not have to flush this cache as things in it are never modified by us.
+ void Flush() override {}
+
/// Gets the shader entries for the shader
const GLShader::ShaderEntries& GetShaderEntries() const {
return entries;
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index ca063d90d..f4340a017 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -1142,6 +1142,7 @@ private:
case Tegra::Shader::TextureType::Texture2D: {
return 2;
}
+ case Tegra::Shader::TextureType::Texture3D:
case Tegra::Shader::TextureType::TextureCube: {
return 3;
}
@@ -2036,9 +2037,9 @@ private:
break;
}
case OpCode::Id::TEX: {
- ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented");
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
std::string coord;
+ const bool is_array = instr.tex.array != 0;
ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
@@ -2053,21 +2054,59 @@ private:
switch (num_coordinates) {
case 1: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- coord = "float coords = " + x + ';';
+ if (is_array) {
+ const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ coord = "vec2 coords = vec2(" + x + ", " + index + ");";
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ coord = "float coords = " + x + ';';
+ }
break;
}
case 2: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- coord = "vec2 coords = vec2(" + x + ", " + y + ");";
+ if (is_array) {
+ const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
+ coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");";
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ coord = "vec2 coords = vec2(" + x + ", " + y + ");";
+ }
break;
}
case 3: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
- coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
+ if (depth_compare) {
+ if (is_array) {
+ const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
+ coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index +
+ ");";
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
+ coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
+ }
+ } else {
+ if (is_array) {
+ const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 3);
+ coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index +
+ ");";
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
+ coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
+ }
+ }
break;
}
default:
@@ -2086,7 +2125,7 @@ private:
std::string op_c;
const std::string sampler =
- GetSampler(instr.sampler, texture_type, false, depth_compare);
+ GetSampler(instr.sampler, texture_type, is_array, depth_compare);
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
@@ -2106,10 +2145,13 @@ private:
}
case Tegra::Shader::TextureProcessMode::LB:
case Tegra::Shader::TextureProcessMode::LBA: {
- if (num_coordinates <= 2) {
- op_c = regs.GetRegisterAsFloat(instr.gpr20);
+ if (depth_compare) {
+ if (is_array)
+ op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 2);
+ else
+ op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
} else {
- op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
+ op_c = regs.GetRegisterAsFloat(instr.gpr20);
}
// TODO: Figure if A suffix changes the equation at all.
texture = "texture(" + sampler + ", coords, " + op_c + ')';
@@ -2252,6 +2294,8 @@ private:
ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ),
"MZ is not implemented");
+ u32 op_c_offset = 0;
+
switch (texture_type) {
case Tegra::Shader::TextureType::Texture1D: {
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
@@ -2266,6 +2310,7 @@ private:
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
const std::string y = regs.GetRegisterAsInteger(instr.gpr20);
coord = "ivec2 coords = ivec2(" + x + ", " + y + ");";
+ op_c_offset = 1;
}
break;
}
@@ -2277,13 +2322,14 @@ private:
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, false);
std::string texture = "texelFetch(" + sampler + ", coords, 0)";
- const std::string op_c = regs.GetRegisterAsInteger(instr.gpr20.Value() + 1);
switch (instr.tlds.GetTextureProcessMode()) {
case Tegra::Shader::TextureProcessMode::LZ: {
texture = "texelFetch(" + sampler + ", coords, 0)";
break;
}
case Tegra::Shader::TextureProcessMode::LL: {
+ const std::string op_c =
+ regs.GetRegisterAsInteger(instr.gpr20.Value() + op_c_offset);
texture = "texelFetch(" + sampler + ", coords, " + op_c + ')';
break;
}
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 7fec15991..71c6ebb41 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -85,8 +85,8 @@ void Config::ReadValues() {
Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool();
Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt();
- Settings::values.use_accurate_framebuffers =
- qt_config->value("use_accurate_framebuffers", false).toBool();
+ Settings::values.use_accurate_gpu_emulation =
+ qt_config->value("use_accurate_gpu_emulation", false).toBool();
Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
@@ -233,7 +233,7 @@ void Config::SaveValues() {
qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit);
qt_config->setValue("frame_limit", Settings::values.frame_limit);
- qt_config->setValue("use_accurate_framebuffers", Settings::values.use_accurate_framebuffers);
+ qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation);
// Cast to double because Qt's written float values are not human-readable
qt_config->setValue("bg_red", (double)Settings::values.bg_red);
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index cd1549462..8290b4384 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -75,7 +75,7 @@ void ConfigureGraphics::setConfiguration() {
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
ui->frame_limit->setValue(Settings::values.frame_limit);
- ui->use_accurate_framebuffers->setChecked(Settings::values.use_accurate_framebuffers);
+ ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
Settings::values.bg_blue);
ui->bg_button->setStyleSheet(
@@ -87,7 +87,7 @@ void ConfigureGraphics::applyConfiguration() {
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
Settings::values.frame_limit = ui->frame_limit->value();
- Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked();
+ Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
Settings::values.bg_red = static_cast<float>(bg_color.redF());
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 8fc00af1b..91fcad994 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -50,9 +50,9 @@
</layout>
</item>
<item>
- <widget class="QCheckBox" name="use_accurate_framebuffers">
+ <widget class="QCheckBox" name="use_accurate_gpu_emulation">
<property name="text">
- <string>Use accurate framebuffers (slow)</string>
+ <string>Use accurate GPU emulation (slow)</string>
</property>
</widget>
</item>
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 2470f4640..5e42e48b2 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -99,8 +99,8 @@ void Config::ReadValues() {
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
- Settings::values.use_accurate_framebuffers =
- sdl2_config->GetBoolean("Renderer", "use_accurate_framebuffers", false);
+ Settings::values.use_accurate_gpu_emulation =
+ sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 762396e3b..a97b75f7b 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -110,9 +110,9 @@ use_frame_limit =
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
frame_limit =
-# Whether to use accurate framebuffers
+# Whether to use accurate GPU emulation
# 0 (default): Off (fast), 1 : On (slow)
-use_accurate_framebuffers =
+use_accurate_gpu_emulation =
# The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0.0-1.0. Defaults to 1.0 for all.