diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 2a9f54221..924f66839 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -69,7 +69,7 @@ std::optional Controller::GetAnalogInputBytes() const void Controller::LoadSettings(SettingsInterface& si, const char* section) {} -bool Controller::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) +bool Controller::GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) { return false; } diff --git a/src/core/controller.h b/src/core/controller.h index 282894c59..7873da645 100644 --- a/src/core/controller.h +++ b/src/core/controller.h @@ -88,7 +88,7 @@ public: virtual void LoadSettings(SettingsInterface& si, const char* section); /// Returns the software cursor to use for this controller, if any. - virtual bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode); + virtual bool GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode); /// Creates a new controller of the specified type. static std::unique_ptr Create(ControllerType type, u32 index); diff --git a/src/core/guncon.cpp b/src/core/guncon.cpp index bc61daf5f..d7de1468a 100644 --- a/src/core/guncon.cpp +++ b/src/core/guncon.cpp @@ -4,7 +4,6 @@ #include "guncon.h" #include "gpu.h" #include "host.h" -#include "resources.h" #include "system.h" #include "util/gpu_device.h" @@ -12,6 +11,7 @@ #include "common/assert.h" #include "common/log.h" +#include "common/path.h" #include @@ -19,7 +19,9 @@ Log_SetChannel(GunCon); static constexpr std::array(GunCon::Button::Count)> s_button_indices = {{13, 3, 14}}; -GunCon::GunCon(u32 index) : Controller(index) {} +GunCon::GunCon(u32 index) : Controller(index) +{ +} GunCon::~GunCon() = default; @@ -248,22 +250,10 @@ void GunCon::LoadSettings(SettingsInterface& si, const char* section) { Controller::LoadSettings(si, section); - std::string path = si.GetStringValue(section, "CrosshairImagePath"); - if (path != m_crosshair_image_path) - { - m_crosshair_image_path = std::move(path); - if (m_crosshair_image_path.empty() || !m_crosshair_image.LoadFromFile(m_crosshair_image_path.c_str())) - { - m_crosshair_image.Invalidate(); - } - } - + m_crosshair_image_path = si.GetStringValue(section, "CrosshairImagePath"); #ifndef __ANDROID__ - if (!m_crosshair_image.IsValid()) - { - m_crosshair_image.SetPixels(Resources::CROSSHAIR_IMAGE_WIDTH, Resources::CROSSHAIR_IMAGE_HEIGHT, - Resources::CROSSHAIR_IMAGE_DATA.data()); - } + if (m_crosshair_image_path.empty()) + m_crosshair_image_path = Path::Combine(EmuFolders::Resources, "images/crosshair.png"); #endif m_crosshair_image_scale = si.GetFloatValue(section, "CrosshairScale", 1.0f); @@ -271,12 +261,12 @@ void GunCon::LoadSettings(SettingsInterface& si, const char* section) m_x_scale = si.GetFloatValue(section, "XScale", 1.0f); } -bool GunCon::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) +bool GunCon::GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) { - if (!m_crosshair_image.IsValid()) + if (m_crosshair_image_path.empty()) return false; - *image = &m_crosshair_image; + *image_path = m_crosshair_image_path; *image_scale = m_crosshair_image_scale; *relative_mode = false; return true; diff --git a/src/core/guncon.h b/src/core/guncon.h index d8aec5c1c..6fb712d01 100644 --- a/src/core/guncon.h +++ b/src/core/guncon.h @@ -32,7 +32,7 @@ public: bool DoState(StateWrapper& sw, bool apply_input_state) override; void LoadSettings(SettingsInterface& si, const char* section) override; - bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) override; + bool GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) override; float GetBindState(u32 index) const override; void SetBindState(u32 index, float value) override; @@ -56,7 +56,6 @@ private: YMSB }; - Common::RGBA8Image m_crosshair_image; std::string m_crosshair_image_path; float m_crosshair_image_scale = 1.0f; float m_x_scale = 1.0f; diff --git a/src/core/host.cpp b/src/core/host.cpp index e41a14226..ff8efb97c 100644 --- a/src/core/host.cpp +++ b/src/core/host.cpp @@ -349,6 +349,7 @@ void Host::RenderDisplay(bool skip_present) FullscreenUI::Render(); ImGuiManager::RenderTextOverlays(); ImGuiManager::RenderOSDMessages(); + ImGuiManager::RenderSoftwareCursors(); } // Debug windows are always rendered, otherwise mouse input breaks on skip. diff --git a/src/core/playstation_mouse.cpp b/src/core/playstation_mouse.cpp index e0a998459..e2505e380 100644 --- a/src/core/playstation_mouse.cpp +++ b/src/core/playstation_mouse.cpp @@ -215,7 +215,7 @@ void PlayStationMouse::LoadSettings(SettingsInterface& si, const char* section) m_use_relative_mode = si.GetBoolValue(section, "RelativeMouseMode", false); } -bool PlayStationMouse::GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) +bool PlayStationMouse::GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) { *relative_mode = m_use_relative_mode; return m_use_relative_mode; diff --git a/src/core/playstation_mouse.h b/src/core/playstation_mouse.h index be1c18546..11fce1939 100644 --- a/src/core/playstation_mouse.h +++ b/src/core/playstation_mouse.h @@ -36,7 +36,7 @@ public: bool Transfer(const u8 data_in, u8* data_out) override; void LoadSettings(SettingsInterface& si, const char* section) override; - bool GetSoftwareCursor(const Common::RGBA8Image** image, float* image_scale, bool* relative_mode) override; + bool GetSoftwareCursor(std::string* image_path, float* image_scale, bool* relative_mode) override; private: void UpdatePosition(); diff --git a/src/core/system.cpp b/src/core/system.cpp index c9dd51a42..82003a97e 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -4624,11 +4624,11 @@ void System::UpdateSoftwareCursor() if (!IsValid()) { Host::SetMouseMode(false, false); - g_gpu_device->ClearSoftwareCursor(); + ImGuiManager::ClearSoftwareCursor(0); return; } - const Common::RGBA8Image* image = nullptr; + std::string image_path; float image_scale = 1.0f; bool relative_mode = false; bool hide_cursor = false; @@ -4636,7 +4636,7 @@ void System::UpdateSoftwareCursor() for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { Controller* controller = System::GetController(i); - if (controller && controller->GetSoftwareCursor(&image, &image_scale, &relative_mode)) + if (controller && controller->GetSoftwareCursor(&image_path, &image_scale, &relative_mode)) { hide_cursor = true; break; @@ -4645,15 +4645,10 @@ void System::UpdateSoftwareCursor() Host::SetMouseMode(relative_mode, hide_cursor); - if (image && image->IsValid()) - { - g_gpu_device->SetSoftwareCursor(image->GetPixels(), image->GetWidth(), image->GetHeight(), image->GetPitch(), - image_scale); - } + if (!image_path.empty()) + ImGuiManager::SetSoftwareCursor(0, std::move(image_path), image_scale); else - { - g_gpu_device->ClearSoftwareCursor(); - } + ImGuiManager::ClearSoftwareCursor(0); } void System::RequestDisplaySize(float scale /*= 0.0f*/) diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 21d3782d6..5d930a634 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -20,7 +20,6 @@ #include "fmt/format.h" #include "imgui.h" -#include "stb_image.h" #include "stb_image_resize.h" #include "stb_image_write.h" @@ -432,13 +431,10 @@ bool GPUDevice::CreateResources() std::unique_ptr display_vs = CreateShader(GPUShaderStage::Vertex, shadergen.GenerateDisplayVertexShader()); std::unique_ptr display_fs = CreateShader(GPUShaderStage::Fragment, shadergen.GenerateDisplayFragmentShader(true)); - std::unique_ptr cursor_fs = - CreateShader(GPUShaderStage::Fragment, shadergen.GenerateDisplayFragmentShader(false)); - if (!display_vs || !display_fs || !cursor_fs) + if (!display_vs || !display_fs) return false; GL_OBJECT_NAME(display_vs, "Display Vertex Shader"); GL_OBJECT_NAME(display_fs, "Display Fragment Shader"); - GL_OBJECT_NAME(cursor_fs, "Cursor Fragment Shader"); plconfig.vertex_shader = display_vs.get(); plconfig.fragment_shader = display_fs.get(); @@ -446,12 +442,6 @@ bool GPUDevice::CreateResources() return false; GL_OBJECT_NAME(m_display_pipeline, "Display Pipeline"); - plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState(); - plconfig.fragment_shader = cursor_fs.get(); - if (!(m_cursor_pipeline = CreatePipeline(plconfig))) - return false; - GL_OBJECT_NAME(m_cursor_pipeline, "Cursor Pipeline"); - std::unique_ptr imgui_vs = CreateShader(GPUShaderStage::Vertex, shadergen.GenerateImGuiVertexShader()); std::unique_ptr imgui_fs = CreateShader(GPUShaderStage::Fragment, shadergen.GenerateImGuiFragmentShader()); if (!imgui_vs || !imgui_fs) @@ -472,6 +462,7 @@ bool GPUDevice::CreateResources() plconfig.input_layout.vertex_stride = sizeof(ImDrawVert); plconfig.vertex_shader = imgui_vs.get(); plconfig.fragment_shader = imgui_fs.get(); + plconfig.blend = GPUPipeline::BlendState::GetAlphaBlendingState(); m_imgui_pipeline = CreatePipeline(plconfig); if (!m_imgui_pipeline) @@ -486,12 +477,9 @@ bool GPUDevice::CreateResources() void GPUDevice::DestroyResources() { - m_cursor_texture.reset(); - m_imgui_font_texture.reset(); m_imgui_pipeline.reset(); - m_cursor_pipeline.reset(); m_display_pipeline.reset(); m_imgui_pipeline.reset(); @@ -862,61 +850,6 @@ float GPUDevice::GetAndResetAccumulatedGPUTime() return 0.0f; } -void GPUDevice::SetSoftwareCursor(std::unique_ptr texture, float scale /*= 1.0f*/) -{ - if (texture) - texture->MakeReadyForSampling(); - - m_cursor_texture = std::move(texture); - m_cursor_texture_scale = scale; -} - -bool GPUDevice::SetSoftwareCursor(const void* pixels, u32 width, u32 height, u32 stride, float scale /*= 1.0f*/) -{ - std::unique_ptr tex = - CreateTexture(width, height, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8, pixels, stride, false); - if (!tex) - return false; - - SetSoftwareCursor(std::move(tex), scale); - return true; -} - -bool GPUDevice::SetSoftwareCursor(const char* path, float scale /*= 1.0f*/) -{ - auto fp = FileSystem::OpenManagedCFile(path, "rb"); - if (!fp) - { - return false; - } - - int width, height, file_channels; - u8* pixel_data = stbi_load_from_file(fp.get(), &width, &height, &file_channels, 4); - if (!pixel_data) - { - const char* error_reason = stbi_failure_reason(); - Log_ErrorPrintf("Failed to load image from '%s': %s", path, error_reason ? error_reason : "unknown error"); - return false; - } - - std::unique_ptr tex = - CreateTexture(static_cast(width), static_cast(height), 1, 1, 1, GPUTexture::Type::Texture, - GPUTexture::Format::RGBA8, pixel_data, sizeof(u32) * static_cast(width), false); - stbi_image_free(pixel_data); - if (!tex) - return false; - - Log_InfoPrintf("Loaded %dx%d image from '%s' for software cursor", width, height, path); - SetSoftwareCursor(std::move(tex), scale); - return true; -} - -void GPUDevice::ClearSoftwareCursor() -{ - m_cursor_texture.reset(); - m_cursor_texture_scale = 1.0f; -} - bool GPUDevice::IsUsingLinearFiltering() const { return g_settings.display_linear_filtering; @@ -962,7 +895,6 @@ bool GPUDevice::Render(bool skip_present) SetViewportAndScissor(0, 0, GetWindowWidth(), GetWindowHeight()); RenderImGui(); - RenderSoftwareCursor(); EndPresent(); return true; @@ -1054,27 +986,6 @@ bool GPUDevice::RenderDisplay(GPUFramebuffer* target, s32 left, s32 top, s32 wid } } -void GPUDevice::RenderSoftwareCursor() -{ - if (!HasSoftwareCursor()) - return; - - const auto [left, top, width, height] = CalculateSoftwareCursorDrawRect(); - RenderSoftwareCursor(left, top, width, height, m_cursor_texture.get()); -} - -void GPUDevice::RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture) -{ - SetPipeline(m_display_pipeline.get()); - SetTextureSampler(0, texture, m_linear_sampler.get()); - - const float uniforms[4] = {0.0f, 0.0f, 1.0f, 1.0f}; - PushUniformBuffer(uniforms, sizeof(uniforms)); - - SetViewportAndScissor(left, top, width, height); - Draw(3, 0); -} - void GPUDevice::CalculateDrawRect(s32 window_width, s32 window_height, float* out_left, float* out_top, float* out_width, float* out_height, float* out_left_padding, float* out_top_padding, float* out_scale, float* out_x_scale, bool apply_aspect_ratio /* = true */) const @@ -1193,25 +1104,6 @@ std::tuple GPUDevice::CalculateDrawRect(s32 window_width, s3 static_cast(width), static_cast(height)); } -std::tuple GPUDevice::CalculateSoftwareCursorDrawRect() const -{ - return CalculateSoftwareCursorDrawRect(m_mouse_position_x, m_mouse_position_y); -} - -std::tuple GPUDevice::CalculateSoftwareCursorDrawRect(s32 cursor_x, s32 cursor_y) const -{ - const float scale = m_window_info.surface_scale * m_cursor_texture_scale; - const u32 cursor_extents_x = static_cast(static_cast(m_cursor_texture->GetWidth()) * scale * 0.5f); - const u32 cursor_extents_y = static_cast(static_cast(m_cursor_texture->GetHeight()) * scale * 0.5f); - - const s32 out_left = cursor_x - cursor_extents_x; - const s32 out_top = cursor_y - cursor_extents_y; - const s32 out_width = cursor_extents_x * 2u; - const s32 out_height = cursor_extents_y * 2u; - - return std::tie(out_left, out_top, out_width, out_height); -} - std::tuple GPUDevice::ConvertWindowCoordinatesToDisplayCoordinates(s32 window_x, s32 window_y, s32 window_width, s32 window_height) const diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 12f60ad04..9b732bab9 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -638,18 +638,6 @@ public: /// Returns the amount of GPU time utilized since the last time this method was called. virtual float GetAndResetAccumulatedGPUTime(); - /// Sets the software cursor to the specified texture. Ownership of the texture is transferred. - void SetSoftwareCursor(std::unique_ptr texture, float scale = 1.0f); - - /// Sets the software cursor to the specified image. - bool SetSoftwareCursor(const void* pixels, u32 width, u32 height, u32 stride, float scale = 1.0f); - - /// Sets the software cursor to the specified path (png image). - bool SetSoftwareCursor(const char* path, float scale = 1.0f); - - /// Disables the software cursor. - void ClearSoftwareCursor(); - /// Helper function for computing the draw rectangle in a larger window. std::tuple CalculateDrawRect(s32 window_width, s32 window_height, bool apply_aspect_ratio = true) const; @@ -705,7 +693,6 @@ protected: bool m_debug_device = false; private: - ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast(m_cursor_texture); } ALWAYS_INLINE bool HasDisplayTexture() const { return (m_display_texture != nullptr); } void OpenShaderCache(const std::string_view& base_path, u32 version); @@ -719,17 +706,11 @@ private: float* out_height, float* out_left_padding, float* out_top_padding, float* out_scale, float* out_x_scale, bool apply_aspect_ratio = true) const; - std::tuple CalculateSoftwareCursorDrawRect() const; - std::tuple CalculateSoftwareCursorDrawRect(s32 cursor_x, s32 cursor_y) const; - void RenderImGui(); - void RenderSoftwareCursor(); - bool RenderDisplay(GPUFramebuffer* target, s32 left, s32 top, s32 width, s32 height, GPUTexture* texture, s32 texture_view_x, s32 texture_view_y, s32 texture_view_width, s32 texture_view_height, bool linear_filter); - void RenderSoftwareCursor(s32 left, s32 top, s32 width, s32 height, GPUTexture* texture); u64 m_last_frame_displayed_time = 0; @@ -755,10 +736,6 @@ private: std::unique_ptr m_imgui_pipeline; std::unique_ptr m_imgui_font_texture; - std::unique_ptr m_cursor_pipeline; - std::unique_ptr m_cursor_texture; - float m_cursor_texture_scale = 1.0f; - bool m_display_changed = false; std::unique_ptr m_post_processing_chain; diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 67f43375f..d0202eff2 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -9,6 +9,7 @@ #include "common/assert.h" #include "common/file_system.h" +#include "common/image.h" #include "common/log.h" #include "common/string_util.h" #include "common/timer.h" @@ -28,6 +29,17 @@ Log_SetChannel(ImGuiManager); namespace ImGuiManager { +struct SoftwareCursor +{ + std::string image_path; + std::unique_ptr texture; + u32 color; + float scale; + float extent_x; + float extent_y; + std::pair pos; +}; + static void SetStyle(); static void SetKeyMap(); static bool LoadFontData(); @@ -37,6 +49,10 @@ static ImFont* AddFixedFont(float size); static bool AddIconFonts(float size); static void AcquirePendingOSDMessages(); static void DrawOSDMessages(); +static void CreateSoftwareCursorTextures(); +static void UpdateSoftwareCursorTexture(u32 index); +static void DestroySoftwareCursorTextures(); +static void DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair& pos); } // namespace ImGuiManager static float s_global_prescale = 1.0f; // before window scale @@ -63,6 +79,20 @@ static std::atomic_bool s_imgui_wants_mouse{false}; // mapping of host key -> imgui key static std::unordered_map s_imgui_key_map; +struct OSDMessage +{ + std::string key; + std::string text; + std::chrono::steady_clock::time_point time; + float duration; +}; + +static std::deque s_osd_active_messages; +static std::deque s_osd_posted_messages; +static std::mutex s_osd_messages_lock; + +static std::array s_software_cursors = {}; + void ImGuiManager::SetFontPath(std::string path) { s_font_path = std::move(path); @@ -125,11 +155,15 @@ bool ImGuiManager::Initialize(float global_scale) ImGui::GetIO().Fonts->ClearTexData(); NewFrame(); + + CreateSoftwareCursorTextures(); return true; } void ImGuiManager::Shutdown() { + DestroySoftwareCursorTextures(); + if (ImGui::GetCurrentContext()) ImGui::DestroyContext(); @@ -541,18 +575,6 @@ bool ImGuiManager::HasFullscreenFonts() return (s_medium_font && s_large_font); } -struct OSDMessage -{ - std::string key; - std::string text; - std::chrono::steady_clock::time_point time; - float duration; -}; - -static std::deque s_osd_active_messages; -static std::deque s_osd_posted_messages; -static std::mutex s_osd_messages_lock; - void Host::AddOSDMessage(std::string message, float duration /*= 2.0f*/) { AddKeyedOSDMessage(std::string(), std::move(message), duration); @@ -634,9 +656,9 @@ void ImGuiManager::AcquirePendingOSDMessages() OSDMessage& new_msg = s_osd_posted_messages.front(); std::deque::iterator iter; if (!new_msg.key.empty() && (iter = std::find_if(s_osd_active_messages.begin(), s_osd_active_messages.end(), - [&new_msg](const OSDMessage& other) { - return new_msg.key == other.key; - })) != s_osd_active_messages.end()) + [&new_msg](const OSDMessage& other) { + return new_msg.key == other.key; + })) != s_osd_active_messages.end()) { iter->text = std::move(new_msg.text); iter->duration = new_msg.duration; @@ -842,3 +864,110 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value ImGui::GetIO().AddKeyAnalogEvent(key_map[static_cast(key)], (value > 0.0f), value); return true; } + +void ImGuiManager::CreateSoftwareCursorTextures() +{ + for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++) + { + if (!s_software_cursors[i].image_path.empty()) + UpdateSoftwareCursorTexture(i); + } +} + +void ImGuiManager::DestroySoftwareCursorTextures() +{ + for (u32 i = 0; i < InputManager::MAX_POINTER_DEVICES; i++) + { + s_software_cursors[i].texture.reset(); + } +} + +void ImGuiManager::UpdateSoftwareCursorTexture(u32 index) +{ + SoftwareCursor& sc = s_software_cursors[index]; + if (sc.image_path.empty()) + { + sc.texture.reset(); + return; + } + + Common::RGBA8Image image; + if (!image.LoadFromFile(sc.image_path.c_str())) + { + Log_ErrorPrintf("Failed to load software cursor %u image '%s'", index, sc.image_path.c_str()); + return; + } + sc.texture = g_gpu_device->CreateTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture, + GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch()); + if (!sc.texture) + { + Log_ErrorPrintf("Failed to upload %ux%u software cursor %u image '%s'", image.GetWidth(), image.GetHeight(), index, + sc.image_path.c_str()); + return; + } + + sc.extent_x = std::ceil(static_cast(image.GetWidth()) * sc.scale * s_global_scale) / 2.0f; + sc.extent_y = std::ceil(static_cast(image.GetHeight()) * sc.scale * s_global_scale) / 2.0f; +} + +void ImGuiManager::DrawSoftwareCursor(const SoftwareCursor& sc, const std::pair& pos) +{ + if (!sc.texture) + return; + + const ImVec2 min(pos.first - sc.extent_x, pos.second - sc.extent_y); + const ImVec2 max(pos.first + sc.extent_x, pos.second + sc.extent_y); + + ImDrawList* dl = ImGui::GetForegroundDrawList(); + + dl->AddImage(reinterpret_cast(sc.texture.get()), min, max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), + sc.color); +} + +void ImGuiManager::RenderSoftwareCursors() +{ + // This one's okay to race, worst that happens is we render the wrong number of cursors for a frame. + const u32 pointer_count = InputManager::MAX_POINTER_DEVICES; + for (u32 i = 0; i < pointer_count; i++) + DrawSoftwareCursor(s_software_cursors[i], InputManager::GetPointerAbsolutePosition(i)); + + for (u32 i = InputManager::MAX_POINTER_DEVICES; i < InputManager::MAX_SOFTWARE_CURSORS; i++) + DrawSoftwareCursor(s_software_cursors[i], s_software_cursors[i].pos); +} + +void ImGuiManager::SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color) +{ + DebugAssert(index < std::size(s_software_cursors)); + SoftwareCursor& sc = s_software_cursors[index]; + sc.color = multiply_color | 0xFF000000; + if (sc.image_path == image_path && sc.scale == image_scale) + return; + + const bool is_hiding_or_showing = (image_path.empty() != sc.image_path.empty()); + sc.image_path = std::move(image_path); + sc.scale = image_scale; + if (g_gpu_device) + UpdateSoftwareCursorTexture(index); + + // Hide the system cursor when we activate a software cursor. + if (is_hiding_or_showing && index == 0) + InputManager::UpdateHostMouseMode(); +} + +bool ImGuiManager::HasSoftwareCursor(u32 index) +{ + return (index < s_software_cursors.size() && !s_software_cursors[index].image_path.empty()); +} + +void ImGuiManager::ClearSoftwareCursor(u32 index) +{ + SetSoftwareCursor(index, std::string(), 0.0f, 0); +} + +void ImGuiManager::SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y) +{ + DebugAssert(index >= InputManager::MAX_POINTER_DEVICES); + SoftwareCursor& sc = s_software_cursors[index]; + sc.pos.first = pos_x; + sc.pos.second = pos_y; +} diff --git a/src/util/imgui_manager.h b/src/util/imgui_manager.h index b5ec12dcb..5cd822508 100644 --- a/src/util/imgui_manager.h +++ b/src/util/imgui_manager.h @@ -85,6 +85,17 @@ bool ProcessHostKeyEvent(InputBindingKey key, float value); /// Called on the CPU thread when any input event fires. Allows imgui to take over controller navigation. bool ProcessGenericInputEvent(GenericInputBinding key, float value); + +/// Sets an image and scale for a software cursor. Software cursors can be used for things like crosshairs. +void SetSoftwareCursor(u32 index, std::string image_path, float image_scale, u32 multiply_color = 0xFFFFFF); +bool HasSoftwareCursor(u32 index); +void ClearSoftwareCursor(u32 index); + +/// Sets the position of a software cursor, used when we have relative coordinates such as controllers. +void SetSoftwareCursorPosition(u32 index, float pos_x, float pos_y); + +/// Adds software cursors to ImGui render list. +void RenderSoftwareCursors(); } // namespace ImGuiManager namespace Host { diff --git a/src/util/input_manager.cpp b/src/util/input_manager.cpp index a12f48212..8e713f32c 100644 --- a/src/util/input_manager.cpp +++ b/src/util/input_manager.cpp @@ -1026,6 +1026,12 @@ void InputManager::GenerateRelativeMouseEvents() } } +std::pair InputManager::GetPointerAbsolutePosition(u32 index) +{ + return std::make_pair(s_host_pointer_positions[index][static_cast(InputPointerAxis::X)], + s_host_pointer_positions[index][static_cast(InputPointerAxis::Y)]); +} + void InputManager::UpdatePointerAbsolutePosition(u32 index, float x, float y) { const float dx = x - std::exchange(s_host_pointer_positions[index][static_cast(InputPointerAxis::X)], x); @@ -1046,6 +1052,11 @@ void InputManager::UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, std::memory_order_release); } +void InputManager::UpdateHostMouseMode() +{ + // TODO: Move from System to here. +} + bool InputManager::IsUsingRawInput() { #if defined(_WIN32) diff --git a/src/util/input_manager.h b/src/util/input_manager.h index 8c621ebac..03947e0d4 100644 --- a/src/util/input_manager.h +++ b/src/util/input_manager.h @@ -174,6 +174,11 @@ static constexpr double VIBRATION_UPDATE_INTERVAL_SECONDS = 0.5; // 500ms /// Maximum number of host mouse devices. static constexpr u32 MAX_POINTER_DEVICES = 1; +static constexpr u32 MAX_POINTER_BUTTONS = 3; + +/// Maximum number of software cursors. We allocate an extra two for controllers with +/// positioning data from the controller instead of a mouse. +static constexpr u32 MAX_SOFTWARE_CURSORS = MAX_POINTER_BUTTONS + 2; /// Number of macro buttons per controller. static constexpr u32 NUM_MACRO_BUTTONS_PER_CONTROLLER = 4; @@ -302,6 +307,9 @@ void SetPadVibrationIntensity(u32 pad_index, float large_or_single_motor_intensi /// The pad vibration state will internally remain, so that when emulation is unpaused, the effect resumes. void PauseVibration(); +/// Reads absolute pointer position. +std::pair GetPointerAbsolutePosition(u32 index); + /// Updates absolute pointer position. Can call from UI thread, use when the host only reports absolute coordinates. void UpdatePointerAbsolutePosition(u32 index, float x, float y); @@ -309,6 +317,9 @@ void UpdatePointerAbsolutePosition(u32 index, float x, float y); /// reporting. void UpdatePointerRelativeDelta(u32 index, InputPointerAxis axis, float d, bool raw_input = false); +/// Updates host mouse mode (relative/cursor hiding). +void UpdateHostMouseMode(); + /// Sets the state of the specified macro button. void SetMacroButtonState(u32 pad, u32 index, bool state);