diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 7b424a45dc..6ce5f17a3b 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -430,6 +430,7 @@ target_sources(rpcs3_emu PRIVATE RSX/Null/NullGSRender.cpp RSX/Overlays/overlay_animation.cpp RSX/Overlays/overlay_controls.cpp + RSX/Overlays/overlay_cursor.cpp RSX/Overlays/overlay_edit_text.cpp RSX/Overlays/overlay_fonts.cpp RSX/Overlays/overlay_list_view.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 22bd291d4c..e2142025f1 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -9,6 +9,7 @@ #include "Emu/system_config.h" #include "Emu/System.h" #include "Emu/IdManager.h" +#include "Emu/RSX/Overlays/overlay_cursor.h" #include "Input/pad_thread.h" #include // for fmod @@ -107,6 +108,23 @@ public: g = std::clamp(g_, 0.0f, 1.0f); b = std::clamp(b_, 0.0f, 1.0f); } + + static inline const gem_color& get_default_color(u32 gem_num) + { + static const gem_color gold = gem_color(1.0f, 0.85f, 0.0f); + static const gem_color green = gem_color(0.0f, 1.0f, 0.0f); + static const gem_color red = gem_color(1.0f, 0.0f, 0.0f); + static const gem_color pink = gem_color(0.9f, 0.0f, 0.5f); + + switch (gem_num) + { + case 0: return green; + case 1: return gold; + case 2: return red; + case 3: return pink; + default: fmt::throw_exception("unexpected gem_num %d", gem_num); + } + } }; struct gem_controller @@ -183,6 +201,11 @@ public: void reset_controller(u32 gem_num) { + if (gem_num >= CELL_GEM_MAX_NUM) + { + return; + } + switch (g_cfg.io.move) { case move_handler::fake: @@ -196,12 +219,15 @@ public: break; } + gem_controller& controller = controllers.at(gem_num); + controller = {}; + controller.sphere_rgb = gem_color::get_default_color(gem_num); + // Assign status and port number if (gem_num < connected_controllers) { - controllers[gem_num] = {}; - controllers[gem_num].status = CELL_GEM_STATUS_READY; - controllers[gem_num].port = 7u - gem_num; + controller.status = CELL_GEM_STATUS_READY; + controller.port = CELL_PAD_MAX_PORT_NUM - gem_num; } } @@ -420,7 +446,19 @@ static bool check_gem_num(const u32 gem_num) return gem_num < CELL_GEM_MAX_NUM; } -static inline void pos_to_gem_image_state(const gem_config::gem_controller& controller, vm::ptr& gem_image_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max) +static inline void draw_overlay_cursor(u32 gem_num, const gem_config::gem_controller& controller, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max) +{ + const u16 x = static_cast(x_pos / (x_max / static_cast(rsx::overlays::overlay::virtual_width))); + const u16 y = static_cast(y_pos / (y_max / static_cast(rsx::overlays::overlay::virtual_height))); + + // Note: We shouldn't use sphere_rgb here. The game will set it to black in many cases. + const gem_config_data::gem_color& rgb = gem_config_data::gem_color::get_default_color(gem_num); + const color4f color = { rgb.r, rgb.g, rgb.b, 0.85f }; + + rsx::overlays::set_cursor(rsx::overlays::cursor_offset::cell_gem + gem_num, x, y, color, 2'000'000, false); +} + +static inline void pos_to_gem_image_state(u32 gem_num, const gem_config::gem_controller& controller, vm::ptr& gem_image_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max) { const auto& shared_data = g_fxo->get(); @@ -450,9 +488,14 @@ static inline void pos_to_gem_image_state(const gem_config::gem_controller& cont // Projected camera coordinates in mm gem_image_state->projectionx = camera_x / controller.distance; gem_image_state->projectiony = camera_y / controller.distance; + + if (g_cfg.io.show_move_cursor) + { + draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max); + } } -static inline void pos_to_gem_state(const gem_config::gem_controller& controller, vm::ptr& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max) +static inline void pos_to_gem_state(u32 gem_num, const gem_config::gem_controller& controller, vm::ptr& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max) { const auto& shared_data = g_fxo->get(); @@ -490,6 +533,11 @@ static inline void pos_to_gem_state(const gem_config::gem_controller& controller gem_state->handle_pos[1] = camera_y; gem_state->handle_pos[2] = static_cast(controller.distance + 10); gem_state->handle_pos[3] = 0.f; + + if (g_cfg.io.show_move_cursor) + { + draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max); + } } extern bool is_input_allowed(); @@ -587,10 +635,10 @@ static inline void ds3_get_stick_values(const std::shared_ptr& pad, s32& x_ switch (stick.m_offset) { case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_X: - x_pos = stick.m_value - 128; + x_pos = stick.m_value; break; case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y: - y_pos = stick.m_value - 128; + y_pos = stick.m_value; break; default: break; @@ -618,7 +666,7 @@ static void ds3_pos_to_gem_image_state(const u32 port_no, const gem_config::gem_ s32 ds3_pos_x, ds3_pos_y; ds3_get_stick_values(pad, ds3_pos_x, ds3_pos_y); - pos_to_gem_image_state(controller, gem_image_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y); + pos_to_gem_image_state(port_no, controller, gem_image_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y); } static void ds3_pos_to_gem_state(const u32 port_no, const gem_config::gem_controller& controller, vm::ptr& gem_state) @@ -641,7 +689,7 @@ static void ds3_pos_to_gem_state(const u32 port_no, const gem_config::gem_contro s32 ds3_pos_x, ds3_pos_y; ds3_get_stick_values(pad, ds3_pos_x, ds3_pos_y); - pos_to_gem_state(controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y); + pos_to_gem_state(port_no, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y); } /** @@ -776,7 +824,7 @@ static void mouse_pos_to_gem_image_state(const u32 mouse_no, const gem_config::g const auto& mouse = handler.GetMice().at(mouse_no); - pos_to_gem_image_state(controller, gem_image_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max); + pos_to_gem_image_state(mouse_no, controller, gem_image_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max); } static void mouse_pos_to_gem_state(const u32 mouse_no, const gem_config::gem_controller& controller, vm::ptr& gem_state) @@ -800,7 +848,7 @@ static void mouse_pos_to_gem_state(const u32 mouse_no, const gem_config::gem_con const auto& mouse = handler.GetMice().at(mouse_no); - pos_to_gem_state(controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max); + pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max); } // ********************* @@ -1319,7 +1367,7 @@ error_code cellGemGetRGB(u32 gem_num, vm::ptr r, vm::ptr g, vm::pt return CELL_GEM_ERROR_INVALID_PARAMETER; } - auto& sphere_color = gem.controllers[gem_num].sphere_rgb; + const gem_config_data::gem_color& sphere_color = gem.controllers[gem_num].sphere_rgb; *r = sphere_color.r; *g = sphere_color.g; *b = sphere_color.b; diff --git a/rpcs3/Emu/RSX/Overlays/overlay_cursor.cpp b/rpcs3/Emu/RSX/Overlays/overlay_cursor.cpp new file mode 100644 index 0000000000..3ad3d3b191 --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_cursor.cpp @@ -0,0 +1,150 @@ +#include "stdafx.h" +#include "overlay_cursor.h" +#include "Emu/RSX/RSXThread.h" + +namespace rsx +{ + namespace overlays + { + cursor_item::cursor_item() + { + m_cross_h.set_size(15, 1); + m_cross_v.set_size(1, 15); + } + + bool cursor_item::set_position(u16 x, u16 y) + { + if (m_x == x && m_y == y) + { + return false; + } + + m_x = x; + m_y = y; + + m_cross_h.set_pos(m_x - m_cross_h.w / 2, m_y - m_cross_h.h / 2); + m_cross_v.set_pos(m_x - m_cross_v.w / 2, m_y - m_cross_v.h / 2); + + return true; + } + + bool cursor_item::set_color(color4f color) + { + if (m_cross_h.back_color == color && m_cross_v.back_color == color) + { + return false; + } + + m_cross_h.back_color = color; + m_cross_h.refresh(); + + m_cross_v.back_color = color; + m_cross_v.refresh(); + + return true; + } + + void cursor_item::set_expiration(u64 expiration_time) + { + m_expiration_time = expiration_time; + } + + bool cursor_item::update_visibility(u64 time) + { + m_visible = time <= m_expiration_time; + + return m_visible; + } + + compiled_resource cursor_item::get_compiled() + { + if (!m_visible) + { + return {}; + } + + compiled_resource cr = m_cross_h.get_compiled(); + cr.add(m_cross_v.get_compiled()); + + return cr; + } + + void cursor_manager::update() + { + if (!visible) + { + return; + } + + std::lock_guard lock(m_mutex); + + const u64 cur_time = get_system_time(); + bool any_cursor_visible = false; + + for (auto& entry : m_cursors) + { + any_cursor_visible |= entry.second.update_visibility(cur_time); + } + + if (!any_cursor_visible) + { + visible = false; + } + } + + compiled_resource cursor_manager::get_compiled() + { + if (!visible) + { + return {}; + } + + std::lock_guard lock(m_mutex); + + compiled_resource cr{}; + + for (auto& entry : m_cursors) + { + cr.add(entry.second.get_compiled()); + } + + return cr; + } + + void cursor_manager::update_cursor(u32 id, u16 x, u16 y, const color4f& color, u64 duration_us, bool force_update) + { + std::lock_guard lock(m_mutex); + + cursor_item& cursor = m_cursors[id]; + + bool is_dirty = cursor.set_position(x, y); + is_dirty |= cursor.set_color(color); + + if (is_dirty || force_update) + { + const u64 expiration_time = get_system_time() + duration_us; + cursor.set_expiration(expiration_time); + + if (cursor.update_visibility(expiration_time)) + { + visible = true; + } + } + } + + void set_cursor(u32 id, u16 x, u16 y, const color4f& color, u64 duration_us, bool force_update) + { + if (auto manager = g_fxo->try_get()) + { + auto cursor_overlay = manager->get(); + if (!cursor_overlay) + { + cursor_overlay = std::make_shared(); + cursor_overlay = manager->add(cursor_overlay); + } + cursor_overlay->update_cursor(id, x, y, color, duration_us, force_update); + } + } + + } // namespace overlays +} // namespace rsx diff --git a/rpcs3/Emu/RSX/Overlays/overlay_cursor.h b/rpcs3/Emu/RSX/Overlays/overlay_cursor.h new file mode 100644 index 0000000000..ebf39a4fc9 --- /dev/null +++ b/rpcs3/Emu/RSX/Overlays/overlay_cursor.h @@ -0,0 +1,54 @@ +#pragma once + +#include "overlays.h" +#include + +namespace rsx +{ + namespace overlays + { + enum cursor_offset : u32 + { + cell_gem = 0, // CELL_GEM_MAX_NUM = 4 Move controllers + last = 4 + }; + + class cursor_item + { + public: + cursor_item(); + + void set_expiration(u64 expiration_time); + bool set_position(u16 x, u16 y); + bool set_color(color4f color); + + bool update_visibility(u64 time); + + compiled_resource get_compiled(); + + private: + bool m_visible = false; + overlay_element m_cross_h{}; + overlay_element m_cross_v{}; + u64 m_expiration_time = 0; + u16 m_x = 0; + u16 m_y = 0; + }; + + class cursor_manager final : public overlay + { + public: + void update() override; + compiled_resource get_compiled() override; + + void update_cursor(u32 id, u16 x, u16 y, const color4f& color, u64 duration_us, bool force_update); + + private: + shared_mutex m_mutex; + std::map m_cursors; + }; + + void set_cursor(u32 id, u16 x, u16 y, const color4f& color, u64 duration_us, bool force_update); + + } // namespace overlays +} // namespace rsx diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message.h b/rpcs3/Emu/RSX/Overlays/overlay_message.h index 7fa0fdc544..62e429d819 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_message.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_message.h @@ -26,7 +26,7 @@ namespace rsx usz m_cur_pos = umax; }; - class message final : public user_interface + class message final : public overlay { public: void update() override; diff --git a/rpcs3/Emu/RSX/Overlays/overlays.h b/rpcs3/Emu/RSX/Overlays/overlays.h index fd08e59291..e828c7449f 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.h +++ b/rpcs3/Emu/RSX/Overlays/overlays.h @@ -22,8 +22,8 @@ namespace rsx u32 uid = umax; u32 type_index = umax; - u16 virtual_width = 1280; - u16 virtual_height = 720; + static const u16 virtual_width = 1280; + static const u16 virtual_height = 720; u32 min_refresh_duration_us = 16600; atomic_t visible = false; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 194f2b82dc..858a5c3079 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -280,6 +280,7 @@ struct cfg_root : cfg::node cfg::_enum pad_mode{this, "Pad handler mode", pad_handler_mode::single_threaded, true}; cfg::uint<0, 100'000> pad_sleep{this, "Pad handler sleep (microseconds)", 1'000, true}; cfg::_bool background_input_enabled{this, "Background input enabled", true, true}; + cfg::_bool show_move_cursor{this, "Show move cursor", false, true}; } io{ this }; struct node_sys : cfg::node diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index c92b2e3a58..bd423171d9 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -78,6 +78,7 @@ + @@ -506,6 +507,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 2b164a04d8..ce41a00dbe 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1081,6 +1081,9 @@ Loader + + Emu\GPU\RSX\Overlays + @@ -2149,6 +2152,9 @@ Loader + + Emu\GPU\RSX\Overlays + diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 5bc8dc400b..bd63c955a8 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -138,6 +138,7 @@ enum class emu_settings_type // Input / Output BackgroundInput, + ShowMoveCursor, PadHandlerMode, KeyboardHandler, MouseHandler, @@ -312,6 +313,7 @@ inline static const QMap settings_location = // Input / Output { emu_settings_type::BackgroundInput, { "Input/Output", "Background input enabled"}}, + { emu_settings_type::ShowMoveCursor, { "Input/Output", "Show move cursor"}}, { emu_settings_type::PadHandlerMode, { "Input/Output", "Pad handler mode"}}, { emu_settings_type::KeyboardHandler, { "Input/Output", "Keyboard"}}, { emu_settings_type::MouseHandler, { "Input/Output", "Mouse"}}, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 93b1c54fce..3e72ca59d0 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1179,6 +1179,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->backgroundInputBox, emu_settings_type::BackgroundInput); SubscribeTooltip(ui->backgroundInputBox, tooltips.settings.background_input); + m_emu_settings->EnhanceCheckBox(ui->showMoveCursorBox, emu_settings_type::ShowMoveCursor); + SubscribeTooltip(ui->showMoveCursorBox, tooltips.settings.show_move_cursor); + // _____ _ _______ _ // / ____| | | |__ __| | | // | (___ _ _ ___| |_ ___ _ __ ___ | | __ _| |__ diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index d8cd2b5126..9ca8dce7b7 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -1685,6 +1685,13 @@ + + + + Show PS Move Cursor + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 070db1f21f..d91b13ecc4 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -215,6 +215,7 @@ public: const QString turntable = tr("DJ Hero Turntable controller support.\nSelect 1 or 2 controllers if the game requires DJ Hero Turntable controllers and you don't have real turntable controllers.\nSelect Null if the game has support for DualShock or if you have real turntable controllers.\nA real turntable controller can be used at the same time as an emulated turntable controller."); const QString ghltar = tr("Guitar Hero Live (GHL) Guitar controller support.\nSelect 1 or 2 controllers if the game requires GHL Guitar controllers and you don't have real guitar controllers.\nSelect Null if the game has support for DualShock or if you have real guitar controllers.\nA real guitar controller can be used at the same time as an emulated guitar controller."); const QString background_input = tr("Allows pad and keyboard input while the game window is unfocused."); + const QString show_move_cursor = tr("Shows the raw position of the PS Move input.\nThis can be very helpful during calibration screens."); // network