diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt
index 8e6deec6f8..3d987cae90 100644
--- a/Source/Core/DolphinQt/CMakeLists.txt
+++ b/Source/Core/DolphinQt/CMakeLists.txt
@@ -143,6 +143,7 @@ PRIVATE
core
Qt5::Widgets
uicommon
+ imgui
)
if(WIN32)
diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj
index bd0346edd7..694f7b9f13 100644
--- a/Source/Core/DolphinQt/DolphinQt.vcxproj
+++ b/Source/Core/DolphinQt/DolphinQt.vcxproj
@@ -463,6 +463,9 @@
{29f29a19-f141-45ad-9679-5a2923b49da3}
+
+ {4c3b2264-ea73-4a7b-9cfe-65b0fd635ebb}
+
diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp
index 4be5f26b1d..6953de0798 100644
--- a/Source/Core/DolphinQt/RenderWidget.cpp
+++ b/Source/Core/DolphinQt/RenderWidget.cpp
@@ -17,6 +17,8 @@
#include
#include
+#include "imgui.h"
+
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/State.h"
@@ -26,6 +28,7 @@
#include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h"
+#include "VideoCommon/RenderBase.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/VideoConfig.h"
@@ -49,6 +52,8 @@ RenderWidget::RenderWidget(QWidget* parent) : QWidget(parent)
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
SetFillBackground(SConfig::GetInstance().bRenderToMain && state == Core::State::Uninitialized);
+ if (state == Core::State::Running)
+ SetImGuiKeyMap();
});
// We have to use Qt::DirectConnection here because we don't want those signals to get queued
@@ -153,6 +158,8 @@ void RenderWidget::showFullScreen()
bool RenderWidget::event(QEvent* event)
{
+ PassEventToImGui(event);
+
switch (event->type())
{
case QEvent::Paint:
@@ -244,3 +251,83 @@ void RenderWidget::OnFreeLookMouseMove(QMouseEvent* event)
m_last_mouse[0] = event->x();
m_last_mouse[1] = event->y();
}
+
+void RenderWidget::PassEventToImGui(const QEvent* event)
+{
+ if (!Core::IsRunningAndStarted())
+ return;
+
+ switch (event->type())
+ {
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease:
+ {
+ // As the imgui KeysDown array is only 512 elements wide, and some Qt keys which
+ // we need to track (e.g. alt) are above this value, we mask the lower 9 bits.
+ // Even masked, the key codes are still unique, so conflicts aren't an issue.
+ // The actual text input goes through AddInputCharactersUTF8().
+ const QKeyEvent* key_event = static_cast(event);
+ const bool is_down = event->type() == QEvent::KeyPress;
+ const int key = (key_event->key() & 0x1FF);
+ auto lock = g_renderer->GetImGuiLock();
+ if (key < ArraySize(ImGui::GetIO().KeysDown))
+ ImGui::GetIO().KeysDown[key] = is_down;
+
+ if (is_down)
+ {
+ auto utf8 = key_event->text().toUtf8();
+ ImGui::GetIO().AddInputCharactersUTF8(utf8.constData());
+ }
+ }
+ break;
+
+ case QEvent::MouseMove:
+ {
+ auto lock = g_renderer->GetImGuiLock();
+ ImGui::GetIO().MousePos.x = static_cast(event)->x();
+ ImGui::GetIO().MousePos.y = static_cast(event)->y();
+ }
+ break;
+
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ {
+ auto lock = g_renderer->GetImGuiLock();
+ const u32 button_mask = static_cast(static_cast(event)->buttons());
+ for (size_t i = 0; i < ArraySize(ImGui::GetIO().MouseDown); i++)
+ ImGui::GetIO().MouseDown[i] = (button_mask & (1u << i)) != 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void RenderWidget::SetImGuiKeyMap()
+{
+ static const int key_map[][2] = {{ImGuiKey_Tab, Qt::Key_Tab},
+ {ImGuiKey_LeftArrow, Qt::Key_Left},
+ {ImGuiKey_RightArrow, Qt::Key_Right},
+ {ImGuiKey_UpArrow, Qt::Key_Up},
+ {ImGuiKey_DownArrow, Qt::Key_Down},
+ {ImGuiKey_PageUp, Qt::Key_PageUp},
+ {ImGuiKey_PageDown, Qt::Key_PageDown},
+ {ImGuiKey_Home, Qt::Key_Home},
+ {ImGuiKey_End, Qt::Key_End},
+ {ImGuiKey_Insert, Qt::Key_Insert},
+ {ImGuiKey_Delete, Qt::Key_Delete},
+ {ImGuiKey_Backspace, Qt::Key_Backspace},
+ {ImGuiKey_Space, Qt::Key_Space},
+ {ImGuiKey_Enter, Qt::Key_Enter},
+ {ImGuiKey_Escape, Qt::Key_Escape},
+ {ImGuiKey_A, Qt::Key_A},
+ {ImGuiKey_C, Qt::Key_C},
+ {ImGuiKey_V, Qt::Key_V},
+ {ImGuiKey_X, Qt::Key_X},
+ {ImGuiKey_Y, Qt::Key_Y},
+ {ImGuiKey_Z, Qt::Key_Z}};
+ auto lock = g_renderer->GetImGuiLock();
+ for (size_t i = 0; i < ArraySize(key_map); i++)
+ ImGui::GetIO().KeyMap[key_map[i][0]] = (key_map[i][1] & 0x1FF);
+}
diff --git a/Source/Core/DolphinQt/RenderWidget.h b/Source/Core/DolphinQt/RenderWidget.h
index 4d7e53f24e..a9099fd5a7 100644
--- a/Source/Core/DolphinQt/RenderWidget.h
+++ b/Source/Core/DolphinQt/RenderWidget.h
@@ -36,6 +36,8 @@ private:
void OnKeepOnTopChanged(bool top);
void SetFillBackground(bool fill);
void OnFreeLookMouseMove(QMouseEvent* event);
+ void PassEventToImGui(const QEvent* event);
+ void SetImGuiKeyMap();
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp
index 76f3d29864..dd988b3f5c 100644
--- a/Source/Core/VideoCommon/RenderBase.cpp
+++ b/Source/Core/VideoCommon/RenderBase.cpp
@@ -812,9 +812,18 @@ void Renderer::DrawImGui()
}
}
+std::unique_lock Renderer::GetImGuiLock()
+{
+ return std::unique_lock(m_imgui_mutex);
+}
+
void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
u64 ticks)
{
+ // Hold the imgui lock while we're presenting.
+ // It's only to prevent races on inputs anyway, at this point.
+ std::unique_lock imgui_lock(m_imgui_mutex);
+
const AspectMode suggested = g_ActiveConfig.suggested_aspect_mode;
if (suggested == AspectMode::Analog || suggested == AspectMode::AnalogWide)
{
diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h
index bc0145a0d1..df0de0fa48 100644
--- a/Source/Core/VideoCommon/RenderBase.h
+++ b/Source/Core/VideoCommon/RenderBase.h
@@ -188,6 +188,11 @@ public:
virtual std::unique_ptr CreateAsyncShaderCompiler();
+ // Returns a lock for the ImGui mutex, enabling data structures to be modified from outside.
+ // Use with care, only non-drawing functions should be called from outside the video thread,
+ // as the drawing is tied to a "frame".
+ std::unique_lock GetImGuiLock();
+
protected:
std::tuple CalculateTargetScale(int x, int y) const;
bool CalculateTargetSize();
@@ -243,6 +248,7 @@ protected:
std::unique_ptr m_imgui_vertex_format;
std::vector> m_imgui_textures;
std::unique_ptr m_imgui_pipeline;
+ std::mutex m_imgui_mutex;
u64 m_imgui_last_frame_time;
private: