diff options
Diffstat (limited to 'src')
61 files changed, 814 insertions, 314 deletions
diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 98f093258..bd8ac563b 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -63,8 +63,7 @@ void Config::ReadValues() { // Renderer Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true); Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true); - Settings::values.use_scaled_resolution = - sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false); + Settings::values.resolution_factor = sdl2_config->GetReal("Renderer", "resolution_factor", 1.0); Settings::values.use_vsync = sdl2_config->GetBoolean("Renderer", "use_vsync", false); Settings::values.toggle_framelimit = sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 50c4a2812..7996813b4 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -51,14 +51,21 @@ use_hw_renderer = # 0: Interpreter (slow), 1 (default): JIT (fast) use_shader_jit = -# Whether to use native 3DS screen resolution or to scale rendering resolution to the displayed screen size. -# 0 (default): Native, 1: Scaled -use_scaled_resolution = +# Resolution scale factor +# 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale +# factor for the 3DS resolution +resolution_factor = # Whether to enable V-Sync (caps the framerate at 60FPS) or not. # 0 (default): Off, 1: On use_vsync = +# The clear color for the renderer. What shows up on the sides of the bottom screen. +# Must be in range of 0.0-1.0. Defaults to 1.0 for all. +bg_red = +bg_blue = +bg_green = + [Layout] # Layout for the screen inside the render window. # 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen @@ -73,12 +80,6 @@ toggle_framelimit = # 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent swap_screen = -# The clear color for the renderer. What shows up on the sides of the bottom screen. -# Must be in range of 0.0-1.0. Defaults to 1.0 for all. -bg_red = -bg_blue = -bg_green = - [Audio] # Which audio output engine to use. # auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index b0d82b670..81a3abe3f 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -19,16 +19,22 @@ void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + motion_emu->Tilt(x, y); } void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { - if (button != SDL_BUTTON_LEFT) - return; - - if (state == SDL_PRESSED) { - TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); - } else { - TouchReleased(); + if (button == SDL_BUTTON_LEFT) { + if (state == SDL_PRESSED) { + TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + } else { + TouchReleased(); + } + } else if (button == SDL_BUTTON_RIGHT) { + if (state == SDL_PRESSED) { + motion_emu->BeginTilt(x, y); + } else { + motion_emu->EndTilt(); + } } } @@ -54,6 +60,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() { keyboard_id = KeyMap::NewDeviceId(); ReloadSetKeymaps(); + motion_emu = std::make_unique<Motion::MotionEmu>(*this); SDL_SetMainReady(); @@ -109,6 +116,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() { EmuWindow_SDL2::~EmuWindow_SDL2() { SDL_GL_DeleteContext(gl_context); SDL_Quit(); + motion_emu = nullptr; } void EmuWindow_SDL2::SwapBuffers() { diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h index c8cd919c6..b1cbf16d7 100644 --- a/src/citra/emu_window/emu_window_sdl2.h +++ b/src/citra/emu_window/emu_window_sdl2.h @@ -4,8 +4,10 @@ #pragma once +#include <memory> #include <utility> #include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" struct SDL_Window; @@ -61,4 +63,7 @@ private: /// Device id of keyboard for use with KeyMap int keyboard_id; + + /// Motion sensors emulation + std::unique_ptr<Motion::MotionEmu> motion_emu; }; diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 57fde6caa..948db384d 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -99,7 +99,7 @@ private: }; GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) - : QWidget(parent), keyboard_id(0), emu_thread(emu_thread), child(nullptr) { + : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) { std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); @@ -191,6 +191,7 @@ qreal GRenderWindow::windowPixelRatio() { } void GRenderWindow::closeEvent(QCloseEvent* event) { + motion_emu = nullptr; emit Closed(); QWidget::closeEvent(event); } @@ -204,11 +205,13 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { } void GRenderWindow::mousePressEvent(QMouseEvent* event) { + auto pos = event->pos(); if (event->button() == Qt::LeftButton) { - auto pos = event->pos(); qreal pixelRatio = windowPixelRatio(); this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio), static_cast<unsigned>(pos.y() * pixelRatio)); + } else if (event->button() == Qt::RightButton) { + motion_emu->BeginTilt(pos.x(), pos.y()); } } @@ -217,11 +220,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { qreal pixelRatio = windowPixelRatio(); this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u), std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u)); + motion_emu->Tilt(pos.x(), pos.y()); } void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) this->TouchReleased(); + else if (event->button() == Qt::RightButton) + motion_emu->EndTilt(); } void GRenderWindow::ReloadSetKeymaps() { @@ -279,11 +285,13 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest( } void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { + motion_emu = std::make_unique<Motion::MotionEmu>(*this); this->emu_thread = emu_thread; child->DisablePainting(); } void GRenderWindow::OnEmulationStopping() { + motion_emu = nullptr; emu_thread = nullptr; child->EnablePainting(); } diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index 43015390b..7dac1c480 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -11,6 +11,7 @@ #include <QThread> #include "common/thread.h" #include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" class QKeyEvent; class QScreen; @@ -156,6 +157,9 @@ private: EmuThread* emu_thread; + /// Motion sensors emulation + std::unique_ptr<Motion::MotionEmu> motion_emu; + protected: void showEvent(QShowEvent* event) override; }; diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index c904c4b00..8021667d0 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -44,8 +44,7 @@ void Config::ReadValues() { qt_config->beginGroup("Renderer"); Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", true).toBool(); Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool(); - Settings::values.use_scaled_resolution = - qt_config->value("use_scaled_resolution", false).toBool(); + Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat(); Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool(); Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool(); @@ -152,7 +151,7 @@ void Config::SaveValues() { qt_config->beginGroup("Renderer"); qt_config->setValue("use_hw_renderer", Settings::values.use_hw_renderer); qt_config->setValue("use_shader_jit", Settings::values.use_shader_jit); - qt_config->setValue("use_scaled_resolution", Settings::values.use_scaled_resolution); + qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor); qt_config->setValue("use_vsync", Settings::values.use_vsync); qt_config->setValue("toggle_framelimit", Settings::values.toggle_framelimit); diff --git a/src/citra_qt/configure_graphics.cpp b/src/citra_qt/configure_graphics.cpp index cea7db388..54f799b47 100644 --- a/src/citra_qt/configure_graphics.cpp +++ b/src/citra_qt/configure_graphics.cpp @@ -18,10 +18,81 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) ConfigureGraphics::~ConfigureGraphics() {} +enum class Resolution : int { + Auto, + Scale1x, + Scale2x, + Scale3x, + Scale4x, + Scale5x, + Scale6x, + Scale7x, + Scale8x, + Scale9x, + Scale10x, +}; + +float ToResolutionFactor(Resolution option) { + switch (option) { + case Resolution::Auto: + return 0.f; + case Resolution::Scale1x: + return 1.f; + case Resolution::Scale2x: + return 2.f; + case Resolution::Scale3x: + return 3.f; + case Resolution::Scale4x: + return 4.f; + case Resolution::Scale5x: + return 5.f; + case Resolution::Scale6x: + return 6.f; + case Resolution::Scale7x: + return 7.f; + case Resolution::Scale8x: + return 8.f; + case Resolution::Scale9x: + return 9.f; + case Resolution::Scale10x: + return 10.f; + } + return 0.f; +} + +Resolution FromResolutionFactor(float factor) { + if (factor == 0.f) { + return Resolution::Auto; + } else if (factor == 1.f) { + return Resolution::Scale1x; + } else if (factor == 2.f) { + return Resolution::Scale2x; + } else if (factor == 3.f) { + return Resolution::Scale3x; + } else if (factor == 4.f) { + return Resolution::Scale4x; + } else if (factor == 5.f) { + return Resolution::Scale5x; + } else if (factor == 6.f) { + return Resolution::Scale6x; + } else if (factor == 7.f) { + return Resolution::Scale7x; + } else if (factor == 8.f) { + return Resolution::Scale8x; + } else if (factor == 9.f) { + return Resolution::Scale9x; + } else if (factor == 10.f) { + return Resolution::Scale10x; + } + return Resolution::Auto; +} + void ConfigureGraphics::setConfiguration() { ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer); + ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer); ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit); - ui->toggle_scaled_resolution->setChecked(Settings::values.use_scaled_resolution); + ui->resolution_factor_combobox->setCurrentIndex( + static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); ui->toggle_vsync->setChecked(Settings::values.use_vsync); ui->toggle_framelimit->setChecked(Settings::values.toggle_framelimit); ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option)); @@ -31,7 +102,8 @@ void ConfigureGraphics::setConfiguration() { void ConfigureGraphics::applyConfiguration() { Settings::values.use_hw_renderer = ui->toggle_hw_renderer->isChecked(); Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked(); - Settings::values.use_scaled_resolution = ui->toggle_scaled_resolution->isChecked(); + Settings::values.resolution_factor = + ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); Settings::values.use_vsync = ui->toggle_vsync->isChecked(); Settings::values.toggle_framelimit = ui->toggle_framelimit->isChecked(); Settings::values.layout_option = diff --git a/src/citra_qt/configure_graphics.ui b/src/citra_qt/configure_graphics.ui index 964aa0bbd..62021fe22 100644 --- a/src/citra_qt/configure_graphics.ui +++ b/src/citra_qt/configure_graphics.ui @@ -37,13 +37,6 @@ </widget> </item> <item> - <widget class="QCheckBox" name="toggle_scaled_resolution"> - <property name="text"> - <string>Enable scaled resolution</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="toggle_vsync"> <property name="text"> <string>Enable V-Sync</string> @@ -57,6 +50,76 @@ </property> </widget> </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Internal Resolution:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="resolution_factor_combobox"> + <item> + <property name="text"> + <string notr="true">Auto (Window Size)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">Native (400x240)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">2x Native (800x480)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">3x Native (1200x720)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">4x Native (1600x960)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">5x Native (2000x1200)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">6x Native (2400x1440)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">7x Native (2800x1680)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">8x Native (3200x1920)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">9x Native (3600x2160)</string> + </property> + </item> + <item> + <property name="text"> + <string notr="true">10x Native (4000x2400)</string> + </property> + </item> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> @@ -128,5 +191,12 @@ </layout> </widget> <resources/> - <connections/> + <connections> + <connection> + <sender>toggle_hw_renderer</sender> + <signal>toggled(bool)</signal> + <receiver>resolution_factor_combobox</receiver> + <slot>setEnabled(bool)</slot> + </connection> + </connections> </ui> diff --git a/src/citra_qt/debugger/callstack.cpp b/src/citra_qt/debugger/callstack.cpp index c1db93583..08d2e7a22 100644 --- a/src/citra_qt/debugger/callstack.cpp +++ b/src/citra_qt/debugger/callstack.cpp @@ -45,7 +45,6 @@ void CallstackWidget::OnDebugModeEntered() { if (ARM_Disasm::Decode(insn) == OP_BL) { std::string name; // ripped from disasm - u8 cond = (insn >> 28) & 0xf; u32 i_offset = insn & 0xffffff; // Sign-extend the 24-bit offset if ((i_offset >> 23) & 1) diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp index dab529e3a..f5a2ec761 100644 --- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp @@ -135,11 +135,6 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { UNREACHABLE_MSG("Unknown texture command"); } - const auto texture = Pica::g_state.regs.GetTextures()[texture_index]; - const auto config = texture.config; - const auto format = texture.format; - const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); - // TODO: Open a surface debugger } } diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp index b75b94ef8..ff2e7e363 100644 --- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp +++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp @@ -276,9 +276,6 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con output << 'b' << instr.flow_control.bool_uniform_id << ' '; } - u32 target_addr = instr.flow_control.dest_offset; - u32 target_addr_else = instr.flow_control.dest_offset; - if (opcode_info.subtype & OpCode::Info::HasAlternative) { output << "else jump to 0x" << std::setw(4) << std::right << std::setfill('0') << std::hex @@ -473,7 +470,6 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget( } void GraphicsVertexShaderWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) { - auto input = static_cast<Pica::Shader::InputVertex*>(data); if (event == Pica::DebugContext::Event::VertexShaderInvocation) { Reload(true, data); } else { diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp index 1d2de5185..b6ecf3819 100644 --- a/src/citra_qt/debugger/wait_tree.cpp +++ b/src/citra_qt/debugger/wait_tree.cpp @@ -153,7 +153,8 @@ QString WaitTreeThread::GetText() const { case THREADSTATUS_WAIT_SLEEP: status = tr("sleeping"); break; - case THREADSTATUS_WAIT_SYNCH: + case THREADSTATUS_WAIT_SYNCH_ALL: + case THREADSTATUS_WAIT_SYNCH_ANY: status = tr("waiting for objects"); break; case THREADSTATUS_DORMANT: @@ -180,7 +181,8 @@ QColor WaitTreeThread::GetColor() const { return QColor(Qt::GlobalColor::darkRed); case THREADSTATUS_WAIT_SLEEP: return QColor(Qt::GlobalColor::darkYellow); - case THREADSTATUS_WAIT_SYNCH: + case THREADSTATUS_WAIT_SYNCH_ALL: + case THREADSTATUS_WAIT_SYNCH_ANY: return QColor(Qt::GlobalColor::red); case THREADSTATUS_DORMANT: return QColor(Qt::GlobalColor::darkCyan); @@ -228,7 +230,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { } else { list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes)); } - if (thread.status == THREADSTATUS_WAIT_SYNCH) { + if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY || + thread.status == THREADSTATUS_WAIT_SYNCH_ALL) { list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, thread.IsSleepingOnWaitAll())); } diff --git a/src/citra_qt/util/spinbox.cpp b/src/citra_qt/util/spinbox.cpp index feb0ea1b3..212709007 100644 --- a/src/citra_qt/util/spinbox.cpp +++ b/src/citra_qt/util/spinbox.cpp @@ -165,13 +165,6 @@ void CSpinBox::UpdateText() { // Uppercase digits greater than 9. mask += ">"; - // The greatest signed 64-bit number has 19 decimal digits. - // TODO: Could probably make this more generic with some logarithms. - // For reference, unsigned 64-bit can have up to 20 decimal digits. - int digits = (num_digits != 0) - ? num_digits - : (base == 16) ? 16 : (base == 10) ? 19 : 0xFF; // fallback case... - // Match num_digits digits // Digits irrelevant to the chosen number base are filtered in the validator mask += QString("H").repeated(std::max(num_digits, 1)); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5aecf6e6e..a7a4a688c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -46,6 +46,7 @@ set(HEADERS microprofileui.h platform.h profiler_reporting.h + quaternion.h scm_rev.h scope_exit.h string_util.h diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 3ea102229..2ef3e6b05 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -45,6 +45,7 @@ namespace Log { SUB(Service, LDR) \ SUB(Service, MIC) \ SUB(Service, NDM) \ + SUB(Service, NFC) \ SUB(Service, NIM) \ SUB(Service, NWM) \ SUB(Service, CAM) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 9d8c18d8e..4330ef879 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -62,6 +62,7 @@ enum class Class : ClassType { Service_LDR, ///< The LDR (3ds dll loader) service Service_MIC, ///< The MIC (Microphone) service Service_NDM, ///< The NDM (Network daemon manager) service + Service_NFC, ///< The NFC service Service_NIM, ///< The NIM (Network interface manager) service Service_NWM, ///< The NWM (Network wlan manager) service Service_CAM, ///< The CAM (Camera) service diff --git a/src/common/math_util.h b/src/common/math_util.h index cdeaeb733..45a1ed367 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -10,6 +10,8 @@ namespace MathUtil { +static constexpr float PI = 3.14159265f; + inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1, unsigned length1) { return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1)); diff --git a/src/common/quaternion.h b/src/common/quaternion.h new file mode 100644 index 000000000..84ac82ed3 --- /dev/null +++ b/src/common/quaternion.h @@ -0,0 +1,44 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/vector_math.h" + +namespace Math { + +template <typename T> +class Quaternion { +public: + Math::Vec3<T> xyz; + T w; + + Quaternion<decltype(-T{})> Inverse() const { + return {-xyz, w}; + } + + Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const { + return {xyz + other.xyz, w + other.w}; + } + + Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const { + return {xyz - other.xyz, w - other.w}; + } + + Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const { + return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz), + w * other.w - Dot(xyz, other.xyz)}; + } +}; + +template <typename T> +auto QuaternionRotate(const Quaternion<T>& q, const Math::Vec3<T>& v) { + return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w); +} + +inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float angle) { + return {axis * std::sin(angle / 2), std::cos(angle / 2)}; +} + +} // namspace Math diff --git a/src/common/thread.h b/src/common/thread.h index 9c08be7e3..fa475ab51 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -4,6 +4,7 @@ #pragma once +#include <chrono> #include <condition_variable> #include <cstddef> #include <mutex> @@ -54,6 +55,15 @@ public: is_set = false; } + template <class Clock, class Duration> + bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { + std::unique_lock<std::mutex> lk(mutex); + if (!condvar.wait_until(lk, time, [this] { return is_set; })) + return false; + is_set = false; + return true; + } + void Reset() { std::unique_lock<std::mutex> lk(mutex); // no other action required, since wait loops on the predicate and any lingering signal will diff --git a/src/common/vector_math.h b/src/common/vector_math.h index a57d86d88..7ca8e15f5 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -186,6 +186,18 @@ Vec2<T> operator*(const V& f, const Vec2<T>& vec) { typedef Vec2<float> Vec2f; +template <> +inline float Vec2<float>::Length() const { + return std::sqrt(x * x + y * y); +} + +template <> +inline float Vec2<float>::Normalize() { + float length = Length(); + *this /= length; + return length; +} + template <typename T> class Vec3 { public: @@ -388,6 +400,13 @@ inline Vec3<float> Vec3<float>::Normalized() const { return *this / Length(); } +template <> +inline float Vec3<float>::Normalize() { + float length = Length(); + *this /= length; + return length; +} + typedef Vec3<float> Vec3f; template <typename T> diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ee456027a..faad0a561 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -31,6 +31,7 @@ set(SRCS file_sys/savedata_archive.cpp frontend/emu_window.cpp frontend/key_map.cpp + frontend/motion_emu.cpp gdbstub/gdbstub.cpp hle/config_mem.cpp hle/applets/applet.cpp @@ -204,6 +205,7 @@ set(HEADERS file_sys/savedata_archive.h frontend/emu_window.h frontend/key_map.h + frontend/motion_emu.h gdbstub/gdbstub.h hle/config_mem.h hle/function_wrappers.h diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index f6f90f9e1..1541cc39d 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -5,6 +5,7 @@ #include <algorithm> #include <cmath> #include "common/assert.h" +#include "common/profiler_reporting.h" #include "core/frontend/emu_window.h" #include "core/frontend/key_map.h" #include "video_core/video_core.h" @@ -89,6 +90,30 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { TouchPressed(framebuffer_x, framebuffer_y); } +void EmuWindow::AccelerometerChanged(float x, float y, float z) { + constexpr float coef = 512; + + std::lock_guard<std::mutex> lock(accel_mutex); + + // TODO(wwylele): do a time stretch as it in GyroscopeChanged + // The time stretch formula should be like + // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity + accel_x = x * coef; + accel_y = y * coef; + accel_z = z * coef; +} + +void EmuWindow::GyroscopeChanged(float x, float y, float z) { + constexpr float FULL_FPS = 60; + float coef = GetGyroscopeRawToDpsCoefficient(); + float stretch = + FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps; + std::lock_guard<std::mutex> lock(gyro_mutex); + gyro_x = x * coef * stretch; + gyro_y = y * coef * stretch; + gyro_z = z * coef * stretch; +} + void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { Layout::FramebufferLayout layout; switch (Settings::values.layout_option) { diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 835c4d500..1ba64c92b 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -4,6 +4,7 @@ #pragma once +#include <mutex> #include <tuple> #include <utility> #include "common/common_types.h" @@ -93,6 +94,27 @@ public: void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); /** + * Signal accelerometer state has changed. + * @param x X-axis accelerometer value + * @param y Y-axis accelerometer value + * @param z Z-axis accelerometer value + * @note all values are in unit of g (gravitational acceleration). + * e.g. x = 1.0 means 9.8m/s^2 in x direction. + * @see GetAccelerometerState for axis explanation. + */ + void AccelerometerChanged(float x, float y, float z); + + /** + * Signal gyroscope state has changed. + * @param x X-axis accelerometer value + * @param y Y-axis accelerometer value + * @param z Z-axis accelerometer value + * @note all values are in deg/sec. + * @see GetGyroscopeState for axis explanation. + */ + void GyroscopeChanged(float x, float y, float z); + + /** * Gets the current pad state (which buttons are pressed). * @note This should be called by the core emu thread to get a state set by the window thread. * @note This doesn't include analog input like circle pad direction @@ -134,12 +156,11 @@ public: * 1 unit of return value = 1/512 g (measured by hw test), * where g is the gravitational acceleration (9.8 m/sec2). * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Implement accelerometer input in front-end. * @return std::tuple of (x, y, z) */ - std::tuple<s16, s16, s16> GetAccelerometerState() const { - // stubbed - return std::make_tuple(0, -512, 0); + std::tuple<s16, s16, s16> GetAccelerometerState() { + std::lock_guard<std::mutex> lock(accel_mutex); + return std::make_tuple(accel_x, accel_y, accel_z); } /** @@ -153,12 +174,11 @@ public: * 1 unit of return value = (1/coef) deg/sec, * where coef is the return value of GetGyroscopeRawToDpsCoefficient(). * @note This should be called by the core emu thread to get a state set by the window thread. - * @todo Implement gyroscope input in front-end. * @return std::tuple of (x, y, z) */ - std::tuple<s16, s16, s16> GetGyroscopeState() const { - // stubbed - return std::make_tuple(0, 0, 0); + std::tuple<s16, s16, s16> GetGyroscopeState() { + std::lock_guard<std::mutex> lock(gyro_mutex); + return std::make_tuple(gyro_x, gyro_y, gyro_z); } /** @@ -216,6 +236,12 @@ protected: circle_pad_x = 0; circle_pad_y = 0; touch_pressed = false; + accel_x = 0; + accel_y = -512; + accel_z = 0; + gyro_x = 0; + gyro_y = 0; + gyro_z = 0; } virtual ~EmuWindow() {} @@ -281,6 +307,16 @@ private: s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156) s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156) + std::mutex accel_mutex; + s16 accel_x; ///< Accelerometer X-axis value in native 3DS units + s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units + s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units + + std::mutex gyro_mutex; + s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units + s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units + s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units + /** * Clip the provided coordinates to be inside the touchscreen area. */ diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp new file mode 100644 index 000000000..9a5b3185d --- /dev/null +++ b/src/core/frontend/motion_emu.cpp @@ -0,0 +1,89 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/math_util.h" +#include "common/quaternion.h" +#include "core/frontend/emu_window.h" +#include "core/frontend/motion_emu.h" + +namespace Motion { + +static constexpr int update_millisecond = 100; +static constexpr auto update_duration = + std::chrono::duration_cast<std::chrono::steady_clock::duration>( + std::chrono::milliseconds(update_millisecond)); + +MotionEmu::MotionEmu(EmuWindow& emu_window) + : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {} + +MotionEmu::~MotionEmu() { + if (motion_emu_thread.joinable()) { + shutdown_event.Set(); + motion_emu_thread.join(); + } +} + +void MotionEmu::MotionEmuThread(EmuWindow& emu_window) { + auto update_time = std::chrono::steady_clock::now(); + Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0); + Math::Quaternion<float> old_q; + + while (!shutdown_event.WaitUntil(update_time)) { + update_time += update_duration; + old_q = q; + + { + std::lock_guard<std::mutex> guard(tilt_mutex); + + // Find the quaternion describing current 3DS tilting + q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), + tilt_angle); + } + + auto inv_q = q.Inverse(); + + // Set the gravity vector in world space + auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f); + + // Find the angular rate vector in world space + auto angular_rate = ((q - old_q) * inv_q).xyz * 2; + angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180; + + // Transform the two vectors from world space to 3DS space + gravity = QuaternionRotate(inv_q, gravity); + angular_rate = QuaternionRotate(inv_q, angular_rate); + + // Update the sensor state + emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z); + emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z); + } +} + +void MotionEmu::BeginTilt(int x, int y) { + mouse_origin = Math::MakeVec(x, y); + is_tilting = true; +} + +void MotionEmu::Tilt(int x, int y) { + constexpr float SENSITIVITY = 0.01f; + auto mouse_move = Math::MakeVec(x, y) - mouse_origin; + if (is_tilting) { + std::lock_guard<std::mutex> guard(tilt_mutex); + if (mouse_move.x == 0 && mouse_move.y == 0) { + tilt_angle = 0; + } else { + tilt_direction = mouse_move.Cast<float>(); + tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f, + MathUtil::PI * 0.5f); + } + } +} + +void MotionEmu::EndTilt() { + std::lock_guard<std::mutex> guard(tilt_mutex); + tilt_angle = 0; + is_tilting = false; +} + +} // namespace Motion diff --git a/src/core/frontend/motion_emu.h b/src/core/frontend/motion_emu.h new file mode 100644 index 000000000..99d41a726 --- /dev/null +++ b/src/core/frontend/motion_emu.h @@ -0,0 +1,52 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include "common/thread.h" +#include "common/vector_math.h" + +class EmuWindow; + +namespace Motion { + +class MotionEmu final { +public: + MotionEmu(EmuWindow& emu_window); + ~MotionEmu(); + + /** + * Signals that a motion sensor tilt has begun. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + */ + void BeginTilt(int x, int y); + + /** + * Signals that a motion sensor tilt is occurring. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + */ + void Tilt(int x, int y); + + /** + * Signals that a motion sensor tilt has ended. + */ + void EndTilt(); + +private: + Math::Vec2<int> mouse_origin; + + std::mutex tilt_mutex; + Math::Vec2<float> tilt_direction; + float tilt_angle = 0; + + bool is_tilting = false; + + Common::Event shutdown_event; + std::thread motion_emu_thread; + + void MotionEmuThread(EmuWindow& emu_window); +}; + +} // namespace Motion diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index d88e25073..5cf45ada5 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -57,7 +57,6 @@ const u32 SIGTERM = 15; const u32 MSG_WAITALL = 8; #endif -const u32 R0_REGISTER = 0; const u32 R15_REGISTER = 15; const u32 CPSR_REGISTER = 25; const u32 FPSCR_REGISTER = 58; @@ -816,10 +815,6 @@ static void RemoveBreakpoint() { auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset)); - start_offset = addr_pos + 1; - u32 len = - HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset)); - if (type == BreakpointType::Access) { // Access is made up of Read and Write types, so add both breakpoints type = BreakpointType::Read; diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp index 3e116e3df..23f9df0d6 100644 --- a/src/core/hle/kernel/event.cpp +++ b/src/core/hle/kernel/event.cpp @@ -22,23 +22,17 @@ SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) { evt->reset_type = reset_type; evt->name = std::move(name); - if (reset_type == ResetType::Pulse) { - LOG_ERROR(Kernel, "Unimplemented event reset type Pulse"); - UNIMPLEMENTED(); - } - return evt; } -bool Event::ShouldWait() { +bool Event::ShouldWait(Thread* thread) const { return !signaled; } -void Event::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Event::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - // Release the event if it's not sticky... - if (reset_type != ResetType::Sticky) + if (reset_type == ResetType::OneShot) signaled = false; } @@ -51,4 +45,11 @@ void Event::Clear() { signaled = false; } +void Event::WakeupAllWaitingThreads() { + WaitObject::WakeupAllWaitingThreads(); + + if (reset_type == ResetType::Pulse) + signaled = false; +} + } // namespace diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h index 8dcd23edb..3e3673508 100644 --- a/src/core/hle/kernel/event.h +++ b/src/core/hle/kernel/event.h @@ -35,8 +35,10 @@ public: bool signaled; ///< Whether the event has already been signaled std::string name; ///< Name of event (optional) - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void WakeupAllWaitingThreads() override; void Signal(); void Clear(); diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1db8e102f..f599916f0 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include <algorithm> -#include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" #include "common/logging/log.h" #include "core/hle/config_mem.h" @@ -28,32 +27,39 @@ void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) { void WaitObject::RemoveWaitingThread(Thread* thread) { auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread); + // If a thread passed multiple handles to the same object, + // the kernel might attempt to remove the thread from the object's + // waiting threads list multiple times. if (itr != waiting_threads.end()) waiting_threads.erase(itr); } SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { - // Remove the threads that are ready or already running from our waitlist - boost::range::remove_erase_if(waiting_threads, [](const SharedPtr<Thread>& thread) { - return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY || - thread->status == THREADSTATUS_DEAD; - }); - - // TODO(Subv): This call should be performed inside the loop below to check if an object can be - // acquired by a particular thread. This is useful for things like recursive locking of Mutexes. - if (ShouldWait()) - return nullptr; - Thread* candidate = nullptr; s32 candidate_priority = THREADPRIO_LOWEST + 1; for (const auto& thread : waiting_threads) { + // The list of waiting threads must not contain threads that are not waiting to be awakened. + ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + "Inconsistent thread statuses in waiting_threads"); + if (thread->current_priority >= candidate_priority) continue; - bool ready_to_run = - std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), - [](const SharedPtr<WaitObject>& object) { return object->ShouldWait(); }); + if (ShouldWait(thread.get())) + continue; + + // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or + // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready. + bool ready_to_run = true; + if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) { + ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), + [&thread](const SharedPtr<WaitObject>& object) { + return object->ShouldWait(thread.get()); + }); + } + if (ready_to_run) { candidate = thread.get(); candidate_priority = thread->current_priority; @@ -66,7 +72,7 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { void WaitObject::WakeupAllWaitingThreads() { while (auto thread = GetHighestPriorityReadyThread()) { if (!thread->IsSleepingOnWaitAll()) { - Acquire(); + Acquire(thread.get()); // Set the output index of the WaitSynchronizationN call to the index of this object. if (thread->wait_set_output) { thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); @@ -74,18 +80,17 @@ void WaitObject::WakeupAllWaitingThreads() { } } else { for (auto& object : thread->wait_objects) { - object->Acquire(); - object->RemoveWaitingThread(thread.get()); + object->Acquire(thread.get()); } // Note: This case doesn't update the output index of WaitSynchronizationN. - // Clear the thread's waitlist - thread->wait_objects.clear(); } + for (auto& object : thread->wait_objects) + object->RemoveWaitingThread(thread.get()); + thread->wait_objects.clear(); + thread->SetWaitSynchronizationResult(RESULT_SUCCESS); thread->ResumeFromWait(); - // Note: Removing the thread from the object's waitlist will be - // done by GetHighestPriorityReadyThread. } } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 9503e7d04..bb8b99bb5 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -132,31 +132,32 @@ using SharedPtr = boost::intrusive_ptr<T>; class WaitObject : public Object { public: /** - * Check if the current thread should wait until the object is available + * Check if the specified thread should wait until the object is available + * @param thread The thread about which we're deciding. * @return True if the current thread should wait due to this object being unavailable */ - virtual bool ShouldWait() = 0; + virtual bool ShouldWait(Thread* thread) const = 0; - /// Acquire/lock the object if it is available - virtual void Acquire() = 0; + /// Acquire/lock the object for the specified thread if it is available + virtual void Acquire(Thread* thread) = 0; /** * Add a thread to wait on this object * @param thread Pointer to thread to add */ - void AddWaitingThread(SharedPtr<Thread> thread); + virtual void AddWaitingThread(SharedPtr<Thread> thread); /** * Removes a thread from waiting on this object (e.g. if it was resumed already) * @param thread Pointer to thread to remove */ - void RemoveWaitingThread(Thread* thread); + virtual void RemoveWaitingThread(Thread* thread); /** * Wake up all threads waiting on this object that can be awoken, in priority order, * and set the synchronization result and output of the thread. */ - void WakeupAllWaitingThreads(); + virtual void WakeupAllWaitingThreads(); /// Obtains the highest priority thread that is ready to run from this object's waiting list. SharedPtr<Thread> GetHighestPriorityReadyThread(); diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 736944bae..cef961289 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -6,26 +6,18 @@ #include <vector> #include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" +#include "core/core.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/thread.h" namespace Kernel { -/** - * Resumes a thread waiting for the specified mutex - * @param mutex The mutex that some thread is waiting on - */ -static void ResumeWaitingThread(Mutex* mutex) { - // Reset mutex lock thread handle, nothing is waiting - mutex->lock_count = 0; - mutex->holding_thread = nullptr; - mutex->WakeupAllWaitingThreads(); -} - void ReleaseThreadMutexes(Thread* thread) { for (auto& mtx : thread->held_mutexes) { - ResumeWaitingThread(mtx.get()); + mtx->lock_count = 0; + mtx->holding_thread = nullptr; + mtx->WakeupAllWaitingThreads(); } thread->held_mutexes.clear(); } @@ -40,52 +32,74 @@ SharedPtr<Mutex> Mutex::Create(bool initial_locked, std::string name) { mutex->name = std::move(name); mutex->holding_thread = nullptr; - // Acquire mutex with current thread if initialized as locked... + // Acquire mutex with current thread if initialized as locked if (initial_locked) - mutex->Acquire(); + mutex->Acquire(GetCurrentThread()); return mutex; } -bool Mutex::ShouldWait() { - auto thread = GetCurrentThread(); - bool wait = lock_count > 0 && holding_thread != thread; - - // If the holding thread of the mutex is lower priority than this thread, that thread should - // temporarily inherit this thread's priority - if (wait && thread->current_priority < holding_thread->current_priority) - holding_thread->BoostPriority(thread->current_priority); - - return wait; -} - -void Mutex::Acquire() { - Acquire(GetCurrentThread()); +bool Mutex::ShouldWait(Thread* thread) const { + return lock_count > 0 && thread != holding_thread; } -void Mutex::Acquire(SharedPtr<Thread> thread) { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Mutex::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); - // Actually "acquire" the mutex only if we don't already have it... + // Actually "acquire" the mutex only if we don't already have it if (lock_count == 0) { + priority = thread->current_priority; thread->held_mutexes.insert(this); - holding_thread = std::move(thread); + holding_thread = thread; + thread->UpdatePriority(); + Core::System::GetInstance().PrepareReschedule(); } lock_count++; } void Mutex::Release() { - // Only release if the mutex is held... + // Only release if the mutex is held if (lock_count > 0) { lock_count--; - // Yield to the next thread only if we've fully released the mutex... + // Yield to the next thread only if we've fully released the mutex if (lock_count == 0) { holding_thread->held_mutexes.erase(this); - ResumeWaitingThread(this); + holding_thread->UpdatePriority(); + holding_thread = nullptr; + WakeupAllWaitingThreads(); + Core::System::GetInstance().PrepareReschedule(); } } } +void Mutex::AddWaitingThread(SharedPtr<Thread> thread) { + WaitObject::AddWaitingThread(thread); + thread->pending_mutexes.insert(this); + UpdatePriority(); +} + +void Mutex::RemoveWaitingThread(Thread* thread) { + WaitObject::RemoveWaitingThread(thread); + thread->pending_mutexes.erase(this); + UpdatePriority(); +} + +void Mutex::UpdatePriority() { + if (!holding_thread) + return; + + s32 best_priority = THREADPRIO_LOWEST; + for (auto& waiter : GetWaitingThreads()) { + if (waiter->current_priority < best_priority) + best_priority = waiter->current_priority; + } + + if (best_priority != priority) { + priority = best_priority; + holding_thread->UpdatePriority(); + } +} + } // namespace diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h index 53c3dc1f1..c57adf400 100644 --- a/src/core/hle/kernel/mutex.h +++ b/src/core/hle/kernel/mutex.h @@ -35,17 +35,22 @@ public: } int lock_count; ///< Number of times the mutex has been acquired + u32 priority; ///< The priority of the mutex, used for priority inheritance. std::string name; ///< Name of mutex (optional) SharedPtr<Thread> holding_thread; ///< Thread that has acquired the mutex - bool ShouldWait() override; - void Acquire() override; - /** - * Acquires the specified mutex for the specified thread - * @param thread Thread that will acquire the mutex + * Elevate the mutex priority to the best priority + * among the priorities of all its waiting threads. */ - void Acquire(SharedPtr<Thread> thread); + void UpdatePriority(); + + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void AddWaitingThread(SharedPtr<Thread> thread) override; + void RemoveWaitingThread(Thread* thread) override; + void Release(); private: diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp index bf7600780..8bda2f75d 100644 --- a/src/core/hle/kernel/semaphore.cpp +++ b/src/core/hle/kernel/semaphore.cpp @@ -30,12 +30,13 @@ ResultVal<SharedPtr<Semaphore>> Semaphore::Create(s32 initial_count, s32 max_cou return MakeResult<SharedPtr<Semaphore>>(std::move(semaphore)); } -bool Semaphore::ShouldWait() { +bool Semaphore::ShouldWait(Thread* thread) const { return available_count <= 0; } -void Semaphore::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Semaphore::Acquire(Thread* thread) { + if (available_count <= 0) + return; --available_count; } diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h index e01908a25..cde94f7cc 100644 --- a/src/core/hle/kernel/semaphore.h +++ b/src/core/hle/kernel/semaphore.h @@ -39,8 +39,8 @@ public: s32 available_count; ///< Number of free slots left in the semaphore std::string name; ///< Name of semaphore (optional) - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Releases a certain number of slots from a semaphore. diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp index 6c19aa7c0..fd3bbbcad 100644 --- a/src/core/hle/kernel/server_port.cpp +++ b/src/core/hle/kernel/server_port.cpp @@ -14,13 +14,13 @@ namespace Kernel { ServerPort::ServerPort() {} ServerPort::~ServerPort() {} -bool ServerPort::ShouldWait() { +bool ServerPort::ShouldWait(Thread* thread) const { // If there are no pending sessions, we wait until a new one is added. return pending_sessions.size() == 0; } -void ServerPort::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void ServerPort::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair( diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h index b0f8df62c..6f8bdb6a9 100644 --- a/src/core/hle/kernel/server_port.h +++ b/src/core/hle/kernel/server_port.h @@ -53,8 +53,8 @@ public: /// ServerSessions created from this port inherit a reference to this handler. std::shared_ptr<Service::SessionRequestHandler> hle_handler; - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; private: ServerPort(); diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 146458c1c..9447ff236 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -29,12 +29,12 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create( return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); } -bool ServerSession::ShouldWait() { +bool ServerSession::ShouldWait(Thread* thread) const { return !signaled; } -void ServerSession::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void ServerSession::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); signaled = false; } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 458284a5d..c088b9a19 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -57,9 +57,9 @@ public: */ ResultCode HandleSyncRequest(); - bool ShouldWait() override; + bool ShouldWait(Thread* thread) const override; - void Acquire() override; + void Acquire(Thread* thread) override; std::string name; ///< The name of this session (optional) bool signaled; ///< Whether there's new data available to this ServerSession diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 5fb95dada..8c6fbcd04 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -27,12 +27,12 @@ namespace Kernel { /// Event type for the thread wake up event static int ThreadWakeupEventType; -bool Thread::ShouldWait() { +bool Thread::ShouldWait(Thread* thread) const { return status != THREADSTATUS_DEAD; } -void Thread::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Thread::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); } // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, allowing @@ -66,20 +66,6 @@ Thread* GetCurrentThread() { } /** - * Check if a thread is waiting on the specified wait object - * @param thread The thread to test - * @param wait_object The object to test against - * @return True if the thread is waiting, false otherwise - */ -static bool CheckWait_WaitObject(const Thread* thread, WaitObject* wait_object) { - if (thread->status != THREADSTATUS_WAIT_SYNCH) - return false; - - auto itr = std::find(thread->wait_objects.begin(), thread->wait_objects.end(), wait_object); - return itr != thread->wait_objects.end(); -} - -/** * Check if the specified thread is waiting on the specified address to be arbitrated * @param thread The thread to test * @param wait_address The address to test against @@ -90,9 +76,6 @@ static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) { } void Thread::Stop() { - // Release all the mutexes that this thread holds - ReleaseThreadMutexes(this); - // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle); wakeup_callback_handle_table.Close(callback_handle); @@ -114,6 +97,9 @@ void Thread::Stop() { } wait_objects.clear(); + // Release all the mutexes that this thread holds + ReleaseThreadMutexes(this); + // Mark the TLS slot in the thread's page as free. u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; u32 tls_slot = @@ -155,28 +141,6 @@ void ArbitrateAllThreads(u32 address) { } } -/// Boost low priority threads (temporarily) that have been starved -static void PriorityBoostStarvedThreads() { - u64 current_ticks = CoreTiming::GetTicks(); - - for (auto& thread : thread_list) { - // TODO(bunnei): Threads that have been waiting to be scheduled for `boost_ticks` (or - // longer) will have their priority temporarily adjusted to 1 higher than the highest - // priority thread to prevent thread starvation. This general behavior has been verified - // on hardware. However, this is almost certainly not perfect, and the real CTR OS scheduler - // should probably be reversed to verify this. - - const u64 boost_timeout = 2000000; // Boost threads that have been ready for > this long - - u64 delta = current_ticks - thread->last_running_ticks; - - if (thread->status == THREADSTATUS_READY && delta > boost_timeout) { - const s32 priority = std::max(ready_queue.get_first()->current_priority - 1, 0); - thread->BoostPriority(priority); - } - } -} - /** * Switches the CPU's active thread context to that of the specified thread * @param new_thread The thread to switch to @@ -199,8 +163,8 @@ static void SwitchContext(Thread* new_thread) { // Load context of new thread if (new_thread) { - DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY, - "Thread must be ready to become running."); + ASSERT_MSG(new_thread->status == THREADSTATUS_READY, + "Thread must be ready to become running."); // Cancel any outstanding wakeup events for this thread CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle); @@ -210,9 +174,6 @@ static void SwitchContext(Thread* new_thread) { ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; - // Restores thread to its nominal priority if it has been temporarily changed - new_thread->current_priority = new_thread->nominal_priority; - Core::CPU().LoadContext(new_thread->context); Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); } else { @@ -248,14 +209,6 @@ void WaitCurrentThread_Sleep() { thread->status = THREADSTATUS_WAIT_SLEEP; } -void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output) { - Thread* thread = GetCurrentThread(); - thread->wait_set_output = wait_set_output; - thread->wait_objects = std::move(wait_objects); - thread->status = THREADSTATUS_WAIT_SYNCH; -} - void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) { Thread* thread = GetCurrentThread(); thread->wait_address = wait_address; @@ -281,7 +234,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { return; } - if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) { + if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { thread->wait_set_output = false; // Remove the thread from each of its waiting objects' waitlists for (auto& object : thread->wait_objects) @@ -305,8 +259,11 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { } void Thread::ResumeFromWait() { + ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects"); + switch (status) { - case THREADSTATUS_WAIT_SYNCH: + case THREADSTATUS_WAIT_SYNCH_ALL: + case THREADSTATUS_WAIT_SYNCH_ANY: case THREADSTATUS_WAIT_ARB: case THREADSTATUS_WAIT_SLEEP: break; @@ -515,8 +472,21 @@ void Thread::SetPriority(s32 priority) { nominal_priority = current_priority = priority; } +void Thread::UpdatePriority() { + s32 best_priority = nominal_priority; + for (auto& mutex : held_mutexes) { + if (mutex->priority < best_priority) + best_priority = mutex->priority; + } + BoostPriority(best_priority); +} + void Thread::BoostPriority(s32 priority) { - ready_queue.move(this, current_priority, priority); + // If thread was ready, adjust queues + if (status == THREADSTATUS_READY) + ready_queue.move(this, current_priority, priority); + else + ready_queue.prepare(priority); current_priority = priority; } @@ -538,9 +508,11 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) { return thread; } -void Reschedule() { - PriorityBoostStarvedThreads(); +bool HaveReadyThreads() { + return ready_queue.get_first() != nullptr; +} +void Reschedule() { Thread* cur = GetCurrentThread(); Thread* next = PopNextReadyThread(); @@ -563,6 +535,12 @@ void Thread::SetWaitSynchronizationOutput(s32 output) { context.cpu_registers[1] = output; } +s32 Thread::GetWaitObjectIndex(WaitObject* object) const { + ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); + auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object); + return std::distance(match, wait_objects.rend()) - 1; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// void ThreadingInit() { diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c77ac644d..c557a2279 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -31,13 +31,14 @@ enum ThreadProcessorId : s32 { }; enum ThreadStatus { - THREADSTATUS_RUNNING, ///< Currently running - THREADSTATUS_READY, ///< Ready to run - THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter - THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC - THREADSTATUS_WAIT_SYNCH, ///< Waiting due to a WaitSynchronization SVC - THREADSTATUS_DORMANT, ///< Created but not yet made ready - THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated + THREADSTATUS_RUNNING, ///< Currently running + THREADSTATUS_READY, ///< Ready to run + THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter + THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC + THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false + THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true + THREADSTATUS_DORMANT, ///< Created but not yet made ready + THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated }; namespace Kernel { @@ -72,8 +73,8 @@ public: return HANDLE_TYPE; } - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; /** * Gets the thread's current priority @@ -90,6 +91,12 @@ public: void SetPriority(s32 priority); /** + * Boost's a thread's priority to the best priority among the thread's held mutexes. + * This prevents priority inversion via priority inheritance. + */ + void UpdatePriority(); + + /** * Temporarily boosts the thread's priority until the next time it is scheduled * @param priority The new priority */ @@ -128,13 +135,14 @@ public: /** * Retrieves the index that this particular object occupies in the list of objects - * that the thread passed to WaitSynchronizationN. + * that the thread passed to WaitSynchronizationN, starting the search from the last element. * It is used to set the output value of WaitSynchronizationN when the thread is awakened. + * When a thread wakes up due to an object signal, the kernel will use the index of the last + * matching object in the wait objects list in case of having multiple instances of the same + * object in the list. * @param object Object to query the index of. */ - s32 GetWaitObjectIndex(const WaitObject* object) const { - return wait_objects_index.at(object->GetObjectId()); - } + s32 GetWaitObjectIndex(WaitObject* object) const; /** * Stops a thread, invalidating it from further use @@ -152,10 +160,10 @@ public: /** * Returns whether this thread is waiting for all the objects in * its wait list to become ready, as a result of a WaitSynchronizationN call - * with wait_all = true, or a ReplyAndReceive call. + * with wait_all = true. */ bool IsSleepingOnWaitAll() const { - return !wait_objects.empty(); + return status == THREADSTATUS_WAIT_SYNCH_ALL; } ARM_Interface::ThreadContext context; @@ -178,15 +186,15 @@ public: /// Mutexes currently held by this thread, which will be released when it exits. boost::container::flat_set<SharedPtr<Mutex>> held_mutexes; + /// Mutexes that this thread is currently waiting for. + boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes; + SharedPtr<Process> owner_process; ///< Process that owns this thread - /// Objects that the thread is waiting on. - /// This is only populated when the thread should wait for all the objects to become ready. + /// Objects that the thread is waiting on, in the same order as they were + // passed to WaitSynchronization1/N. std::vector<SharedPtr<WaitObject>> wait_objects; - /// Mapping of Object ids to their position in the last waitlist that this object waited on. - boost::container::flat_map<int, s32> wait_objects_index; - VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address /// True if the WaitSynchronizationN output parameter should be set on thread wakeup. @@ -211,6 +219,11 @@ private: SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority); /** + * Returns whether there are any threads that are ready to run. + */ +bool HaveReadyThreads(); + +/** * Reschedules to the next available thread (call after current thread is suspended) */ void Reschedule(); @@ -238,15 +251,6 @@ Thread* GetCurrentThread(); void WaitCurrentThread_Sleep(); /** - * Waits the current thread from a WaitSynchronization call - * @param wait_objects Kernel objects that we are waiting on - * @param wait_set_output If true, set the output parameter on thread wakeup (for - * WaitSynchronizationN only) - */ -void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects, - bool wait_set_output); - -/** * Waits the current thread from an ArbitrateAddress call * @param wait_address Arbitration address used to resume from wait */ diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index b50cf520d..60537f355 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -31,20 +31,15 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) { timer->interval_delay = 0; timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom(); - if (reset_type == ResetType::Pulse) { - LOG_ERROR(Kernel, "Unimplemented timer reset type Pulse"); - UNIMPLEMENTED(); - } - return timer; } -bool Timer::ShouldWait() { +bool Timer::ShouldWait(Thread* thread) const { return !signaled; } -void Timer::Acquire() { - ASSERT_MSG(!ShouldWait(), "object unavailable!"); +void Timer::Acquire(Thread* thread) { + ASSERT_MSG(!ShouldWait(thread), "object unavailable!"); if (reset_type == ResetType::OneShot) signaled = false; @@ -70,6 +65,13 @@ void Timer::Clear() { signaled = false; } +void Timer::WakeupAllWaitingThreads() { + WaitObject::WakeupAllWaitingThreads(); + + if (reset_type == ResetType::Pulse) + signaled = false; +} + /// The timer callback event, called when a timer is fired static void TimerCallback(u64 timer_handle, int cycles_late) { SharedPtr<Timer> timer = diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index 18ea0236b..c174f5664 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -39,8 +39,10 @@ public: u64 initial_delay; ///< The delay until the timer fires for the first time u64 interval_delay; ///< The delay until the timer fires after the first time - bool ShouldWait() override; - void Acquire() override; + bool ShouldWait(Thread* thread) const override; + void Acquire(Thread* thread) override; + + void WakeupAllWaitingThreads() override; /** * Starts the timer, with the specified initial delay and interval. diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp index 6ab16ccd5..e0de037f8 100644 --- a/src/core/hle/service/boss/boss.cpp +++ b/src/core/hle/service/boss/boss.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include "core/hle/service/boss/boss.h" #include "core/hle/service/boss/boss_p.h" #include "core/hle/service/boss/boss_u.h" @@ -33,7 +34,8 @@ void InitializeSession(Service::Interface* self) { cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; - LOG_WARNING(Service_BOSS, "(STUBBED) unk_param=0x%016X, translation=0x%08X, unk_param4=0x%08X", + LOG_WARNING(Service_BOSS, + "(STUBBED) unk_param=0x%016" PRIX64 ", translation=0x%08X, unk_param4=0x%08X", unk_param, translation, unk_param4); } diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 0bf59eb76..59dd6d1cd 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -84,7 +84,6 @@ struct ConsoleCountryInfo { static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); } -static const u64 CFG_SAVE_ID = 0x00010017; static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE; static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}}; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp index 4f1dd2fce..c62f8afc6 100644 --- a/src/core/hle/service/mic_u.cpp +++ b/src/core/hle/service/mic_u.cpp @@ -99,7 +99,8 @@ static void StartSampling(Interface* self) { is_sampling = true; LOG_WARNING(Service_MIC, "(STUBBED) called, encoding=%u, sample_rate=%u, " "audio_buffer_offset=%d, audio_buffer_size=%u, audio_buffer_loop=%u", - encoding, sample_rate, audio_buffer_offset, audio_buffer_size, audio_buffer_loop); + static_cast<u32>(encoding), static_cast<u32>(sample_rate), audio_buffer_offset, + audio_buffer_size, audio_buffer_loop); } /** @@ -114,7 +115,7 @@ static void AdjustSampling(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); sample_rate = static_cast<SampleRate>(cmd_buff[1] & 0xFF); cmd_buff[1] = RESULT_SUCCESS.raw; // No error - LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", sample_rate); + LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", static_cast<u32>(sample_rate)); } /** diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index d9738c6a1..e248285f9 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/kernel/event.h" #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_m.h" #include "core/hle/service/nfc/nfc_u.h" @@ -9,9 +10,28 @@ namespace Service { namespace NFC { +static Kernel::SharedPtr<Kernel::Event> tag_in_range_event; + +void GetTagInRangeEvent(Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + cmd_buff[0] = IPC::MakeHeader(0xB, 1, 2); + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = IPC::CopyHandleDesc(); + cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).MoveFrom(); + LOG_WARNING(Service_NFC, "(STUBBED) called"); +} + void Init() { AddService(new NFC_M()); AddService(new NFC_U()); + + tag_in_range_event = + Kernel::Event::Create(Kernel::ResetType::OneShot, "NFC::tag_in_range_event"); +} + +void Shutdown() { + tag_in_range_event = nullptr; } } // namespace NFC diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h index cd65a5fdc..b02354201 100644 --- a/src/core/hle/service/nfc/nfc.h +++ b/src/core/hle/service/nfc/nfc.h @@ -5,10 +5,27 @@ #pragma once namespace Service { + +class Interface; + namespace NFC { +/** + * NFC::GetTagInRangeEvent service function + * Inputs: + * 0 : Header code [0x000B0000] + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Copy handle descriptor + * 3 : Event Handle + */ +void GetTagInRangeEvent(Interface* self); + /// Initialize all NFC services. void Init(); +/// Shutdown all NFC services. +void Shutdown(); + } // namespace NFC } // namespace Service diff --git a/src/core/hle/service/nfc/nfc_m.cpp b/src/core/hle/service/nfc/nfc_m.cpp index 717335c11..f43b4029a 100644 --- a/src/core/hle/service/nfc/nfc_m.cpp +++ b/src/core/hle/service/nfc/nfc_m.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_m.h" namespace Service { @@ -19,6 +20,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00070000, nullptr, "LoadAmiiboData"}, {0x00080000, nullptr, "ResetTagScanState"}, {0x00090002, nullptr, "UpdateStoredAmiiboData"}, + {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, {0x000D0000, nullptr, "GetTagState"}, {0x000F0000, nullptr, "CommunicationGetStatus"}, {0x00100000, nullptr, "GetTagInfo2"}, diff --git a/src/core/hle/service/nfc/nfc_u.cpp b/src/core/hle/service/nfc/nfc_u.cpp index deffb0b4f..4b5200ae8 100644 --- a/src/core/hle/service/nfc/nfc_u.cpp +++ b/src/core/hle/service/nfc/nfc_u.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfc/nfc_u.h" namespace Service { @@ -18,6 +19,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00070000, nullptr, "LoadAmiiboData"}, {0x00080000, nullptr, "ResetTagScanState"}, {0x00090002, nullptr, "UpdateStoredAmiiboData"}, + {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"}, {0x000D0000, nullptr, "GetTagState"}, {0x000F0000, nullptr, "CommunicationGetStatus"}, {0x00100000, nullptr, "GetTagInfo2"}, diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index e23f864a3..0672ac2e3 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -177,6 +177,7 @@ void Init() { /// Shutdown ServiceManager void Shutdown() { PTM::Shutdown(); + NFC::Shutdown(); NIM::Shutdown(); NEWS::Shutdown(); NDM::Shutdown(); diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp index c3918cdd0..dcc5c3c90 100644 --- a/src/core/hle/service/soc_u.cpp +++ b/src/core/hle/service/soc_u.cpp @@ -603,7 +603,6 @@ static void RecvFrom(Interface* self) { u32 socket_handle = cmd_buffer[1]; u32 len = cmd_buffer[2]; u32 flags = cmd_buffer[3]; - socklen_t addr_len = static_cast<socklen_t>(cmd_buffer[4]); struct { u32 output_buffer_descriptor; @@ -693,7 +692,6 @@ static void Poll(Interface* self) { static void GetSockName(Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t ctr_len = cmd_buffer[2]; // Memory address of the ctr_dest_addr structure VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; @@ -734,7 +732,6 @@ static void Shutdown(Interface* self) { static void GetPeerName(Interface* self) { u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t len = cmd_buffer[2]; // Memory address of the ctr_dest_addr structure VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2]; @@ -765,7 +762,6 @@ static void Connect(Interface* self) { // performing nonblocking operations and spinlock until the data is available u32* cmd_buffer = Kernel::GetCommandBuffer(); u32 socket_handle = cmd_buffer[1]; - socklen_t len = cmd_buffer[2]; // Memory address of the ctr_input_addr structure VAddr ctr_input_addr_addr = cmd_buffer[6]; diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 2ca270de3..2b242ff98 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -248,6 +248,8 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) { LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str()); + Core::System::GetInstance().PrepareReschedule(); + // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server // responds and cause a reschedule. return session->SendSyncRequest(); @@ -270,27 +272,27 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds) LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle, object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds); - if (object->ShouldWait()) { + if (object->ShouldWait(thread)) { if (nano_seconds == 0) return ERR_SYNC_TIMEOUT; + thread->wait_objects = {object}; object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. - thread->status = THREADSTATUS_WAIT_SYNCH; + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread // resumes due to a signal in its wait objects. // Otherwise we retain the default value of timeout. return ERR_SYNC_TIMEOUT; } - object->Acquire(); + object->Acquire(thread); return RESULT_SUCCESS; } @@ -324,19 +326,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha objects[i] = object; } - // Clear the mapping of wait object indices. - // We don't want any lingering state in this map. - // It will be repopulated later in the wait_all = false case. - thread->wait_objects_index.clear(); - if (wait_all) { bool all_available = std::all_of(objects.begin(), objects.end(), - [](const ObjectPtr& object) { return !object->ShouldWait(); }); + [thread](const ObjectPtr& object) { return !object->ShouldWait(thread); }); if (all_available) { // We can acquire all objects right now, do so. for (auto& object : objects) - object->Acquire(); + object->Acquire(thread); // Note: In this case, the `out` parameter is not set, // and retains whatever value it had before. return RESULT_SUCCESS; @@ -350,22 +347,20 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; // Put the thread to sleep - thread->status = THREADSTATUS_WAIT_SYNCH; + thread->status = THREADSTATUS_WAIT_SYNCH_ALL; // Add the thread to each of the objects' waiting threads. for (auto& object : objects) { object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. } - // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN thread->wait_objects = std::move(objects); // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // This value gets set to -1 by default in this case, it is not modified after this. *out = -1; // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to @@ -373,13 +368,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; } else { // Find the first object that is acquirable in the provided list of objects - auto itr = std::find_if(objects.begin(), objects.end(), - [](const ObjectPtr& object) { return !object->ShouldWait(); }); + auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) { + return !object->ShouldWait(thread); + }); if (itr != objects.end()) { // We found a ready object, acquire it and set the result value Kernel::WaitObject* object = itr->get(); - object->Acquire(); + object->Acquire(thread); *out = std::distance(objects.begin(), itr); return RESULT_SUCCESS; } @@ -392,28 +388,24 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha return ERR_SYNC_TIMEOUT; // Put the thread to sleep - thread->status = THREADSTATUS_WAIT_SYNCH; - - // Clear the thread's waitlist, we won't use it for wait_all = false - thread->wait_objects.clear(); + thread->status = THREADSTATUS_WAIT_SYNCH_ANY; // Add the thread to each of the objects' waiting threads. for (size_t i = 0; i < objects.size(); ++i) { Kernel::WaitObject* object = objects[i].get(); - // Set the index of this object in the mapping of Objects -> index for this thread. - thread->wait_objects_index[object->GetObjectId()] = static_cast<int>(i); object->AddWaitingThread(thread); - // TODO(Subv): Perform things like update the mutex lock owner's priority to - // prevent priority inversion. Currently this is done in Mutex::ShouldWait, - // but it should be moved to a function that is called from here. } + thread->wait_objects = std::move(objects); + // Note: If no handles and no timeout were given, then the thread will deadlock, this is // consistent with hardware behavior. // Create an event to wake the thread up after the specified nanosecond delay has passed thread->WakeAfterDelay(nano_seconds); + Core::System::GetInstance().PrepareReschedule(); + // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a // signal in one of its wait objects. // Otherwise we retain the default value of timeout, and -1 in the out parameter @@ -448,6 +440,9 @@ static ResultCode ArbitrateAddress(Kernel::Handle handle, u32 address, u32 type, auto res = arbiter->ArbitrateAddress(static_cast<Kernel::ArbitrationType>(type), address, value, nanoseconds); + // TODO(Subv): Identify in which specific cases this call should cause a reschedule. + Core::System::GetInstance().PrepareReschedule(); + return res; } @@ -574,6 +569,8 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(thread))); + Core::System::GetInstance().PrepareReschedule(); + LOG_TRACE(Kernel_SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, " "threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X", entry_point, name.c_str(), arg, stack_top, priority, processor_id, *out_handle); @@ -586,6 +583,7 @@ static void ExitThread() { LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::CPU().GetPC()); Kernel::ExitCurrentThread(); + Core::System::GetInstance().PrepareReschedule(); } /// Gets the priority for the specified thread @@ -605,6 +603,13 @@ static ResultCode SetThreadPriority(Kernel::Handle handle, s32 priority) { return ERR_INVALID_HANDLE; thread->SetPriority(priority); + thread->UpdatePriority(); + + // Update the mutexes that this thread is waiting for + for (auto& mutex : thread->pending_mutexes) + mutex->UpdatePriority(); + + Core::System::GetInstance().PrepareReschedule(); return RESULT_SUCCESS; } @@ -844,11 +849,18 @@ static ResultCode CancelTimer(Kernel::Handle handle) { static void SleepThread(s64 nanoseconds) { LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds); + // Don't attempt to yield execution if there are no available threads to run, + // this way we avoid a useless reschedule to the idle thread. + if (nanoseconds == 0 && !Kernel::HaveReadyThreads()) + return; + // Sleep current thread and check for next thread to schedule Kernel::WaitCurrentThread_Sleep(); // Create an event to wake the thread up after the specified nanosecond delay has passed Kernel::GetCurrentThread()->WakeAfterDelay(nanoseconds); + + Core::System::GetInstance().PrepareReschedule(); } /// This returns the total CPU ticks elapsed since the CPU was powered-on @@ -890,7 +902,11 @@ static ResultCode CreateMemoryBlock(Kernel::Handle* out_handle, u32 addr, u32 si return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); - if (addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) { + // TODO(Subv): Processes with memory type APPLICATION are not allowed + // to create memory blocks with addr = 0, any attempts to do so + // should return error 0xD92007EA. + if ((addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) && + addr != 0) { return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } @@ -1184,8 +1200,6 @@ void CallSVC(u32 immediate) { if (info) { if (info->func) { info->func(); - // TODO(Subv): Not all service functions should cause a reschedule in all cases. - Core::System::GetInstance().PrepareReschedule(); } else { LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name); } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index a204dc336..5df33f6d2 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -288,7 +288,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() { LOG_DEBUG(Loader, "Thread priority: 0x%X", priority); LOG_DEBUG(Loader, "Resource limit category: %d", resource_limit_category); LOG_DEBUG(Loader, "System Mode: %d", - exheader_header.arm11_system_local_caps.system_mode); + static_cast<int>(exheader_header.arm11_system_local_caps.system_mode)); if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index fe08f5b45..4ef95b5c6 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -181,7 +181,7 @@ public: * Loads the Exheader and returns the system mode for this application. * @return Optional with the kernel system mode */ - boost::optional<u32> LoadKernelSystemMode(); + boost::optional<u32> LoadKernelSystemMode() override; ResultStatus ReadCode(std::vector<u8>& buffer) override; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 5d23c52f9..9afaf79ec 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -20,7 +20,6 @@ void Apply() { VideoCore::g_hw_renderer_enabled = values.use_hw_renderer; VideoCore::g_shader_jit_enabled = values.use_shader_jit; - VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution; VideoCore::g_toggle_framelimit_enabled = values.toggle_framelimit; if (VideoCore::g_emu_window) { diff --git a/src/core/settings.h b/src/core/settings.h index 4e7a4b1be..8dbda653a 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -88,7 +88,7 @@ struct Values { // Renderer bool use_hw_renderer; bool use_shader_jit; - bool use_scaled_resolution; + float resolution_factor; bool use_vsync; bool toggle_framelimit; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 85aa06cd5..ef3b06a7b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -556,14 +556,21 @@ RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfi color_params.width = depth_params.width = config.GetWidth(); color_params.height = depth_params.height = config.GetHeight(); color_params.is_tiled = depth_params.is_tiled = true; - if (VideoCore::g_scaled_resolution_enabled) { - auto layout = VideoCore::g_emu_window->GetFramebufferLayout(); - // Assume same scaling factor for top and bottom screens + // Set the internal resolution, assume the same scaling factor for top and bottom screens + const Layout::FramebufferLayout& layout = VideoCore::g_emu_window->GetFramebufferLayout(); + if (Settings::values.resolution_factor == 0.0f) { + // Auto - scale resolution to the window size color_params.res_scale_width = depth_params.res_scale_width = (float)layout.top_screen.GetWidth() / VideoCore::kScreenTopWidth; color_params.res_scale_height = depth_params.res_scale_height = (float)layout.top_screen.GetHeight() / VideoCore::kScreenTopHeight; + } else { + // Otherwise, scale the resolution by the specified factor + color_params.res_scale_width = Settings::values.resolution_factor; + depth_params.res_scale_width = Settings::values.resolution_factor; + color_params.res_scale_height = Settings::values.resolution_factor; + depth_params.res_scale_height = Settings::values.resolution_factor; } color_params.addr = config.GetColorBufferPhysicalAddress(); diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index 70db4167e..20fb9754b 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -27,8 +27,6 @@ namespace Pica { namespace Shader { -constexpr u32 INVALID_ADDRESS = 0xFFFFFFFF; - struct CallStackElement { u32 final_address; // Address upon which we jump to return_address u32 return_address; // Where to jump when leaving scope diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 8db882f59..7186a7652 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -19,7 +19,6 @@ std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin std::atomic<bool> g_hw_renderer_enabled; std::atomic<bool> g_shader_jit_enabled; -std::atomic<bool> g_scaled_resolution_enabled; std::atomic<bool> g_vsync_enabled; std::atomic<bool> g_toggle_framelimit_enabled; diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index c397c1974..4aba19ca0 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -37,7 +37,6 @@ extern EmuWindow* g_emu_window; ///< Emu window // qt ui) extern std::atomic<bool> g_hw_renderer_enabled; extern std::atomic<bool> g_shader_jit_enabled; -extern std::atomic<bool> g_scaled_resolution_enabled; extern std::atomic<bool> g_toggle_framelimit_enabled; /// Start the video core |