From f6c7681ef0043140c354bdb8274ac1f43763283d Mon Sep 17 00:00:00 2001 From: Stenzek Date: Mon, 13 Jan 2025 16:49:31 +1000 Subject: [PATCH] ImGuiOverlays: Double buffer input OSD and add vibration --- src/core/imgui_overlays.cpp | 163 +++++++++++++++++++++++++----------- src/core/imgui_overlays.h | 1 + src/core/system.cpp | 4 + 3 files changed, 121 insertions(+), 47 deletions(-) diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index a2ecb2988..341fdb4c6 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -56,6 +56,31 @@ namespace ImGuiManager { namespace { +struct InputOverlayState +{ + static constexpr u32 MAX_BINDS = 32; + + struct PadState + { + ControllerType ctype; + u8 port; + u8 slot; + bool multitap; + u32 icon_color; + float vibration_state[InputManager::MAX_MOTORS_PER_PAD]; + float bind_state[MAX_BINDS]; + }; + + std::array pads; + u32 num_active_pads = 0; +}; + +struct InputOverlayStateUpdateBuffer +{ + u32 num_active_pads = 0; + InputOverlayState::PadState pads[0]; +}; + #ifndef __ANDROID__ struct DebugWindowInfo @@ -79,6 +104,7 @@ static void DrawMediaCaptureOverlay(float& position_y, float scale, float margin static void DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing); static void DrawEnhancementsOverlay(const GPUBackend* gpu); static void DrawInputsOverlay(); +static void UpdateInputOverlay(void* buffer); #ifndef __ANDROID__ @@ -96,6 +122,9 @@ static constexpr const std::array s_debug_wi static std::array s_debug_window_state = {}; #endif + +static InputOverlayState s_input_overlay_state = {}; + } // namespace ImGuiManager static std::tuple GetMinMax(std::span values) @@ -648,6 +677,70 @@ void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float ma position_y += history_size.y + spacing; } +void ImGuiManager::UpdateInputOverlay() +{ + u32 num_active_pads = 0; + for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++) + { + if (g_settings.controller_types[port] != ControllerType::None) + num_active_pads++; + } + + const u32 buffer_size = + sizeof(InputOverlayStateUpdateBuffer) + (static_cast(sizeof(InputOverlayState)) * num_active_pads); + const auto& [cmd, buffer] = GPUThread::BeginASyncBufferCall(&ImGuiManager::UpdateInputOverlay, buffer_size); + + InputOverlayStateUpdateBuffer* const ubuffer = static_cast(buffer); + ubuffer->num_active_pads = num_active_pads; + + size_t out_index = 0; + for (const u32 pad : Controller::PortDisplayOrder) + { + const Controller* controller = System::GetController(pad); + if (!controller) + continue; + + const ControllerType ctype = controller->GetType(); + const auto& [port, slot] = Controller::ConvertPadToPortAndSlot(pad); + const bool multitap = g_settings.IsMultitapPortEnabled(port); + InputOverlayState::PadState& pstate = ubuffer->pads[out_index++]; + pstate.port = Truncate8(port); + pstate.slot = Truncate8(slot); + pstate.multitap = multitap; + pstate.ctype = ctype; + pstate.icon_color = controller->GetInputOverlayIconColor(); + + const Controller::ControllerInfo& cinfo = Controller::GetControllerInfo(ctype); + for (const Controller::ControllerBindingInfo& bi : cinfo.bindings) + { + const u32 bidx = bi.bind_index; + + // this will leave some uninitalized, but who cares, it won't be read on the other side + if (bi.type >= InputBindingInfo::Type::Button && bi.type <= InputBindingInfo::Type::HalfAxis) + { + DebugAssert(bidx < InputOverlayState::MAX_BINDS); + pstate.bind_state[bidx] = controller->GetBindState(bidx); + } + else if (bi.type == InputBindingInfo::Type::Motor) + { + DebugAssert(bidx < InputManager::MAX_MOTORS_PER_PAD); + pstate.vibration_state[bidx] = controller->GetVibrationMotorState(bidx); + } + } + } + + GPUThread::PushCommand(cmd); +} + +void ImGuiManager::UpdateInputOverlay(void* buffer) +{ + InputOverlayStateUpdateBuffer* const RESTRICT ubuffer = static_cast(buffer); + DebugAssert(ubuffer->num_active_pads < NUM_CONTROLLER_AND_CARD_PORTS); + s_input_overlay_state.num_active_pads = ubuffer->num_active_pads; + for (u32 i = 0; i < ubuffer->num_active_pads; i++) + s_input_overlay_state.pads[i] = ubuffer->pads[i]; +} + void ImGuiManager::DrawInputsOverlay() { const float scale = ImGuiManager::GetGlobalScale(); @@ -662,16 +755,10 @@ void ImGuiManager::DrawInputsOverlay() const ImVec2& display_size = ImGui::GetIO().DisplaySize; ImDrawList* dl = ImGui::GetBackgroundDrawList(); - u32 num_ports = 0; - for (u32 port = 0; port < NUM_CONTROLLER_AND_CARD_PORTS; port++) - { - if (g_settings.controller_types[port] != ControllerType::None) - num_ports++; - } - float current_x = ImFloor(margin); float current_y = - ImFloor(display_size.y - margin - ((static_cast(num_ports) * (font->FontSize + spacing)) - spacing)); + ImFloor(display_size.y - margin - + ((static_cast(s_input_overlay_state.num_active_pads) * (font->FontSize + spacing)) - spacing)); // This is a bit of a pain. Some of the glyphs slightly overhang/overshoot past the baseline, resulting // in the glyphs getting clipped if we use the text height/margin as a clip point. Instead, just clamp it @@ -680,24 +767,19 @@ void ImGuiManager::DrawInputsOverlay() SmallString text; - for (const u32 pad : Controller::PortDisplayOrder) + for (u32 i = 0; i < s_input_overlay_state.num_active_pads; i++) { - const Controller* controller = System::GetController(pad); - if (!controller) - continue; - - const Controller::ControllerInfo& cinfo = Controller::GetControllerInfo(controller->GetType()); - const auto& [port, slot] = Controller::ConvertPadToPortAndSlot(pad); - const char* port_label = Controller::GetPortDisplayName(port, slot, g_settings.IsMultitapPortEnabled(port)); + const InputOverlayState::PadState& pstate = s_input_overlay_state.pads[i]; + const Controller::ControllerInfo& cinfo = Controller::GetControllerInfo(pstate.ctype); + const char* port_label = Controller::GetPortDisplayName(pstate.port, pstate.slot, pstate.multitap); float text_start_x = current_x; if (cinfo.icon_name) { const ImVec2 icon_size = font->CalcTextSizeA(font->FontSize, FLT_MAX, 0.0f, cinfo.icon_name); - const u32 icon_color = controller->GetInputOverlayIconColor(); dl->AddText(font, font->FontSize, ImVec2(current_x + shadow_offset, current_y + shadow_offset), shadow_color, cinfo.icon_name, nullptr, 0.0f, &clip_rect); - dl->AddText(font, font->FontSize, ImVec2(current_x, current_y), icon_color, cinfo.icon_name, nullptr, 0.0f, + dl->AddText(font, font->FontSize, ImVec2(current_x, current_y), pstate.icon_color, cinfo.icon_name, nullptr, 0.0f, &clip_rect); text_start_x += icon_size.x; text.format(" {}", port_label); @@ -709,36 +791,23 @@ void ImGuiManager::DrawInputsOverlay() for (const Controller::ControllerBindingInfo& bi : cinfo.bindings) { - switch (bi.type) + if (bi.type >= InputBindingInfo::Type::Button && bi.type <= InputBindingInfo::Type::HalfAxis) { - case InputBindingInfo::Type::Axis: - case InputBindingInfo::Type::HalfAxis: - { - // axes are always shown - const float value = controller->GetBindState(bi.bind_index); - if (value >= (254.0f / 255.0f)) - text.append_format(" {}", bi.icon_name ? bi.icon_name : bi.name); - else if (value > (1.0f / 255.0f)) - text.append_format(" {}: {:.2f}", bi.icon_name ? bi.icon_name : bi.name, value); - } - break; - - case InputBindingInfo::Type::Button: - { - // buttons only shown when active - const float value = controller->GetBindState(bi.bind_index); - if (value >= 0.5f) - text.append_format(" {}", bi.icon_name ? bi.icon_name : bi.name); - } - break; - - case InputBindingInfo::Type::Motor: - case InputBindingInfo::Type::Macro: - case InputBindingInfo::Type::Unknown: - case InputBindingInfo::Type::Pointer: - case InputBindingInfo::Type::RelativePointer: - default: - break; + // axes are always shown when not near resting, buttons only shown when active + const float value = pstate.bind_state[bi.bind_index]; + const float threshold = (bi.type == InputBindingInfo::Type::Button) ? 0.5f : (254.0f / 255.0f); + if (value >= threshold) + text.append_format(" {}", bi.icon_name ? bi.icon_name : bi.name); + else if (value > (1.0f / 255.0f)) + text.append_format(" {}: {:.2f}", bi.icon_name ? bi.icon_name : bi.name, value); + } + else if (bi.type == InputBindingInfo::Type::Motor) + { + const float value = pstate.vibration_state[bi.bind_index]; + if (value >= 1.0f) + text.append_format(" {}", bi.icon_name ? bi.icon_name : bi.name); + else if (value > 0.0f) + text.append_format(" {}: {:.0f}%", bi.icon_name ? bi.icon_name : bi.name, std::ceil(value * 100.0f)); } } diff --git a/src/core/imgui_overlays.h b/src/core/imgui_overlays.h index 32d0dce69..ca947b555 100644 --- a/src/core/imgui_overlays.h +++ b/src/core/imgui_overlays.h @@ -15,6 +15,7 @@ namespace ImGuiManager { static constexpr const char* LOGO_IMAGE_NAME = "images/duck.png"; +void UpdateInputOverlay(); void RenderTextOverlays(const GPUBackend* gpu); bool AreAnyDebugWindowsEnabled(const SettingsInterface& si); void RenderDebugWindows(); diff --git a/src/core/system.cpp b/src/core/system.cpp index 2cf2ece1a..8abe303f5 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2163,6 +2163,10 @@ void System::FrameDone() InputManager::PollSources(); CheckForAndExitExecution(); } + + // Update input OSD if we're running + if (g_settings.display_show_inputs) + ImGuiManager::UpdateInputOverlay(); } bool System::GetFramePresentationParameters(GPUBackendFramePresentationParameters* frame)