From 5e69b76f9259335108af7833c0313ee3244b1a88 Mon Sep 17 00:00:00 2001 From: Anon Date: Sat, 30 Jul 2016 09:39:02 -0500 Subject: [PATCH] InputCore overhaul --- src/CMakeLists.txt | 1 + src/citra/CMakeLists.txt | 2 +- src/citra/config.cpp | 7 +- src/citra/emu_window/emu_window_sdl2.cpp | 19 +- src/citra_qt/CMakeLists.txt | 8 +- src/citra_qt/bootmanager.cpp | 104 ++- src/citra_qt/config.cpp | 9 +- src/citra_qt/configure_input.cpp | 2 +- src/common/CMakeLists.txt | 2 - src/common/emu_window.cpp | 19 +- src/common/key_map.cpp | 139 ---- src/common/key_map.h | 96 --- src/common/logging/log.h | 1 + src/core/hle/service/hid/hid.cpp | 456 ++++++------- src/core/hw/gpu.cpp | 826 +++++++++++------------ src/core/settings.h | 210 ++++-- src/core/system.cpp | 3 + src/input_core/CMakeLists.txt | 28 + src/input_core/devices/IDevice.h | 18 + src/input_core/devices/Keyboard.cpp | 55 ++ src/input_core/devices/Keyboard.h | 48 ++ src/input_core/devices/SDLGamepad.cpp | 86 +++ src/input_core/devices/SDLGamepad.h | 48 ++ src/input_core/input_core.cpp | 108 +++ src/input_core/input_core.h | 26 + src/input_core/key_map.cpp | 63 ++ src/input_core/key_map.h | 63 ++ 27 files changed, 1401 insertions(+), 1046 deletions(-) delete mode 100644 src/common/key_map.cpp delete mode 100644 src/common/key_map.h create mode 100644 src/input_core/CMakeLists.txt create mode 100644 src/input_core/devices/IDevice.h create mode 100644 src/input_core/devices/Keyboard.cpp create mode 100644 src/input_core/devices/Keyboard.h create mode 100644 src/input_core/devices/SDLGamepad.cpp create mode 100644 src/input_core/devices/SDLGamepad.h create mode 100644 src/input_core/input_core.cpp create mode 100644 src/input_core/input_core.h create mode 100644 src/input_core/key_map.cpp create mode 100644 src/input_core/key_map.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e1245160..6eddedb0b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories(.) add_subdirectory(common) add_subdirectory(core) add_subdirectory(video_core) +add_subdirectory(input_core) add_subdirectory(audio_core) add_subdirectory(tests) if (ENABLE_SDL2) diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index 43fa06b4e..6298db77d 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -16,7 +16,7 @@ create_directory_groups(${SRCS} ${HEADERS}) include_directories(${SDL2_INCLUDE_DIR}) add_executable(citra ${SRCS} ${HEADERS}) -target_link_libraries(citra core video_core audio_core common) +target_link_libraries(citra core video_core audio_core input_core common) target_link_libraries(citra ${SDL2_LIBRARY} ${OPENGL_gl_LIBRARY} inih glad) if (MSVC) target_link_libraries(citra getopt) diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 22cb51ea8..e15063953 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -52,15 +52,14 @@ static const std::array defaults = { SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L, // indirectly mapped keys - SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, - SDL_SCANCODE_D, + SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT }; void Config::ReadValues() { // Controls for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - Settings::values.input_mappings[Settings::NativeInput::All[i]] = - sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]); + //Settings::values.input_mappings[Settings::NativeInput::All[i]] = + // sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]); } Settings::values.pad_circle_modifier_scale = (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5); diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 591f68aa4..ffc7f9cbf 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -11,7 +11,6 @@ #include -#include "common/key_map.h" #include "common/logging/log.h" #include "common/scm_rev.h" #include "common/string_util.h" @@ -33,16 +32,18 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { if (state == SDL_PRESSED) { TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); - } else { + } + else { TouchReleased(); } } void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { if (state == SDL_PRESSED) { - KeyMap::PressKey(*this, { key, keyboard_id }); - } else if (state == SDL_RELEASED) { - KeyMap::ReleaseKey(*this, { key, keyboard_id }); + //KeyMap::PressKey(*this, key); + } + else if (state == SDL_RELEASED) { + //KeyMap::ReleaseKey(*this, key); } } @@ -59,7 +60,7 @@ void EmuWindow_SDL2::OnResize() { } EmuWindow_SDL2::EmuWindow_SDL2() { - keyboard_id = KeyMap::NewDeviceId(); + keyboard_id = 0; ReloadSetKeymaps(); @@ -168,12 +169,12 @@ void EmuWindow_SDL2::DoneCurrent() { } void EmuWindow_SDL2::ReloadSetKeymaps() { - KeyMap::ClearKeyMapping(keyboard_id); + //KeyMap::ClearKeyMapping(); for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]); + //KeyMap::SetKeyMapping( Settings::values.input_mappings[Settings::NativeInput::All[i]] , KeyMap::mapping_targets[i]); } } void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(const std::pair& minimal_size) { SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); -} +} \ No newline at end of file diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 4402ad995..c80ea2a76 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -22,8 +22,8 @@ set(SRCS configure_debug.cpp configure_dialog.cpp configure_general.cpp - configure_system.cpp configure_input.cpp + configure_system.cpp game_list.cpp hotkeys.cpp main.cpp @@ -54,8 +54,8 @@ set(HEADERS configure_debug.h configure_dialog.h configure_general.h - configure_system.h configure_input.h + configure_system.h game_list.h game_list_p.h hotkeys.h @@ -73,8 +73,8 @@ set(UIS configure_audio.ui configure_debug.ui configure_general.ui - configure_system.ui configure_input.ui + configure_system.ui hotkeys.ui main.ui ) @@ -95,7 +95,7 @@ if (APPLE) else() add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) endif() -target_link_libraries(citra-qt core video_core audio_core common qhexedit) +target_link_libraries(citra-qt core video_core audio_core input_core common qhexedit) target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS}) target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 414b2f8af..452c3e6ba 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -10,7 +10,6 @@ #include "citra_qt/bootmanager.h" -#include "common/key_map.h" #include "common/microprofile.h" #include "common/scm_rev.h" #include "common/string_util.h" @@ -18,6 +17,8 @@ #include "core/core.h" #include "core/settings.h" #include "core/system.h" +#include "input_core/input_core.h" +#include "input_core\devices\Keyboard.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/video_core.h" @@ -52,7 +53,8 @@ void EmuThread::run() { was_active = running || exec_step; if (!was_active && !stop_run) emit DebugModeEntered(); - } else if (exec_step) { + } + else if (exec_step) { if (!was_active) emit DebugModeLeft(); @@ -62,9 +64,10 @@ void EmuThread::run() { yieldCurrentThread(); was_active = false; - } else { + } + else { std::unique_lock lock(running_mutex); - running_cv.wait(lock, [this]{ return IsRunning() || exec_step || stop_run; }); + running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; }); } } @@ -80,11 +83,10 @@ void EmuThread::run() { // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL context. // The corresponding functionality is handled in EmuThread instead -class GGLWidgetInternal : public QGLWidget -{ +class GGLWidgetInternal : public QGLWidget { public: GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) - : QGLWidget(fmt, parent), parent(parent) { + : QGLWidget(fmt, parent), parent(parent) { } void paintEvent(QPaintEvent* ev) override { @@ -108,16 +110,15 @@ private: GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : QWidget(parent), keyboard_id(0), emu_thread(emu_thread) { - std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); - keyboard_id = KeyMap::NewDeviceId(); + keyboard_id = 0; ReloadSetKeymaps(); // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, WA_DontShowOnScreen, WA_DeleteOnClose QGLFormat fmt; - fmt.setVersion(3,3); + fmt.setVersion(3, 3); fmt.setProfile(QGLFormat::CoreProfile); // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X fmt.setOption(QGL::NoDeprecatedFunctions); @@ -133,14 +134,12 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); OnFramebufferSizeChanged(); - NotifyClientAreaSizeChanged(std::pair(child->width(), child->height())); + NotifyClientAreaSizeChanged(std::pair(child->width(), child->height())); BackupGeometry(); - } -void GRenderWindow::moveContext() -{ +void GRenderWindow::moveContext() { DoneCurrent(); // We need to move GL context to the swapping thread in Qt5 #if QT_VERSION > QT_VERSION_CHECK(5, 0, 0) @@ -150,8 +149,7 @@ void GRenderWindow::moveContext() #endif } -void GRenderWindow::SwapBuffers() -{ +void GRenderWindow::SwapBuffers() { #if !defined(QT_NO_DEBUG) // Qt debug runtime prints a bogus warning on the console if you haven't called makeCurrent // since the last time you called swapBuffers. This presumably means something if you're using @@ -162,13 +160,11 @@ void GRenderWindow::SwapBuffers() child->swapBuffers(); } -void GRenderWindow::MakeCurrent() -{ +void GRenderWindow::MakeCurrent() { child->makeCurrent(); } -void GRenderWindow::DoneCurrent() -{ +void GRenderWindow::DoneCurrent() { child->doneCurrent(); } @@ -180,8 +176,7 @@ void GRenderWindow::PollEvents() { // Older versions get the window size (density independent pixels), // and hence, do not support DPI scaling ("retina" displays). // The result will be a viewport that is smaller than the extent of the window. -void GRenderWindow::OnFramebufferSizeChanged() -{ +void GRenderWindow::OnFramebufferSizeChanged() { // Screen changes potentially incur a change in screen DPI, hence we should update the framebuffer size qreal pixelRatio = windowPixelRatio(); unsigned width = child->QPaintDevice::width() * pixelRatio; @@ -190,26 +185,22 @@ void GRenderWindow::OnFramebufferSizeChanged() NotifyFramebufferLayoutChanged(EmuWindow::FramebufferLayout::DefaultScreenLayout(width, height)); } -void GRenderWindow::BackupGeometry() -{ +void GRenderWindow::BackupGeometry() { geometry = ((QGLWidget*)this)->saveGeometry(); } -void GRenderWindow::RestoreGeometry() -{ +void GRenderWindow::RestoreGeometry() { // We don't want to back up the geometry here (obviously) QWidget::restoreGeometry(geometry); } -void GRenderWindow::restoreGeometry(const QByteArray& geometry) -{ +void GRenderWindow::restoreGeometry(const QByteArray& geometry) { // Make sure users of this class don't need to deal with backing up the geometry themselves QWidget::restoreGeometry(geometry); BackupGeometry(); } -QByteArray GRenderWindow::saveGeometry() -{ +QByteArray GRenderWindow::saveGeometry() { // If we are a top-level widget, store the current geometry // otherwise, store the last backup if (parent() == nullptr) @@ -218,8 +209,7 @@ QByteArray GRenderWindow::saveGeometry() return geometry; } -qreal GRenderWindow::windowPixelRatio() -{ +qreal GRenderWindow::windowPixelRatio() { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) // windowHandle() might not be accessible until the window is displayed to screen. return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; @@ -233,55 +223,51 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); } -void GRenderWindow::keyPressEvent(QKeyEvent* event) -{ - KeyMap::PressKey(*this, { event->key(), keyboard_id }); +void GRenderWindow::keyPressEvent(QKeyEvent* event) { + auto& keyboard = InputCore::main_keyboard; + KeyboardKey param = KeyboardKey(event->key(), event->nativeScanCode(), QKeySequence(event->key()).toString().toStdString()); + keyboard->KeyPressed(param); } -void GRenderWindow::keyReleaseEvent(QKeyEvent* event) -{ - KeyMap::ReleaseKey(*this, { event->key(), keyboard_id }); +void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { + auto& keyboard = InputCore::main_keyboard; + KeyboardKey param = KeyboardKey(event->key(), event->nativeScanCode(), QKeySequence(event->key()).toString().toStdString()); + keyboard->KeyReleased(param); } -void GRenderWindow::mousePressEvent(QMouseEvent *event) -{ - if (event->button() == Qt::LeftButton) - { +void GRenderWindow::mousePressEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton) { auto pos = event->pos(); qreal pixelRatio = windowPixelRatio(); this->TouchPressed(static_cast(pos.x() * pixelRatio), - static_cast(pos.y() * pixelRatio)); + static_cast(pos.y() * pixelRatio)); } } -void GRenderWindow::mouseMoveEvent(QMouseEvent *event) -{ +void GRenderWindow::mouseMoveEvent(QMouseEvent *event) { auto pos = event->pos(); qreal pixelRatio = windowPixelRatio(); this->TouchMoved(std::max(static_cast(pos.x() * pixelRatio), 0u), - std::max(static_cast(pos.y() * pixelRatio), 0u)); + std::max(static_cast(pos.y() * pixelRatio), 0u)); } -void GRenderWindow::mouseReleaseEvent(QMouseEvent *event) -{ +void GRenderWindow::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) this->TouchReleased(); } -void GRenderWindow::ReloadSetKeymaps() -{ - KeyMap::ClearKeyMapping(keyboard_id); +void GRenderWindow::ReloadSetKeymaps() { + //KeyMap::ClearKeyMapping(); for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { - KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]); + //KeyMap::SetKeyMapping( Settings::values.input_mappings[Settings::NativeInput::All[i]], KeyMap::mapping_targets[i]); } } -void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) -{ +void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) { NotifyClientAreaSizeChanged(std::make_pair(width, height)); } -void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair& minimal_size) { +void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair& minimal_size) { setMinimumSize(minimal_size.first, minimal_size.second); } @@ -299,7 +285,7 @@ void GRenderWindow::showEvent(QShowEvent * event) { QWidget::showEvent(event); // windowHandle() is not initialized until the Window is shown, so we connect it here. - #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this, SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection); - #endif -} +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + connect(this->windowHandle(), SIGNAL(screenChanged(QScreen*)), this, SLOT(OnFramebufferSizeChanged()), Qt::UniqueConnection); +#endif +} \ No newline at end of file diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 0e5f285c0..a2b4683d9 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -2,8 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include - #include "citra_qt/config.h" #include "citra_qt/ui_settings.h" @@ -27,15 +25,14 @@ const std::array Config::defaults = Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, // indirectly mapped keys - Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, - Qt::Key_D, + Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right }; void Config::ReadValues() { qt_config->beginGroup("Controls"); for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { Settings::values.input_mappings[Settings::NativeInput::All[i]] = - qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]).toInt(); + Settings::InputDeviceMapping(qt_config->value(Settings::NativeInput::Mapping[i], defaults[i]).toString().toStdString()); } Settings::values.pad_circle_modifier_scale = qt_config->value("pad_circle_modifier_scale", 0.5).toFloat(); qt_config->endGroup(); @@ -126,7 +123,7 @@ void Config::SaveValues() { qt_config->beginGroup("Controls"); for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]), - Settings::values.input_mappings[Settings::NativeInput::All[i]]); + QString::fromStdString(Settings::values.input_mappings[Settings::NativeInput::All[i]].Save())); } qt_config->setValue("pad_circle_modifier_scale", (double)Settings::values.pad_circle_modifier_scale); qt_config->endGroup(); diff --git a/src/citra_qt/configure_input.cpp b/src/citra_qt/configure_input.cpp index 9c7a67174..b376ca9df 100644 --- a/src/citra_qt/configure_input.cpp +++ b/src/citra_qt/configure_input.cpp @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index aa6eee2a3..e17f70818 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -6,7 +6,6 @@ set(SRCS emu_window.cpp file_util.cpp hash.cpp - key_map.cpp logging/filter.cpp logging/text_formatter.cpp logging/backend.cpp @@ -36,7 +35,6 @@ set(HEADERS emu_window.h file_util.h hash.h - key_map.h linear_disk_cache.h logging/text_formatter.h logging/filter.h diff --git a/src/common/emu_window.cpp b/src/common/emu_window.cpp index fd728c109..9560a3dfc 100644 --- a/src/common/emu_window.cpp +++ b/src/common/emu_window.cpp @@ -6,7 +6,6 @@ #include #include "common/assert.h" -#include "common/key_map.h" #include "emu_window.h" #include "video_core/video_core.h" @@ -43,19 +42,20 @@ void EmuWindow::CirclePadUpdated(float x, float y) { * @return True if the coordinates are within the touchpad, otherwise false */ static bool IsWithinTouchscreen(const EmuWindow::FramebufferLayout& layout, unsigned framebuffer_x, - unsigned framebuffer_y) { + unsigned framebuffer_y) { return (framebuffer_y >= layout.bottom_screen.top && - framebuffer_y < layout.bottom_screen.bottom && - framebuffer_x >= layout.bottom_screen.left && - framebuffer_x < layout.bottom_screen.right); + framebuffer_y < layout.bottom_screen.bottom && + framebuffer_x >= layout.bottom_screen.left && + framebuffer_x < layout.bottom_screen.right); } std::tuple EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) { + new_x = std::max(new_x, framebuffer_layout.bottom_screen.left); - new_x = std::min(new_x, framebuffer_layout.bottom_screen.right-1); + new_x = std::min(new_x, framebuffer_layout.bottom_screen.right - 1); new_y = std::max(new_y, framebuffer_layout.bottom_screen.top); - new_y = std::min(new_y, framebuffer_layout.bottom_screen.bottom-1); + new_y = std::min(new_y, framebuffer_layout.bottom_screen.bottom - 1); return std::make_tuple(new_x, new_y); } @@ -118,7 +118,8 @@ EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(u res.bottom_screen.right = res.bottom_screen.left + bottom_width; res.bottom_screen.top = res.top_screen.bottom; res.bottom_screen.bottom = res.bottom_screen.top + viewport_height / 2; - } else { + } + else { // Otherwise, apply borders to the left and right sides of the window. int viewport_width = static_cast(std::round(height / emulation_aspect_ratio)); @@ -138,4 +139,4 @@ EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(u } return res; -} +} \ No newline at end of file diff --git a/src/common/key_map.cpp b/src/common/key_map.cpp deleted file mode 100644 index ad311d66b..000000000 --- a/src/common/key_map.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include - -#include "common/emu_window.h" -#include "common/key_map.h" - -namespace KeyMap { - -// TODO (wwylele): currently we treat c-stick as four direction buttons -// and map it directly to EmuWindow::ButtonPressed. -// It should go the analog input way like circle pad does. -const std::array mapping_targets = {{ - Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y, - Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR, - Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_NONE, - Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT, - Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT, - - IndirectTarget::CirclePadUp, - IndirectTarget::CirclePadDown, - IndirectTarget::CirclePadLeft, - IndirectTarget::CirclePadRight, - IndirectTarget::CirclePadModifier, -}}; - -static std::map key_map; -static int next_device_id = 0; - -static bool circle_pad_up = false; -static bool circle_pad_down = false; -static bool circle_pad_left = false; -static bool circle_pad_right = false; -static bool circle_pad_modifier = false; - -static void UpdateCirclePad(EmuWindow& emu_window) { - constexpr float SQRT_HALF = 0.707106781; - int x = 0, y = 0; - - if (circle_pad_right) - ++x; - if (circle_pad_left) - --x; - if (circle_pad_up) - ++y; - if (circle_pad_down) - --y; - - float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0; - emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0 : SQRT_HALF), y * modifier * (x == 0 ? 1.0 : SQRT_HALF)); -} - -int NewDeviceId() { - return next_device_id++; -} - -void SetKeyMapping(HostDeviceKey key, KeyTarget target) { - key_map[key] = target; -} - -void ClearKeyMapping(int device_id) { - auto iter = key_map.begin(); - while (iter != key_map.end()) { - if (iter->first.device_id == device_id) - key_map.erase(iter++); - else - ++iter; - } -} - -void PressKey(EmuWindow& emu_window, HostDeviceKey key) { - auto target = key_map.find(key); - if (target == key_map.end()) - return; - - if (target->second.direct) { - emu_window.ButtonPressed({{target->second.target.direct_target_hex}}); - } else { - switch (target->second.target.indirect_target) { - case IndirectTarget::CirclePadUp: - circle_pad_up = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadDown: - circle_pad_down = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadLeft: - circle_pad_left = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadRight: - circle_pad_right = true; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadModifier: - circle_pad_modifier = true; - UpdateCirclePad(emu_window); - break; - } - } -} - -void ReleaseKey(EmuWindow& emu_window,HostDeviceKey key) { - auto target = key_map.find(key); - if (target == key_map.end()) - return; - - if (target->second.direct) { - emu_window.ButtonReleased({{target->second.target.direct_target_hex}}); - } else { - switch (target->second.target.indirect_target) { - case IndirectTarget::CirclePadUp: - circle_pad_up = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadDown: - circle_pad_down = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadLeft: - circle_pad_left = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadRight: - circle_pad_right = false; - UpdateCirclePad(emu_window); - break; - case IndirectTarget::CirclePadModifier: - circle_pad_modifier = false; - UpdateCirclePad(emu_window); - break; - } - } -} - -} diff --git a/src/common/key_map.h b/src/common/key_map.h deleted file mode 100644 index b62f017c6..000000000 --- a/src/common/key_map.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include "core/hle/service/hid/hid.h" - -class EmuWindow; - -namespace KeyMap { - -/** - * Represents key mapping targets that are not real 3DS buttons. - * They will be handled by KeyMap and translated to 3DS input. - */ -enum class IndirectTarget { - CirclePadUp, - CirclePadDown, - CirclePadLeft, - CirclePadRight, - CirclePadModifier, -}; - -/** - * Represents a key mapping target. It can be a PadState that represents real 3DS buttons, - * or an IndirectTarget. - */ -struct KeyTarget { - bool direct; - union { - u32 direct_target_hex; - IndirectTarget indirect_target; - } target; - - KeyTarget() : direct(true) { - target.direct_target_hex = 0; - } - - KeyTarget(Service::HID::PadState pad) : direct(true) { - target.direct_target_hex = pad.hex; - } - - KeyTarget(IndirectTarget i) : direct(false) { - target.indirect_target = i; - } -}; - -/** - * Represents a key for a specific host device. - */ -struct HostDeviceKey { - int key_code; - int device_id; ///< Uniquely identifies a host device - - bool operator<(const HostDeviceKey &other) const { - return std::tie(key_code, device_id) < - std::tie(other.key_code, other.device_id); - } - - bool operator==(const HostDeviceKey &other) const { - return std::tie(key_code, device_id) == - std::tie(other.key_code, other.device_id); - } -}; - -extern const std::array mapping_targets; - -/** - * Generates a new device id, which uniquely identifies a host device within KeyMap. - */ -int NewDeviceId(); - -/** - * Maps a device-specific key to a target (a PadState or an IndirectTarget). - */ -void SetKeyMapping(HostDeviceKey key, KeyTarget target); - -/** - * Clears all key mappings belonging to one device. - */ -void ClearKeyMapping(int device_id); - -/** - * Maps a key press action and call the corresponding function in EmuWindow - */ -void PressKey(EmuWindow& emu_window, HostDeviceKey key); - -/** - * Maps a key release action and call the corresponding function in EmuWindow - */ -void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key); - -} diff --git a/src/common/logging/log.h b/src/common/logging/log.h index c6910b1c7..b845c0934 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -82,6 +82,7 @@ enum class Class : ClassType { Audio_DSP, ///< The HLE implementation of the DSP Audio_Sink, ///< Emulator audio output backend Loader, ///< ROM loader + Input, ///< Input backend Count ///< Total number of logging classes }; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index cdec11388..9ff9d1615 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -15,301 +15,303 @@ #include "core/core_timing.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" +#include "input_core\input_core.h" #include "video_core/video_core.h" namespace Service { -namespace HID { + namespace HID { + // Handle to shared memory region designated to HID_User service + static Kernel::SharedPtr shared_mem; -// Handle to shared memory region designated to HID_User service -static Kernel::SharedPtr shared_mem; + // Event handles + static Kernel::SharedPtr event_pad_or_touch_1; + static Kernel::SharedPtr event_pad_or_touch_2; + static Kernel::SharedPtr event_accelerometer; + static Kernel::SharedPtr event_gyroscope; + static Kernel::SharedPtr event_debug_pad; -// Event handles -static Kernel::SharedPtr event_pad_or_touch_1; -static Kernel::SharedPtr event_pad_or_touch_2; -static Kernel::SharedPtr event_accelerometer; -static Kernel::SharedPtr event_gyroscope; -static Kernel::SharedPtr event_debug_pad; + static u32 next_pad_index; + static u32 next_touch_index; + static u32 next_accelerometer_index; + static u32 next_gyroscope_index; -static u32 next_pad_index; -static u32 next_touch_index; -static u32 next_accelerometer_index; -static u32 next_gyroscope_index; + static int enable_accelerometer_count = 0; // positive means enabled + static int enable_gyroscope_count = 0; // positive means enabled -static int enable_accelerometer_count = 0; // positive means enabled -static int enable_gyroscope_count = 0; // positive means enabled + static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { + constexpr float TAN30 = 0.577350269, TAN60 = 1 / TAN30; // 30 degree and 60 degree are angular thresholds for directions + constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; // a circle pad radius greater than 40 will trigger circle pad direction + PadState state; + state.hex = 0; -static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { - constexpr float TAN30 = 0.577350269, TAN60 = 1 / TAN30; // 30 degree and 60 degree are angular thresholds for directions - constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; // a circle pad radius greater than 40 will trigger circle pad direction - PadState state; - state.hex = 0; + if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) { + float t = std::abs(static_cast(circle_pad_y) / circle_pad_x); - if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) { - float t = std::abs(static_cast(circle_pad_y) / circle_pad_x); + if (circle_pad_x != 0 && t < TAN60) { + if (circle_pad_x > 0) + state.circle_right.Assign(1); + else + state.circle_left.Assign(1); + } - if (circle_pad_x != 0 && t < TAN60) { - if (circle_pad_x > 0) - state.circle_right.Assign(1); - else - state.circle_left.Assign(1); + if (circle_pad_x == 0 || t > TAN30) { + if (circle_pad_y > 0) + state.circle_up.Assign(1); + else + state.circle_down.Assign(1); + } + } + + return state; } - if (circle_pad_x == 0 || t > TAN30) { - if (circle_pad_y > 0) - state.circle_up.Assign(1); - else - state.circle_down.Assign(1); - } - } + void Update() { + if (shared_mem == nullptr) { + LOG_DEBUG(Service_HID, "shared_mem is null!"); + return; + } + SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); - return state; -} + if (mem == nullptr) { + LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!"); + return; + } -void Update() { - SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); + PadState state = InputCore::pad_state; - if (mem == nullptr) { - LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!"); - return; - } + // Get current circle pad position and update circle pad direction + s16 circle_pad_x, circle_pad_y; + std::tie(circle_pad_x, circle_pad_y) = InputCore::circle_pad; + state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; - PadState state = VideoCore::g_emu_window->GetPadState(); + mem->pad.current_state.hex = state.hex; + mem->pad.index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); - // Get current circle pad position and update circle pad direction - s16 circle_pad_x, circle_pad_y; - std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState(); - state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; + // Get the previous Pad state + u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); + PadState old_state = mem->pad.entries[last_entry_index].current_state; - mem->pad.current_state.hex = state.hex; - mem->pad.index = next_pad_index; - next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); + // Compute bitmask with 1s for bits different from the old state + PadState changed = { { (state.hex ^ old_state.hex) } }; - // Get the previous Pad state - u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); - PadState old_state = mem->pad.entries[last_entry_index].current_state; + // Get the current Pad entry + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; - // Compute bitmask with 1s for bits different from the old state - PadState changed = { { (state.hex ^ old_state.hex) } }; + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.circle_pad_x = circle_pad_x; + pad_entry.circle_pad_y = circle_pad_y; - // Get the current Pad entry - PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; + // If we just updated index 0, provide a new timestamp + if (mem->pad.index == 0) { + mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; + mem->pad.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } - // Update entry properties - pad_entry.current_state.hex = state.hex; - pad_entry.delta_additions.hex = changed.hex & state.hex; - pad_entry.delta_removals.hex = changed.hex & old_state.hex; - pad_entry.circle_pad_x = circle_pad_x; - pad_entry.circle_pad_y = circle_pad_y; + mem->touch.index = next_touch_index; + next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); - // If we just updated index 0, provide a new timestamp - if (mem->pad.index == 0) { - mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; - mem->pad.index_reset_ticks = (s64)CoreTiming::GetTicks(); - } + // Get the current touch entry + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; + bool pressed = false; - mem->touch.index = next_touch_index; - next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); + std::tie(touch_entry.x, touch_entry.y, pressed) = VideoCore::g_emu_window->GetTouchState(); + touch_entry.valid.Assign(pressed ? 1 : 0); - // Get the current touch entry - TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; - bool pressed = false; + // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which + // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being + // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). - std::tie(touch_entry.x, touch_entry.y, pressed) = VideoCore::g_emu_window->GetTouchState(); - touch_entry.valid.Assign(pressed ? 1 : 0); + // If we just updated index 0, provide a new timestamp + if (mem->touch.index == 0) { + mem->touch.index_reset_ticks_previous = mem->touch.index_reset_ticks; + mem->touch.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } - // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which - // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being - // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). + // Signal both handles when there's an update to Pad or touch + event_pad_or_touch_1->Signal(); + event_pad_or_touch_2->Signal(); - // If we just updated index 0, provide a new timestamp - if (mem->touch.index == 0) { - mem->touch.index_reset_ticks_previous = mem->touch.index_reset_ticks; - mem->touch.index_reset_ticks = (s64)CoreTiming::GetTicks(); - } + // Update accelerometer + if (enable_accelerometer_count > 0) { + mem->accelerometer.index = next_accelerometer_index; + next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); - // Signal both handles when there's an update to Pad or touch - event_pad_or_touch_1->Signal(); - event_pad_or_touch_2->Signal(); + AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; + std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) + = VideoCore::g_emu_window->GetAccelerometerState(); - // Update accelerometer - if (enable_accelerometer_count > 0) { - mem->accelerometer.index = next_accelerometer_index; - next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); + // Make up "raw" entry + // TODO(wwylele): + // From hardware testing, the raw_entry values are approximately, + // but not exactly, as twice as corresponding entries (or with a minus sign). + // It may caused by system calibration to the accelerometer. + // Figure out how it works, or, if no game reads raw_entry, + // the following three lines can be removed and leave raw_entry unimplemented. + mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x; + mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y; + mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z; - AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; - std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) - = VideoCore::g_emu_window->GetAccelerometerState(); + // If we just updated index 0, provide a new timestamp + if (mem->accelerometer.index == 0) { + mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks; + mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } - // Make up "raw" entry - // TODO(wwylele): - // From hardware testing, the raw_entry values are approximately, - // but not exactly, as twice as corresponding entries (or with a minus sign). - // It may caused by system calibration to the accelerometer. - // Figure out how it works, or, if no game reads raw_entry, - // the following three lines can be removed and leave raw_entry unimplemented. - mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x; - mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y; - mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z; + event_accelerometer->Signal(); + } - // If we just updated index 0, provide a new timestamp - if (mem->accelerometer.index == 0) { - mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks; - mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks(); + // Update gyroscope + if (enable_gyroscope_count > 0) { + mem->gyroscope.index = next_gyroscope_index; + next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); + + GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; + std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) + = VideoCore::g_emu_window->GetGyroscopeState(); + + // Make up "raw" entry + mem->gyroscope.raw_entry.x = gyroscope_entry.x; + mem->gyroscope.raw_entry.z = -gyroscope_entry.y; + mem->gyroscope.raw_entry.y = gyroscope_entry.z; + + // If we just updated index 0, provide a new timestamp + if (mem->gyroscope.index == 0) { + mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks; + mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } + + event_gyroscope->Signal(); + } } - event_accelerometer->Signal(); - } + void GetIPCHandles(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); - // Update gyroscope - if (enable_gyroscope_count > 0) { - mem->gyroscope.index = next_gyroscope_index; - next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); - - GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; - std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) - = VideoCore::g_emu_window->GetGyroscopeState(); - - // Make up "raw" entry - mem->gyroscope.raw_entry.x = gyroscope_entry.x; - mem->gyroscope.raw_entry.z = -gyroscope_entry.y; - mem->gyroscope.raw_entry.y = gyroscope_entry.z; - - // If we just updated index 0, provide a new timestamp - if (mem->gyroscope.index == 0) { - mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks; - mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks(); + cmd_buff[1] = 0; // No error + cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header + // TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) + cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).MoveFrom(); + cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).MoveFrom(); + cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).MoveFrom(); + cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).MoveFrom(); + cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).MoveFrom(); + cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).MoveFrom(); } - event_gyroscope->Signal(); - } -} + void EnableAccelerometer(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); -void GetIPCHandles(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + ++enable_accelerometer_count; + event_accelerometer->Signal(); - cmd_buff[1] = 0; // No error - cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header - // TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) - cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).MoveFrom(); - cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).MoveFrom(); - cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).MoveFrom(); - cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).MoveFrom(); - cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).MoveFrom(); -} + cmd_buff[1] = RESULT_SUCCESS.raw; -void EnableAccelerometer(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + LOG_DEBUG(Service_HID, "called"); + } - ++enable_accelerometer_count; - event_accelerometer->Signal(); + void DisableAccelerometer(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[1] = RESULT_SUCCESS.raw; + --enable_accelerometer_count; + event_accelerometer->Signal(); - LOG_DEBUG(Service_HID, "called"); -} + cmd_buff[1] = RESULT_SUCCESS.raw; -void DisableAccelerometer(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + LOG_DEBUG(Service_HID, "called"); + } - --enable_accelerometer_count; - event_accelerometer->Signal(); + void EnableGyroscopeLow(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[1] = RESULT_SUCCESS.raw; + ++enable_gyroscope_count; + event_gyroscope->Signal(); - LOG_DEBUG(Service_HID, "called"); -} + cmd_buff[1] = RESULT_SUCCESS.raw; -void EnableGyroscopeLow(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + LOG_DEBUG(Service_HID, "called"); + } - ++enable_gyroscope_count; - event_gyroscope->Signal(); + void DisableGyroscopeLow(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[1] = RESULT_SUCCESS.raw; + --enable_gyroscope_count; + event_gyroscope->Signal(); - LOG_DEBUG(Service_HID, "called"); -} + cmd_buff[1] = RESULT_SUCCESS.raw; -void DisableGyroscopeLow(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + LOG_DEBUG(Service_HID, "called"); + } - --enable_gyroscope_count; - event_gyroscope->Signal(); + void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_DEBUG(Service_HID, "called"); -} + f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient(); + memcpy(&cmd_buff[2], &coef, 4); + } -void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + void GetGyroscopeLowCalibrateParam(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); - cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[1] = RESULT_SUCCESS.raw; - f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient(); - memcpy(&cmd_buff[2], &coef, 4); -} + const s16 param_unit = 6700; // an approximate value taken from hw + GyroscopeCalibrateParam param = { + { 0, param_unit, -param_unit }, + { 0, param_unit, -param_unit }, + { 0, param_unit, -param_unit }, + }; + memcpy(&cmd_buff[2], ¶m, sizeof(param)); -void GetGyroscopeLowCalibrateParam(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } - cmd_buff[1] = RESULT_SUCCESS.raw; + void GetSoundVolume(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); - const s16 param_unit = 6700; // an approximate value taken from hw - GyroscopeCalibrateParam param = { - { 0, param_unit, -param_unit }, - { 0, param_unit, -param_unit }, - { 0, param_unit, -param_unit }, - }; - memcpy(&cmd_buff[2], ¶m, sizeof(param)); + const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume - LOG_WARNING(Service_HID, "(STUBBED) called"); -} + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = volume; -void GetSoundVolume(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } - const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume + void Init() { + using namespace Kernel; - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = volume; + AddService(new HID_U_Interface); + AddService(new HID_SPVR_Interface); - LOG_WARNING(Service_HID, "(STUBBED) called"); -} + using Kernel::MemoryPermission; + shared_mem = SharedMemory::Create(nullptr, 0x1000, + MemoryPermission::ReadWrite, MemoryPermission::Read, + 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); -void Init() { - using namespace Kernel; + next_pad_index = 0; + next_touch_index = 0; - AddService(new HID_U_Interface); - AddService(new HID_SPVR_Interface); + // Create event handles + event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); + event_pad_or_touch_2 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch2"); + event_accelerometer = Event::Create(ResetType::OneShot, "HID:EventAccelerometer"); + event_gyroscope = Event::Create(ResetType::OneShot, "HID:EventGyroscope"); + event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad"); + } - using Kernel::MemoryPermission; - shared_mem = SharedMemory::Create(nullptr, 0x1000, - MemoryPermission::ReadWrite, MemoryPermission::Read, - 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); - - next_pad_index = 0; - next_touch_index = 0; - - // Create event handles - event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); - event_pad_or_touch_2 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch2"); - event_accelerometer = Event::Create(ResetType::OneShot, "HID:EventAccelerometer"); - event_gyroscope = Event::Create(ResetType::OneShot, "HID:EventGyroscope"); - event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad"); -} - -void Shutdown() { - shared_mem = nullptr; - event_pad_or_touch_1 = nullptr; - event_pad_or_touch_2 = nullptr; - event_accelerometer = nullptr; - event_gyroscope = nullptr; - event_debug_pad = nullptr; -} - -} // namespace HID - -} // namespace Service + void Shutdown() { + shared_mem = nullptr; + event_pad_or_touch_1 = nullptr; + event_pad_or_touch_2 = nullptr; + event_accelerometer = nullptr; + event_gyroscope = nullptr; + event_debug_pad = nullptr; + } + } // namespace HID +} // namespace Service \ No newline at end of file diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index a4dfb7e43..45e1d4a3d 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -32,454 +32,452 @@ #include "video_core/debug_utils/debug_utils.h" - namespace GPU { + Regs g_regs; -Regs g_regs; + /// True if the current frame was skipped + bool g_skip_frame; + /// 268MHz CPU clocks / 60Hz frames per second + const u64 frame_ticks = 268123480ull / 60; + /// Event id for CoreTiming + static int vblank_event; + /// Total number of frames drawn + static u64 frame_count; + /// True if the last frame was skipped + static bool last_skip_frame; -/// True if the current frame was skipped -bool g_skip_frame; -/// 268MHz CPU clocks / 60Hz frames per second -const u64 frame_ticks = 268123480ull / 60; -/// Event id for CoreTiming -static int vblank_event; -/// Total number of frames drawn -static u64 frame_count; -/// True if the last frame was skipped -static bool last_skip_frame; + template + inline void Read(T &var, const u32 raw_addr) { + u32 addr = raw_addr - HW::VADDR_GPU; + u32 index = addr / 4; -template -inline void Read(T &var, const u32 raw_addr) { - u32 addr = raw_addr - HW::VADDR_GPU; - u32 index = addr / 4; - - // Reads other than u32 are untested, so I'd rather have them abort than silently fail - if (index >= Regs::NumIds() || !std::is_same::value) { - LOG_ERROR(HW_GPU, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr); - return; - } - - var = g_regs[addr / 4]; -} - -static Math::Vec4 DecodePixel(Regs::PixelFormat input_format, const u8* src_pixel) { - switch (input_format) { - case Regs::PixelFormat::RGBA8: - return Color::DecodeRGBA8(src_pixel); - - case Regs::PixelFormat::RGB8: - return Color::DecodeRGB8(src_pixel); - - case Regs::PixelFormat::RGB565: - return Color::DecodeRGB565(src_pixel); - - case Regs::PixelFormat::RGB5A1: - return Color::DecodeRGB5A1(src_pixel); - - case Regs::PixelFormat::RGBA4: - return Color::DecodeRGBA4(src_pixel); - - default: - LOG_ERROR(HW_GPU, "Unknown source framebuffer format %x", input_format); - return {0, 0, 0, 0}; - } -} - -MICROPROFILE_DEFINE(GPU_DisplayTransfer, "GPU", "DisplayTransfer", MP_RGB(100, 100, 255)); -MICROPROFILE_DEFINE(GPU_CmdlistProcessing, "GPU", "Cmdlist Processing", MP_RGB(100, 255, 100)); - -template -inline void Write(u32 addr, const T data) { - addr -= HW::VADDR_GPU; - u32 index = addr / 4; - - // Writes other than u32 are untested, so I'd rather have them abort than silently fail - if (index >= Regs::NumIds() || !std::is_same::value) { - LOG_ERROR(HW_GPU, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr); - return; - } - - g_regs[index] = static_cast(data); - - switch (index) { - - // Memory fills are triggered once the fill value is written. - case GPU_REG_INDEX_WORKAROUND(memory_fill_config[0].trigger, 0x00004 + 0x3): - case GPU_REG_INDEX_WORKAROUND(memory_fill_config[1].trigger, 0x00008 + 0x3): - { - const bool is_second_filler = (index != GPU_REG_INDEX(memory_fill_config[0].trigger)); - auto& config = g_regs.memory_fill_config[is_second_filler]; - - if (config.trigger) { - if (config.address_start) { // Some games pass invalid values here - u8* start = Memory::GetPhysicalPointer(config.GetStartAddress()); - u8* end = Memory::GetPhysicalPointer(config.GetEndAddress()); - - // TODO: Consider always accelerating and returning vector of - // regions that the accelerated fill did not cover to - // reduce/eliminate the fill that the cpu has to do. - // This would also mean that the flush below is not needed. - // Fill should first flush all surfaces that touch but are - // not completely within the fill range. - // Then fill all completely covered surfaces, and return the - // regions that were between surfaces or within the touching - // ones for cpu to manually fill here. - if (!VideoCore::g_renderer->Rasterizer()->AccelerateFill(config)) { - Memory::RasterizerFlushAndInvalidateRegion(config.GetStartAddress(), config.GetEndAddress() - config.GetStartAddress()); - - if (config.fill_24bit) { - // fill with 24-bit values - for (u8* ptr = start; ptr < end; ptr += 3) { - ptr[0] = config.value_24bit_r; - ptr[1] = config.value_24bit_g; - ptr[2] = config.value_24bit_b; - } - } else if (config.fill_32bit) { - // fill with 32-bit values - if (end > start) { - u32 value = config.value_32bit; - size_t len = (end - start) / sizeof(u32); - for (size_t i = 0; i < len; ++i) - memcpy(&start[i * sizeof(u32)], &value, sizeof(u32)); - } - } else { - // fill with 16-bit values - u16 value_16bit = config.value_16bit.Value(); - for (u8* ptr = start; ptr < end; ptr += sizeof(u16)) - memcpy(ptr, &value_16bit, sizeof(u16)); - } - } - - LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress()); - - if (!is_second_filler) { - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC0); - } else { - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1); - } - } - - // Reset "trigger" flag and set the "finish" flag - // NOTE: This was confirmed to happen on hardware even if "address_start" is zero. - config.trigger.Assign(0); - config.finished.Assign(1); + // Reads other than u32 are untested, so I'd rather have them abort than silently fail + if (index >= Regs::NumIds() || !std::is_same::value) { + LOG_ERROR(HW_GPU, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr); + return; } - break; + + var = g_regs[addr / 4]; } - case GPU_REG_INDEX(display_transfer_config.trigger): - { - MICROPROFILE_SCOPE(GPU_DisplayTransfer); + static Math::Vec4 DecodePixel(Regs::PixelFormat input_format, const u8* src_pixel) { + switch (input_format) { + case Regs::PixelFormat::RGBA8: + return Color::DecodeRGBA8(src_pixel); - const auto& config = g_regs.display_transfer_config; - if (config.trigger & 1) { + case Regs::PixelFormat::RGB8: + return Color::DecodeRGB8(src_pixel); - if (Pica::g_debug_context) - Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::IncomingDisplayTransfer, nullptr); + case Regs::PixelFormat::RGB565: + return Color::DecodeRGB565(src_pixel); - if (!VideoCore::g_renderer->Rasterizer()->AccelerateDisplayTransfer(config)) { - u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress()); - u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress()); + case Regs::PixelFormat::RGB5A1: + return Color::DecodeRGB5A1(src_pixel); - if (config.is_texture_copy) { - u32 input_width = config.texture_copy.input_width * 16; - u32 input_gap = config.texture_copy.input_gap * 16; - u32 output_width = config.texture_copy.output_width * 16; - u32 output_gap = config.texture_copy.output_gap * 16; + case Regs::PixelFormat::RGBA4: + return Color::DecodeRGBA4(src_pixel); - size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap); - Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), static_cast(contiguous_input_size)); - - size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap); - Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), static_cast(contiguous_output_size)); - - u32 remaining_size = config.texture_copy.size; - u32 remaining_input = input_width; - u32 remaining_output = output_width; - while (remaining_size > 0) { - u32 copy_size = std::min({ remaining_input, remaining_output, remaining_size }); - - std::memcpy(dst_pointer, src_pointer, copy_size); - src_pointer += copy_size; - dst_pointer += copy_size; - - remaining_input -= copy_size; - remaining_output -= copy_size; - remaining_size -= copy_size; - - if (remaining_input == 0) { - remaining_input = input_width; - src_pointer += input_gap; - } - if (remaining_output == 0) { - remaining_output = output_width; - dst_pointer += output_gap; - } - } - - LOG_TRACE(HW_GPU, "TextureCopy: 0x%X bytes from 0x%08X(%u+%u)-> 0x%08X(%u+%u), flags 0x%08X", - config.texture_copy.size, - config.GetPhysicalInputAddress(), input_width, input_gap, - config.GetPhysicalOutputAddress(), output_width, output_gap, - config.flags); - - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); - break; - } - - if (config.scaling > config.ScaleXY) { - LOG_CRITICAL(HW_GPU, "Unimplemented display transfer scaling mode %u", config.scaling.Value()); - UNIMPLEMENTED(); - break; - } - - if (config.input_linear && config.scaling != config.NoScale) { - LOG_CRITICAL(HW_GPU, "Scaling is only implemented on tiled input"); - UNIMPLEMENTED(); - break; - } - - int horizontal_scale = config.scaling != config.NoScale ? 1 : 0; - int vertical_scale = config.scaling == config.ScaleXY ? 1 : 0; - - u32 output_width = config.output_width >> horizontal_scale; - u32 output_height = config.output_height >> vertical_scale; - - u32 input_size = config.input_width * config.input_height * GPU::Regs::BytesPerPixel(config.input_format); - u32 output_size = output_width * output_height * GPU::Regs::BytesPerPixel(config.output_format); - - Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), input_size); - Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), output_size); - - for (u32 y = 0; y < output_height; ++y) { - for (u32 x = 0; x < output_width; ++x) { - Math::Vec4 src_color; - - // Calculate the [x,y] position of the input image - // based on the current output position and the scale - u32 input_x = x << horizontal_scale; - u32 input_y = y << vertical_scale; - - if (config.flip_vertically) { - // Flip the y value of the output data, - // we do this after calculating the [x,y] position of the input image - // to account for the scaling options. - y = output_height - y - 1; - } - - u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format); - u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format); - u32 src_offset; - u32 dst_offset; - - if (config.input_linear) { - if (!config.dont_swizzle) { - // Interpret the input as linear and the output as tiled - u32 coarse_y = y & ~7; - u32 stride = output_width * dst_bytes_per_pixel; - - src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; - dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride; - } else { - // Both input and output are linear - src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; - dst_offset = (x + y * output_width) * dst_bytes_per_pixel; - } - } else { - if (!config.dont_swizzle) { - // Interpret the input as tiled and the output as linear - u32 coarse_y = input_y & ~7; - u32 stride = config.input_width * src_bytes_per_pixel; - - src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + coarse_y * stride; - dst_offset = (x + y * output_width) * dst_bytes_per_pixel; - } else { - // Both input and output are tiled - u32 out_coarse_y = y & ~7; - u32 out_stride = output_width * dst_bytes_per_pixel; - - u32 in_coarse_y = input_y & ~7; - u32 in_stride = config.input_width * src_bytes_per_pixel; - - src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + in_coarse_y * in_stride; - dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + out_coarse_y * out_stride; - } - } - - const u8* src_pixel = src_pointer + src_offset; - src_color = DecodePixel(config.input_format, src_pixel); - if (config.scaling == config.ScaleX) { - Math::Vec4 pixel = DecodePixel(config.input_format, src_pixel + src_bytes_per_pixel); - src_color = ((src_color + pixel) / 2).Cast(); - } else if (config.scaling == config.ScaleXY) { - Math::Vec4 pixel1 = DecodePixel(config.input_format, src_pixel + 1 * src_bytes_per_pixel); - Math::Vec4 pixel2 = DecodePixel(config.input_format, src_pixel + 2 * src_bytes_per_pixel); - Math::Vec4 pixel3 = DecodePixel(config.input_format, src_pixel + 3 * src_bytes_per_pixel); - src_color = (((src_color + pixel1) + (pixel2 + pixel3)) / 4).Cast(); - } - - u8* dst_pixel = dst_pointer + dst_offset; - switch (config.output_format) { - case Regs::PixelFormat::RGBA8: - Color::EncodeRGBA8(src_color, dst_pixel); - break; - - case Regs::PixelFormat::RGB8: - Color::EncodeRGB8(src_color, dst_pixel); - break; - - case Regs::PixelFormat::RGB565: - Color::EncodeRGB565(src_color, dst_pixel); - break; - - case Regs::PixelFormat::RGB5A1: - Color::EncodeRGB5A1(src_color, dst_pixel); - break; - - case Regs::PixelFormat::RGBA4: - Color::EncodeRGBA4(src_color, dst_pixel); - break; - - default: - LOG_ERROR(HW_GPU, "Unknown destination framebuffer format %x", config.output_format.Value()); - break; - } - } - } - - LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x, flags 0x%08X", - config.output_height * output_width * GPU::Regs::BytesPerPixel(config.output_format), - config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(), - config.GetPhysicalOutputAddress(), output_width, output_height, - config.output_format.Value(), config.flags); - } - - g_regs.display_transfer_config.trigger = 0; - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); + default: + LOG_ERROR(HW_GPU, "Unknown source framebuffer format %x", input_format); + return{ 0, 0, 0, 0 }; } - break; } - // Seems like writing to this register triggers processing - case GPU_REG_INDEX(command_processor_config.trigger): - { - const auto& config = g_regs.command_processor_config; - if (config.trigger & 1) + MICROPROFILE_DEFINE(GPU_DisplayTransfer, "GPU", "DisplayTransfer", MP_RGB(100, 100, 255)); + MICROPROFILE_DEFINE(GPU_CmdlistProcessing, "GPU", "Cmdlist Processing", MP_RGB(100, 255, 100)); + + template + inline void Write(u32 addr, const T data) { + addr -= HW::VADDR_GPU; + u32 index = addr / 4; + + // Writes other than u32 are untested, so I'd rather have them abort than silently fail + if (index >= Regs::NumIds() || !std::is_same::value) { + LOG_ERROR(HW_GPU, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr); + return; + } + + g_regs[index] = static_cast(data); + + switch (index) { + // Memory fills are triggered once the fill value is written. + case GPU_REG_INDEX_WORKAROUND(memory_fill_config[0].trigger, 0x00004 + 0x3): + case GPU_REG_INDEX_WORKAROUND(memory_fill_config[1].trigger, 0x00008 + 0x3): { - MICROPROFILE_SCOPE(GPU_CmdlistProcessing); + const bool is_second_filler = (index != GPU_REG_INDEX(memory_fill_config[0].trigger)); + auto& config = g_regs.memory_fill_config[is_second_filler]; - u32* buffer = (u32*)Memory::GetPhysicalPointer(config.GetPhysicalAddress()); + if (config.trigger) { + if (config.address_start) { // Some games pass invalid values here + u8* start = Memory::GetPhysicalPointer(config.GetStartAddress()); + u8* end = Memory::GetPhysicalPointer(config.GetEndAddress()); - if (Pica::g_debug_context && Pica::g_debug_context->recorder) { - Pica::g_debug_context->recorder->MemoryAccessed((u8*)buffer, config.size * sizeof(u32), config.GetPhysicalAddress()); + // TODO: Consider always accelerating and returning vector of + // regions that the accelerated fill did not cover to + // reduce/eliminate the fill that the cpu has to do. + // This would also mean that the flush below is not needed. + // Fill should first flush all surfaces that touch but are + // not completely within the fill range. + // Then fill all completely covered surfaces, and return the + // regions that were between surfaces or within the touching + // ones for cpu to manually fill here. + if (!VideoCore::g_renderer->Rasterizer()->AccelerateFill(config)) { + Memory::RasterizerFlushAndInvalidateRegion(config.GetStartAddress(), config.GetEndAddress() - config.GetStartAddress()); + + if (config.fill_24bit) { + // fill with 24-bit values + for (u8* ptr = start; ptr < end; ptr += 3) { + ptr[0] = config.value_24bit_r; + ptr[1] = config.value_24bit_g; + ptr[2] = config.value_24bit_b; + } + } + else if (config.fill_32bit) { + // fill with 32-bit values + if (end > start) { + u32 value = config.value_32bit; + size_t len = (end - start) / sizeof(u32); + for (size_t i = 0; i < len; ++i) + memcpy(&start[i * sizeof(u32)], &value, sizeof(u32)); + } + } + else { + // fill with 16-bit values + u16 value_16bit = config.value_16bit.Value(); + for (u8* ptr = start; ptr < end; ptr += sizeof(u16)) + memcpy(ptr, &value_16bit, sizeof(u16)); + } + } + + LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress()); + + if (!is_second_filler) { + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC0); + } + else { + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1); + } + } + + // Reset "trigger" flag and set the "finish" flag + // NOTE: This was confirmed to happen on hardware even if "address_start" is zero. + config.trigger.Assign(0); + config.finished.Assign(1); } - - Pica::CommandProcessor::ProcessCommandList(buffer, config.size); - - g_regs.command_processor_config.trigger = 0; + break; + } + + case GPU_REG_INDEX(display_transfer_config.trigger): + { + MICROPROFILE_SCOPE(GPU_DisplayTransfer); + + const auto& config = g_regs.display_transfer_config; + if (config.trigger & 1) { + if (Pica::g_debug_context) + Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::IncomingDisplayTransfer, nullptr); + + if (!VideoCore::g_renderer->Rasterizer()->AccelerateDisplayTransfer(config)) { + u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress()); + u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress()); + + if (config.is_texture_copy) { + u32 input_width = config.texture_copy.input_width * 16; + u32 input_gap = config.texture_copy.input_gap * 16; + u32 output_width = config.texture_copy.output_width * 16; + u32 output_gap = config.texture_copy.output_gap * 16; + + size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap); + Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), static_cast(contiguous_input_size)); + + size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap); + Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), static_cast(contiguous_output_size)); + + u32 remaining_size = config.texture_copy.size; + u32 remaining_input = input_width; + u32 remaining_output = output_width; + while (remaining_size > 0) { + u32 copy_size = std::min({ remaining_input, remaining_output, remaining_size }); + + std::memcpy(dst_pointer, src_pointer, copy_size); + src_pointer += copy_size; + dst_pointer += copy_size; + + remaining_input -= copy_size; + remaining_output -= copy_size; + remaining_size -= copy_size; + + if (remaining_input == 0) { + remaining_input = input_width; + src_pointer += input_gap; + } + if (remaining_output == 0) { + remaining_output = output_width; + dst_pointer += output_gap; + } + } + + LOG_TRACE(HW_GPU, "TextureCopy: 0x%X bytes from 0x%08X(%u+%u)-> 0x%08X(%u+%u), flags 0x%08X", + config.texture_copy.size, + config.GetPhysicalInputAddress(), input_width, input_gap, + config.GetPhysicalOutputAddress(), output_width, output_gap, + config.flags); + + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); + break; + } + + if (config.scaling > config.ScaleXY) { + LOG_CRITICAL(HW_GPU, "Unimplemented display transfer scaling mode %u", config.scaling.Value()); + UNIMPLEMENTED(); + break; + } + + if (config.input_linear && config.scaling != config.NoScale) { + LOG_CRITICAL(HW_GPU, "Scaling is only implemented on tiled input"); + UNIMPLEMENTED(); + break; + } + + int horizontal_scale = config.scaling != config.NoScale ? 1 : 0; + int vertical_scale = config.scaling == config.ScaleXY ? 1 : 0; + + u32 output_width = config.output_width >> horizontal_scale; + u32 output_height = config.output_height >> vertical_scale; + + u32 input_size = config.input_width * config.input_height * GPU::Regs::BytesPerPixel(config.input_format); + u32 output_size = output_width * output_height * GPU::Regs::BytesPerPixel(config.output_format); + + Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), input_size); + Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), output_size); + + for (u32 y = 0; y < output_height; ++y) { + for (u32 x = 0; x < output_width; ++x) { + Math::Vec4 src_color; + + // Calculate the [x,y] position of the input image + // based on the current output position and the scale + u32 input_x = x << horizontal_scale; + u32 input_y = y << vertical_scale; + + if (config.flip_vertically) { + // Flip the y value of the output data, + // we do this after calculating the [x,y] position of the input image + // to account for the scaling options. + y = output_height - y - 1; + } + + u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format); + u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format); + u32 src_offset; + u32 dst_offset; + + if (config.input_linear) { + if (!config.dont_swizzle) { + // Interpret the input as linear and the output as tiled + u32 coarse_y = y & ~7; + u32 stride = output_width * dst_bytes_per_pixel; + + src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; + dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride; + } + else { + // Both input and output are linear + src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; + dst_offset = (x + y * output_width) * dst_bytes_per_pixel; + } + } + else { + if (!config.dont_swizzle) { + // Interpret the input as tiled and the output as linear + u32 coarse_y = input_y & ~7; + u32 stride = config.input_width * src_bytes_per_pixel; + + src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + coarse_y * stride; + dst_offset = (x + y * output_width) * dst_bytes_per_pixel; + } + else { + // Both input and output are tiled + u32 out_coarse_y = y & ~7; + u32 out_stride = output_width * dst_bytes_per_pixel; + + u32 in_coarse_y = input_y & ~7; + u32 in_stride = config.input_width * src_bytes_per_pixel; + + src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + in_coarse_y * in_stride; + dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + out_coarse_y * out_stride; + } + } + + const u8* src_pixel = src_pointer + src_offset; + src_color = DecodePixel(config.input_format, src_pixel); + if (config.scaling == config.ScaleX) { + Math::Vec4 pixel = DecodePixel(config.input_format, src_pixel + src_bytes_per_pixel); + src_color = ((src_color + pixel) / 2).Cast(); + } + else if (config.scaling == config.ScaleXY) { + Math::Vec4 pixel1 = DecodePixel(config.input_format, src_pixel + 1 * src_bytes_per_pixel); + Math::Vec4 pixel2 = DecodePixel(config.input_format, src_pixel + 2 * src_bytes_per_pixel); + Math::Vec4 pixel3 = DecodePixel(config.input_format, src_pixel + 3 * src_bytes_per_pixel); + src_color = (((src_color + pixel1) + (pixel2 + pixel3)) / 4).Cast(); + } + + u8* dst_pixel = dst_pointer + dst_offset; + switch (config.output_format) { + case Regs::PixelFormat::RGBA8: + Color::EncodeRGBA8(src_color, dst_pixel); + break; + + case Regs::PixelFormat::RGB8: + Color::EncodeRGB8(src_color, dst_pixel); + break; + + case Regs::PixelFormat::RGB565: + Color::EncodeRGB565(src_color, dst_pixel); + break; + + case Regs::PixelFormat::RGB5A1: + Color::EncodeRGB5A1(src_color, dst_pixel); + break; + + case Regs::PixelFormat::RGBA4: + Color::EncodeRGBA4(src_color, dst_pixel); + break; + + default: + LOG_ERROR(HW_GPU, "Unknown destination framebuffer format %x", config.output_format.Value()); + break; + } + } + } + + LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x, flags 0x%08X", + config.output_height * output_width * GPU::Regs::BytesPerPixel(config.output_format), + config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(), + config.GetPhysicalOutputAddress(), output_width, output_height, + config.output_format.Value(), config.flags); + } + + g_regs.display_transfer_config.trigger = 0; + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); + } + break; + } + + // Seems like writing to this register triggers processing + case GPU_REG_INDEX(command_processor_config.trigger): + { + const auto& config = g_regs.command_processor_config; + if (config.trigger & 1) { + MICROPROFILE_SCOPE(GPU_CmdlistProcessing); + + u32* buffer = (u32*)Memory::GetPhysicalPointer(config.GetPhysicalAddress()); + + if (Pica::g_debug_context && Pica::g_debug_context->recorder) { + Pica::g_debug_context->recorder->MemoryAccessed((u8*)buffer, config.size * sizeof(u32), config.GetPhysicalAddress()); + } + + Pica::CommandProcessor::ProcessCommandList(buffer, config.size); + + g_regs.command_processor_config.trigger = 0; + } + break; + } + + default: + break; + } + + // Notify tracer about the register write + // This is happening *after* handling the write to make sure we properly catch all memory reads. + if (Pica::g_debug_context && Pica::g_debug_context->recorder) { + // addr + GPU VBase - IO VBase + IO PBase + Pica::g_debug_context->recorder->RegisterWritten(addr + 0x1EF00000 - 0x1EC00000 + 0x10100000, data); } - break; } - default: - break; - } + // Explicitly instantiate template functions because we aren't defining this in the header: - // Notify tracer about the register write - // This is happening *after* handling the write to make sure we properly catch all memory reads. - if (Pica::g_debug_context && Pica::g_debug_context->recorder) { - // addr + GPU VBase - IO VBase + IO PBase - Pica::g_debug_context->recorder->RegisterWritten(addr + 0x1EF00000 - 0x1EC00000 + 0x10100000, data); - } -} + template void Read(u64 &var, const u32 addr); + template void Read(u32 &var, const u32 addr); + template void Read(u16 &var, const u32 addr); + template void Read(u8 &var, const u32 addr); -// Explicitly instantiate template functions because we aren't defining this in the header: + template void Write(u32 addr, const u64 data); + template void Write(u32 addr, const u32 data); + template void Write(u32 addr, const u16 data); + template void Write(u32 addr, const u8 data); -template void Read(u64 &var, const u32 addr); -template void Read(u32 &var, const u32 addr); -template void Read(u16 &var, const u32 addr); -template void Read(u8 &var, const u32 addr); + /// Update hardware + static void VBlankCallback(u64 userdata, int cycles_late) { + frame_count++; + last_skip_frame = g_skip_frame; + g_skip_frame = (frame_count & Settings::values.frame_skip) != 0; -template void Write(u32 addr, const u64 data); -template void Write(u32 addr, const u32 data); -template void Write(u32 addr, const u16 data); -template void Write(u32 addr, const u8 data); - -/// Update hardware -static void VBlankCallback(u64 userdata, int cycles_late) { - frame_count++; - last_skip_frame = g_skip_frame; - g_skip_frame = (frame_count & Settings::values.frame_skip) != 0; - - // Swap buffers based on the frameskip mode, which is a little bit tricky. When - // a frame is being skipped, nothing is being rendered to the internal framebuffer(s). - // So, we should only swap frames if the last frame was rendered. The rules are: - // - If frameskip == 0 (disabled), always swap buffers - // - If frameskip == 1, swap buffers every other frame (starting from the first frame) - // - If frameskip > 1, swap buffers every frameskip^n frames (starting from the second frame) - if ((((Settings::values.frame_skip != 1) ^ last_skip_frame) && last_skip_frame != g_skip_frame) || + // Swap buffers based on the frameskip mode, which is a little bit tricky. When + // a frame is being skipped, nothing is being rendered to the internal framebuffer(s). + // So, we should only swap frames if the last frame was rendered. The rules are: + // - If frameskip == 0 (disabled), always swap buffers + // - If frameskip == 1, swap buffers every other frame (starting from the first frame) + // - If frameskip > 1, swap buffers every frameskip^n frames (starting from the second frame) + if ((((Settings::values.frame_skip != 1) ^ last_skip_frame) && last_skip_frame != g_skip_frame) || Settings::values.frame_skip == 0) { - VideoCore::g_renderer->SwapBuffers(); + VideoCore::g_renderer->SwapBuffers(); + } + + // Signal to GSP that GPU interrupt has occurred + // TODO(yuriks): hwtest to determine if PDC0 is for the Top screen and PDC1 for the Sub + // screen, or if both use the same interrupts and these two instead determine the + // beginning and end of the VBlank period. If needed, split the interrupt firing into + // two different intervals. + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); } - // Signal to GSP that GPU interrupt has occurred - // TODO(yuriks): hwtest to determine if PDC0 is for the Top screen and PDC1 for the Sub - // screen, or if both use the same interrupts and these two instead determine the - // beginning and end of the VBlank period. If needed, split the interrupt firing into - // two different intervals. - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); - GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); + /// Initialize hardware + void Init() { + memset(&g_regs, 0, sizeof(g_regs)); - // Check for user input updates - Service::HID::Update(); + auto& framebuffer_top = g_regs.framebuffer_config[0]; + auto& framebuffer_sub = g_regs.framebuffer_config[1]; - // Reschedule recurrent event - CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); -} + // Setup default framebuffer addresses (located in VRAM) + // .. or at least these are the ones used by system applets. + // There's probably a smarter way to come up with addresses + // like this which does not require hardcoding. + framebuffer_top.address_left1 = 0x181E6000; + framebuffer_top.address_left2 = 0x1822C800; + framebuffer_top.address_right1 = 0x18273000; + framebuffer_top.address_right2 = 0x182B9800; + framebuffer_sub.address_left1 = 0x1848F000; + framebuffer_sub.address_left2 = 0x184C7800; -/// Initialize hardware -void Init() { - memset(&g_regs, 0, sizeof(g_regs)); + framebuffer_top.width.Assign(240); + framebuffer_top.height.Assign(400); + framebuffer_top.stride = 3 * 240; + framebuffer_top.color_format.Assign(Regs::PixelFormat::RGB8); + framebuffer_top.active_fb = 0; - auto& framebuffer_top = g_regs.framebuffer_config[0]; - auto& framebuffer_sub = g_regs.framebuffer_config[1]; + framebuffer_sub.width.Assign(240); + framebuffer_sub.height.Assign(320); + framebuffer_sub.stride = 3 * 240; + framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8); + framebuffer_sub.active_fb = 0; - // Setup default framebuffer addresses (located in VRAM) - // .. or at least these are the ones used by system applets. - // There's probably a smarter way to come up with addresses - // like this which does not require hardcoding. - framebuffer_top.address_left1 = 0x181E6000; - framebuffer_top.address_left2 = 0x1822C800; - framebuffer_top.address_right1 = 0x18273000; - framebuffer_top.address_right2 = 0x182B9800; - framebuffer_sub.address_left1 = 0x1848F000; - framebuffer_sub.address_left2 = 0x184C7800; + last_skip_frame = false; + g_skip_frame = false; + frame_count = 0; - framebuffer_top.width.Assign(240); - framebuffer_top.height.Assign(400); - framebuffer_top.stride = 3 * 240; - framebuffer_top.color_format.Assign(Regs::PixelFormat::RGB8); - framebuffer_top.active_fb = 0; + vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); + CoreTiming::ScheduleEvent(frame_ticks, vblank_event); - framebuffer_sub.width.Assign(240); - framebuffer_sub.height.Assign(320); - framebuffer_sub.stride = 3 * 240; - framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8); - framebuffer_sub.active_fb = 0; + LOG_DEBUG(HW_GPU, "initialized OK"); + } - last_skip_frame = false; - g_skip_frame = false; - frame_count = 0; - - vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback); - CoreTiming::ScheduleEvent(frame_ticks, vblank_event); - - LOG_DEBUG(HW_GPU, "initialized OK"); -} - -/// Shutdown hardware -void Shutdown() { - LOG_DEBUG(HW_GPU, "shutdown OK"); -} - -} // namespace + /// Shutdown hardware + void Shutdown() { + LOG_DEBUG(HW_GPU, "shutdown OK"); + } +} // namespace \ No newline at end of file diff --git a/src/core/settings.h b/src/core/settings.h index f95e62390..bc7720fe4 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -6,87 +6,147 @@ #include #include - #include "common/common_types.h" +#include "common/string_util.h" namespace Settings { + namespace NativeInput { + enum Values { + // directly mapped keys + A, B, X, Y, + L, R, ZL, ZR, + START, SELECT, HOME, + DUP, DDOWN, DLEFT, DRIGHT, + CUP, CDOWN, CLEFT, CRIGHT, -namespace NativeInput { -enum Values { - // directly mapped keys - A, B, X, Y, - L, R, ZL, ZR, - START, SELECT, HOME, - DUP, DDOWN, DLEFT, DRIGHT, - CUP, CDOWN, CLEFT, CRIGHT, + // indirectly mapped keys + CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, - // indirectly mapped keys - CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, - CIRCLE_MODIFIER, + NUM_INPUTS + }; + static const std::array Mapping = { { + // directly mapped keys + "pad_a", "pad_b", "pad_x", "pad_y", + "pad_l", "pad_r", "pad_zl", "pad_zr", + "pad_start", "pad_select", "pad_home", + "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", + "pad_cup", "pad_cdown", "pad_cleft", "pad_cright", - NUM_INPUTS -}; + // indirectly mapped keys + "pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right" + } }; + static const std::array All = { { + A, B, X, Y, + L, R, ZL, ZR, + START, SELECT, HOME, + DUP, DDOWN, DLEFT, DRIGHT, + CUP, CDOWN, CLEFT, CRIGHT, + CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT + } }; + } -static const std::array Mapping = {{ - // directly mapped keys - "pad_a", "pad_b", "pad_x", "pad_y", - "pad_l", "pad_r", "pad_zl", "pad_zr", - "pad_start", "pad_select", "pad_home", - "pad_dup", "pad_ddown", "pad_dleft", "pad_dright", - "pad_cup", "pad_cdown", "pad_cleft", "pad_cright", - - // indirectly mapped keys - "pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right", - "pad_circle_modifier", -}}; -static const std::array All = {{ - A, B, X, Y, - L, R, ZL, ZR, - START, SELECT, HOME, - DUP, DDOWN, DLEFT, DRIGHT, - CUP, CDOWN, CLEFT, CRIGHT, - CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT, - CIRCLE_MODIFIER, -}}; -} - - -struct Values { - // CheckNew3DS - bool is_new_3ds; - - // Controls - std::array input_mappings; - float pad_circle_modifier_scale; - - // Core - int frame_skip; - - // Data Storage - bool use_virtual_sd; - - // System Region - int region_value; - - // Renderer - bool use_hw_renderer; - bool use_shader_jit; - bool use_scaled_resolution; - - float bg_red; - float bg_green; - float bg_blue; - - std::string log_filter; - - // Audio - std::string sink_id; - - // Debugging - bool use_gdbstub; - u16 gdbstub_port; -} extern values; - -void Apply(); + enum class DeviceFramework { + Qt, SDL + }; + enum class Device { + Keyboard, Gamepad + }; + struct InputDeviceMapping { + DeviceFramework framework; + int number; + Device device; + std::string key; + InputDeviceMapping() { + this->framework = DeviceFramework::Qt; + this->number = 0; + this->device = Device::Keyboard; + this->key = ""; + } + InputDeviceMapping(std::string input) { + std::vector parts; + Common::SplitString(input, '/', parts); + if (parts.size() == 4) { + if (parts[0] == "Qt") + this->framework = DeviceFramework::Qt; + else if (parts[0] == "SDL") + this->framework = DeviceFramework::SDL; + this->number = std::stoi(parts[1]); + + if (parts[2] == "Keyboard") + this->device = Device::Keyboard; + else if (parts[2] == "Gamepad") + this->device = Device::Gamepad; + this->key = parts[3]; + } + else { + //default if can't read properly + this->framework = DeviceFramework::Qt; + this->number = 0; + this->device = Device::Keyboard; + this->key = ""; + } + } + + bool operator==(const InputDeviceMapping& rhs) const { + return (this->device == rhs.device) && (this->framework == rhs.framework) && (this->number == rhs.number); + } + std::string Save() { + std::string result = ""; + if (this->framework == DeviceFramework::Qt) + result = "Qt"; + else if (this->framework == DeviceFramework::SDL) + result = "SDL"; + + result += "/"; + result += std::to_string(this->number); + result += "/"; + + if (this->device == Device::Keyboard) + result += "Keyboard"; + else if (this->device == Device::Gamepad) + result += "Gamepad"; + result += "/"; + result += this->key; + return result; + } + }; + + struct Values { + // CheckNew3DS + bool is_new_3ds; + + // Controls + std::array input_mappings; + float pad_circle_modifier_scale; + + // Core + int frame_skip; + + // Data Storage + bool use_virtual_sd; + + // System Region + int region_value; + + // Renderer + bool use_hw_renderer; + bool use_shader_jit; + bool use_scaled_resolution; + + float bg_red; + float bg_green; + float bg_blue; + + std::string log_filter; + + // Audio + std::string sink_id; + + // Debugging + bool use_gdbstub; + u16 gdbstub_port; + } extern values; + + void Apply(); } diff --git a/src/core/system.cpp b/src/core/system.cpp index 4a4757af3..df46ce7e4 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -14,6 +14,7 @@ #include "core/hle/kernel/memory.h" #include "video_core/video_core.h" +#include "input_core/input_core.h" namespace System { @@ -28,6 +29,7 @@ Result Init(EmuWindow* emu_window) { return Result::ErrorInitVideoCore; } AudioCore::Init(); + InputCore::Init(); GDBStub::Init(); return Result::Success; @@ -35,6 +37,7 @@ Result Init(EmuWindow* emu_window) { void Shutdown() { GDBStub::Shutdown(); + InputCore::Shutdown(); AudioCore::Shutdown(); VideoCore::Shutdown(); HLE::Shutdown(); diff --git a/src/input_core/CMakeLists.txt b/src/input_core/CMakeLists.txt new file mode 100644 index 000000000..a8e03a428 --- /dev/null +++ b/src/input_core/CMakeLists.txt @@ -0,0 +1,28 @@ +set(SRCS + input_core.cpp + devices/Keyboard.cpp + devices/SDLGamepad.cpp + key_map.cpp + ) + +set(HEADERS + input_core.h + key_map.h + devices/IDevice.h + devices/Keyboard.h + devices/SDLGamepad.h + ) + + +if(SDL2_FOUND) + include_directories(${SDL2_INCLUDE_DIR}) +endif() + +create_directory_groups(${SRCS} ${HEADERS}) + +add_library(input_core STATIC ${SRCS} ${HEADERS}) + +if(SDL2_FOUND) + target_link_libraries(input_core ${SDL2_LIBRARY}) + set_property(TARGET input_core APPEND PROPERTY COMPILE_DEFINITIONS HAVE_SDL2) +endif() diff --git a/src/input_core/devices/IDevice.h b/src/input_core/devices/IDevice.h new file mode 100644 index 000000000..230c69de2 --- /dev/null +++ b/src/input_core/devices/IDevice.h @@ -0,0 +1,18 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "core/settings.h" +#include "input_core\key_map.h" +class IDevice { +public: + std::map> keyMapping; /// Maps the string in the settings file to the HID Padstate object + + virtual bool InitDevice(int number, std::map> keyMap) = 0; + virtual void ProcessInput() = 0; + virtual bool CloseDevice() = 0; +}; diff --git a/src/input_core/devices/Keyboard.cpp b/src/input_core/devices/Keyboard.cpp new file mode 100644 index 000000000..b320ce949 --- /dev/null +++ b/src/input_core/devices/Keyboard.cpp @@ -0,0 +1,55 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "Keyboard.h" +#include + +Keyboard::Keyboard() { +} + +Keyboard::~Keyboard() { +} + +bool Keyboard::InitDevice(int number, std::map> keyMap) { + keyMapping = keyMap; + return true; +} + +void Keyboard::ProcessInput() { + m.lock(); + auto keysPressedCopy = keysPressed; + m.unlock(); + for (auto const &ent1 : keyMapping) { + int scancode = std::stoul(ent1.first, nullptr, 16); + KeyboardKey proxy = KeyboardKey(0, scancode, ""); + if (keysPressedCopy[proxy] == true && keysPressedLast[scancode] == false) { + for (auto& key : ent1.second) { + KeyMap::PressKey(key, 1.0); + } + keysPressedLast[scancode] = true; + } + else if (keysPressedCopy[proxy] == false && keysPressedLast[scancode] == true) { + for (auto& key : ent1.second) { + KeyMap::ReleaseKey(key); + } + keysPressedLast[scancode] = false; + } + } +} + +bool Keyboard::CloseDevice() { + return true; +} + +void Keyboard::KeyPressed(KeyboardKey key) { + m.lock(); + keysPressed[key] = true; + m.unlock(); +} + +void Keyboard::KeyReleased(KeyboardKey key) { + m.lock(); + keysPressed[key] = false; + m.unlock(); +} \ No newline at end of file diff --git a/src/input_core/devices/Keyboard.h b/src/input_core/devices/Keyboard.h new file mode 100644 index 000000000..084881e24 --- /dev/null +++ b/src/input_core/devices/Keyboard.h @@ -0,0 +1,48 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "IDevice.h" + +struct KeyboardKey; + +class Keyboard : public IDevice { +private: + std::map keysPressed; + std::map keysPressedLast; + std::mutex m; /// Keys pressed from frontend is on a separate thread. +public: + Keyboard(); + ~Keyboard(); + bool InitDevice(int number, std::map> keyMap) override; + void ProcessInput() override; + bool CloseDevice() override; + void KeyPressed(KeyboardKey key); + void KeyReleased(KeyboardKey key); +}; + +struct KeyboardKey { + uint32_t key; + uint32_t scancode; + std::string character; + KeyboardKey(uint32_t Key, uint32_t Scancode, std::string Character) { + key = Key; + scancode = Scancode; + character = Character; + } + bool operator==(KeyboardKey& other) { + return (this->scancode == other.scancode); + } + bool operator==(uint32_t other) { + return (this->scancode == other); + } + bool operator<(const KeyboardKey &o) const { + return (this->scancode < o.scancode); + } +}; \ No newline at end of file diff --git a/src/input_core/devices/SDLGamepad.cpp b/src/input_core/devices/SDLGamepad.cpp new file mode 100644 index 000000000..717516c2d --- /dev/null +++ b/src/input_core/devices/SDLGamepad.cpp @@ -0,0 +1,86 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "SDLGamepad.h" +#include +#include "common/assert.h" +#include "common/logging/log.h" + +bool SDLGamepad::SDLInitialized = false; +SDLGamepad::SDLGamepad() { +} + +SDLGamepad::~SDLGamepad() { + CloseDevice(); +} + +bool SDLGamepad::InitDevice(int number, std::map> keyMap) { + if (!SDLGamepad::SDLInitialized && SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { + LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_GAMECONTROLLER) failed"); + return false; + } + SDL_GameControllerEventState(SDL_IGNORE); + SDLGamepad::SDLInitialized = true; + + if (SDL_IsGameController(number)) { + gamepad = SDL_GameControllerOpen(number); + if (gamepad == nullptr) { + LOG_ERROR(Input, "Controller found but unable to open connection."); + return false; + } + } + keyMapping = keyMap; + for (auto& entry : keyMapping) { + keysPressed[entry.first] = false; + } + + return true; +} + +void SDLGamepad::ProcessInput() { + if (gamepad == nullptr) + return; + SDL_GameControllerUpdate(); + for (auto const &ent1 : keyMapping) { + SDL_GameControllerButton button = SDL_GameControllerGetButtonFromString(friendlyNameMapping[ent1.first].c_str()); + if (button != SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_INVALID) { + Uint8 pressed = SDL_GameControllerGetButton(gamepad, button); + if (pressed == 1 && keysPressed[ent1.first] == false) { + for (auto& padstate : ent1.second) { + KeyMap::PressKey(padstate, 1.0); + keysPressed[ent1.first] = true; + } + } + else if (pressed == 0 && keysPressed[ent1.first] == true) { + for (auto& padstate : ent1.second) { + KeyMap::ReleaseKey(padstate); + keysPressed[ent1.first] = false; + } + } + } + else { + //Try axis if button isn't valid + SDL_GameControllerAxis axis = SDL_GameControllerGetAxisFromString(friendlyNameMapping[ent1.first].c_str()); + if (axis != SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_INVALID) { + Sint16 value = SDL_GameControllerGetAxis(gamepad, axis); + for (auto& padstate : ent1.second) { + if (abs(value) < 0.2 * 32767.0) // dont process if in deadzone. Replace later with settings for deadzone. + KeyMap::ReleaseKey(padstate); + else + KeyMap::PressKey(padstate, (float)value / 32767.0); + } + } + } + } +} + +bool SDLGamepad::CloseDevice() { + if (gamepad != nullptr) { + SDL_GameControllerClose(gamepad); + } + return true; +} \ No newline at end of file diff --git a/src/input_core/devices/SDLGamepad.h b/src/input_core/devices/SDLGamepad.h new file mode 100644 index 000000000..f48ffd107 --- /dev/null +++ b/src/input_core/devices/SDLGamepad.h @@ -0,0 +1,48 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "IDevice.h" + +class SDLGamepad : public IDevice { +private: + std::map friendlyNameMapping = { /// Maps the friendly name shown on GUI with the string name for getting the SDL button instance. + { "Button A","a" }, + { "Button B","b" }, + { "Button X","x" }, + { "Button Y","y" }, + { "Left Shoulder","leftshoulder" }, + { "Right Shoulder","rightshoulder" }, + { "Start","start" }, + { "Back","back" }, + { "D-pad Up","dpup" }, + { "D-pad Down","dpdown" }, + { "D-pad Left","dpleft" }, + { "D-pad Right","dpright" }, + { "L3","leftstick" }, + { "R3","rightstick" }, + { "Left Trigger","lefttrigger" }, + { "Right Trigger","righttrigger" }, + { "Left Y+","lefty" }, + { "Left Y-","lefty" }, + { "Left X+","leftx" }, + { "Left X-","leftx" }, + { "Right Y+","righty" }, + { "Right Y-","righty" }, + { "Right X+","rightx" }, + { "Right X-","rightx" }, + }; + static bool SDLInitialized; + std::map keysPressed; + SDL_GameController* gamepad; +public: + SDLGamepad(); + ~SDLGamepad(); + + virtual bool InitDevice(int number, std::map> keyMap) override; + virtual void ProcessInput() override; + bool CloseDevice() override; +}; diff --git a/src/input_core/input_core.cpp b/src/input_core/input_core.cpp new file mode 100644 index 000000000..608cbde0a --- /dev/null +++ b/src/input_core/input_core.cpp @@ -0,0 +1,108 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "core/core_timing.h" + +#include "input_core/input_core.h" +#include "input_core/devices/Keyboard.h" +#include "input_core/devices/SDLGamepad.h" + +namespace InputCore { + using std::vector; + using std::shared_ptr; + using std::string; + + constexpr u64 frame_ticks = 268123480ull / 60; + static int tick_event; + Service::HID::PadState pad_state; + std::tuple circle_pad = { 0,0 }; + shared_ptr main_keyboard; /// Instance of main keyboard device. Always initialized regardless of settings. + vector> devices; ///Devices that are handling input for the game + + static void InputTickCallback(u64, int cycles_late) { + for (auto& device : devices) + device->ProcessInput(); + + Service::HID::Update(); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(frame_ticks - cycles_late, tick_event); + } + + void InputCore::Init() { + devices = ParseSettings(); + tick_event = CoreTiming::RegisterEvent("InputCore::tick_event", InputTickCallback); + CoreTiming::ScheduleEvent(frame_ticks, tick_event); + } + + void InputCore::Shutdown() { + devices.clear(); + } + + ///Parse the settings to initialize necessary devices to handle input + vector> InputCore::ParseSettings() { + vector> devices; + vector uniqueMappings; //unique mappings from settings file, used to init devices. + + //Get Unique input mappings from settings + for (auto& mapping : Settings::values.input_mappings) { + if (!CheckIfMappingExists(uniqueMappings, mapping)) { + uniqueMappings.push_back(mapping); + } + } + + //Generate a device for each unique mapping + shared_ptr input; + for (auto& mapping : uniqueMappings) { + switch (mapping.framework) { + case Settings::DeviceFramework::Qt: + { + main_keyboard = std::make_shared(); + input = main_keyboard; + break; + } + case Settings::DeviceFramework::SDL: + { + if (mapping.device == Settings::Device::Keyboard) { + main_keyboard = std::make_shared(); + input = main_keyboard; + break; + } + else if (mapping.device == Settings::Device::Gamepad) { + input = std::make_shared(); + break; + } + } + } + devices.push_back(input); + + //Build list of inputs to listen for, for this device + std::map> keyMapping; + for (int i = 0; i < Settings::values.input_mappings.size(); i++) { + KeyMap::KeyTarget val = KeyMap::mapping_targets[i]; + std::string key = Settings::values.input_mappings[i].key; + if (Settings::values.input_mappings[i] == mapping) { + keyMapping[key].push_back(val); + } + } + + input->InitDevice(mapping.number, keyMapping); + } + if (main_keyboard == nullptr) + main_keyboard = std::make_shared(); + + return devices; + } + + ///Helper method to check if device has already been initialized from the mapping. + bool InputCore::CheckIfMappingExists(vector uniqueMapping, Settings::InputDeviceMapping mappingToCheck) { + for (auto& mapping : uniqueMapping) { + if (mapping == mappingToCheck) + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/input_core/input_core.h b/src/input_core/input_core.h new file mode 100644 index 000000000..175b9d2a2 --- /dev/null +++ b/src/input_core/input_core.h @@ -0,0 +1,26 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include +#include + +#include "core/hle/service/hid/hid.h" +#include "core/settings.h" +#include "input_core\devices\IDevice.h" + +class Keyboard; + +namespace InputCore { + extern Service::HID::PadState pad_state; + extern std::tuple circle_pad; + + extern std::shared_ptr main_keyboard; + + void Init(); + void Shutdown(); + + std::vector> ParseSettings(); + bool CheckIfMappingExists(std::vector uniqueMapping, Settings::InputDeviceMapping mappingToCheck); +} // namespace \ No newline at end of file diff --git a/src/input_core/key_map.cpp b/src/input_core/key_map.cpp new file mode 100644 index 000000000..6184e89e5 --- /dev/null +++ b/src/input_core/key_map.cpp @@ -0,0 +1,63 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/emu_window.h" + +#include "input_core/key_map.h" +#include "input_core/input_core.h" + +namespace KeyMap { + constexpr int MAX_CIRCLEPAD_POS = 0x9C; /// Max value for a circle pad position + const std::array mapping_targets = { { + Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y, + Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR, + Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_TOUCH, + Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT, + Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT, + + Service::HID::PAD_CIRCLE_UP, + Service::HID::PAD_CIRCLE_DOWN, + Service::HID::PAD_CIRCLE_LEFT, + Service::HID::PAD_CIRCLE_RIGHT, + } }; + ///Array of inputs that are analog only, and require a strength when set + const std::array analog_inputs = { + Service::HID::PAD_CIRCLE_UP, + Service::HID::PAD_CIRCLE_DOWN, + Service::HID::PAD_CIRCLE_LEFT, + Service::HID::PAD_CIRCLE_RIGHT + }; + + void PressKey(KeyTarget target, const float strength) { + if (std::find(std::begin(analog_inputs), std::end(analog_inputs), target) == std::end(analog_inputs)) { // If is digital keytarget + InputCore::pad_state.hex |= target.target.direct_target_hex; + } + else { // it is analog input + if (target == Service::HID::PAD_CIRCLE_UP || target == Service::HID::PAD_CIRCLE_DOWN) { + std::get<1>(InputCore::circle_pad) = MAX_CIRCLEPAD_POS * strength * -1; + } + else if (target == Service::HID::PAD_CIRCLE_LEFT || target == Service::HID::PAD_CIRCLE_RIGHT) { + std::get<0>(InputCore::circle_pad) = MAX_CIRCLEPAD_POS * strength; + } + } + } + + void ReleaseKey(KeyTarget target) { + if (std::find(std::begin(analog_inputs), std::end(analog_inputs), target) == std::end(analog_inputs)) { // If is digital keytarget + InputCore::pad_state.hex &= ~target.target.direct_target_hex; + } + else { // it is analog input + if (target == Service::HID::PAD_CIRCLE_UP || target == Service::HID::PAD_CIRCLE_DOWN) { + std::get<1>(InputCore::circle_pad) = 0; + } + else if (target == Service::HID::PAD_CIRCLE_LEFT || target == Service::HID::PAD_CIRCLE_RIGHT) { + std::get<0>(InputCore::circle_pad) = 0; + } + } + } +} \ No newline at end of file diff --git a/src/input_core/key_map.h b/src/input_core/key_map.h new file mode 100644 index 000000000..17f4ee739 --- /dev/null +++ b/src/input_core/key_map.h @@ -0,0 +1,63 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "core/hle/service/hid/hid.h" + +class EmuWindow; + +namespace KeyMap { + /** + * Represents key mapping targets that are not real 3DS buttons. + * They will be handled by KeyMap and translated to 3DS input. + */ + enum class IndirectTarget { + CirclePadUp, + CirclePadDown, + CirclePadLeft, + CirclePadRight, + CirclePadModifier + }; + + /** + * Represents a key mapping target. It can be a PadState that represents real 3DS buttons, + * or an IndirectTarget. + */ + struct KeyTarget { + bool direct; + union { + u32 direct_target_hex; + IndirectTarget indirect_target; + } target; + + KeyTarget() : direct(true) { + target.direct_target_hex = 0; + } + + KeyTarget(Service::HID::PadState pad) : direct(true) { + target.direct_target_hex = pad.hex; + } + + KeyTarget(IndirectTarget i) : direct(false) { + target.indirect_target = i; + } + const bool operator==(const Service::HID::PadState &other) const { + return this->target.direct_target_hex == other.hex; + } + const bool operator==(const KeyTarget &other) const { + return this->target.direct_target_hex == other.target.direct_target_hex; + } + }; + + extern const std::array mapping_targets; + + ///Handles the pressing of a key and modifies InputCore state + void PressKey(KeyTarget target, float strength); + + ///Handles the releasing of a key and modifies InputCore state + void ReleaseKey(KeyTarget target); +}