Host: Add 'Auxiliary Render Windows'

i.e. debugging windows
This commit is contained in:
Stenzek 2024-10-13 22:09:38 +10:00
parent e5c5ba22d7
commit b2a4010661
No known key found for this signature in database
8 changed files with 626 additions and 28 deletions

View File

@ -465,3 +465,167 @@ bool DisplayContainer::event(QEvent* event)
return res;
}
AuxiliaryDisplayWidget::AuxiliaryDisplayWidget(QWidget* parent, u32 width, u32 height, const QString& title,
void* userdata)
: QWidget(parent), m_userdata(userdata)
{
// We want a native window for both D3D and OpenGL.
setAutoFillBackground(false);
setAttribute(Qt::WA_NativeWindow, true);
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_PaintOnScreen, true);
setAttribute(Qt::WA_KeyCompression, false);
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
setWindowTitle(title);
resize(width, height);
}
AuxiliaryDisplayWidget::~AuxiliaryDisplayWidget() = default;
QPaintEngine* AuxiliaryDisplayWidget::paintEngine() const
{
return nullptr;
}
bool AuxiliaryDisplayWidget::event(QEvent* event)
{
const QEvent::Type type = event->type();
switch (type)
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
g_emu_thread->queueAuxiliaryRenderWindowInputEvent(
m_userdata,
(type == QEvent::KeyPress) ? Host::AuxiliaryRenderWindowEvent::KeyPressed :
Host::AuxiliaryRenderWindowEvent::KeyReleased,
Host::AuxiliaryRenderWindowEventParam{.uint_param =
static_cast<u32>(static_cast<const QKeyEvent*>(event)->key())});
return true;
}
case QEvent::MouseMove:
{
const qreal dpr = QtUtils::GetDevicePixelRatioForWidget(this);
const QPoint mouse_pos = static_cast<QMouseEvent*>(event)->pos();
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * dpr);
const float scaled_y = static_cast<float>(static_cast<qreal>(mouse_pos.y()) * dpr);
g_emu_thread->queueAuxiliaryRenderWindowInputEvent(
m_userdata, Host::AuxiliaryRenderWindowEvent::MouseMoved,
Host::AuxiliaryRenderWindowEventParam{.float_param = scaled_x},
Host::AuxiliaryRenderWindowEventParam{.float_param = scaled_y});
return true;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonRelease:
{
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
g_emu_thread->queueAuxiliaryRenderWindowInputEvent(
m_userdata,
(type == QEvent::MouseButtonRelease) ? Host::AuxiliaryRenderWindowEvent::MouseReleased :
Host::AuxiliaryRenderWindowEvent::MousePressed,
Host::AuxiliaryRenderWindowEventParam{.uint_param = button_index},
Host::AuxiliaryRenderWindowEventParam{.uint_param = BoolToUInt32(type == QEvent::MouseButtonRelease)});
return true;
}
case QEvent::Wheel:
{
const QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
const QPoint delta = wheel_event->angleDelta();
if (delta.x() != 0 || delta.y())
{
g_emu_thread->queueAuxiliaryRenderWindowInputEvent(
m_userdata, Host::AuxiliaryRenderWindowEvent::MouseWheel,
Host::AuxiliaryRenderWindowEventParam{.float_param = static_cast<float>(delta.x())},
Host::AuxiliaryRenderWindowEventParam{.float_param = static_cast<float>(delta.y())});
}
return true;
}
case QEvent::Close:
{
if (m_destroying)
return QWidget::event(event);
g_emu_thread->queueAuxiliaryRenderWindowInputEvent(m_userdata, Host::AuxiliaryRenderWindowEvent::CloseRequest);
event->ignore();
return true;
}
case QEvent::Paint:
case QEvent::Resize:
{
QWidget::event(event);
const float dpr = QtUtils::GetDevicePixelRatioForWidget(this);
const u32 scaled_width =
static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * dpr)), 1));
const u32 scaled_height =
static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * dpr)), 1));
// avoid spamming resize events for paint events (sent on move on windows)
if (m_last_window_width != scaled_width || m_last_window_height != scaled_height || m_last_window_scale != dpr)
{
m_last_window_width = scaled_width;
m_last_window_height = scaled_height;
m_last_window_scale = dpr;
g_emu_thread->queueAuxiliaryRenderWindowInputEvent(
m_userdata, Host::AuxiliaryRenderWindowEvent::Resized,
Host::AuxiliaryRenderWindowEventParam{.uint_param = scaled_width},
Host::AuxiliaryRenderWindowEventParam{.uint_param = scaled_height},
Host::AuxiliaryRenderWindowEventParam{.float_param = dpr});
}
return true;
}
default:
return QWidget::event(event);
}
}
AuxiliaryDisplayWidget* AuxiliaryDisplayWidget::create(s32 pos_x, s32 pos_y, u32 width, u32 height,
const QString& title, const QString& icon_name, void* userdata)
{
// TODO: Wayland shit
QWidget* parent = nullptr;
AuxiliaryDisplayWidget* widget = new AuxiliaryDisplayWidget(parent, width, height, title, userdata);
if (!icon_name.isEmpty())
{
if (const QIcon icon(icon_name); !icon.isNull())
widget->setWindowIcon(icon);
else
widget->setWindowIcon(QtHost::GetAppIcon());
}
else
{
widget->setWindowIcon(QtHost::GetAppIcon());
}
if (pos_x != std::numeric_limits<s32>::min() && pos_y != std::numeric_limits<s32>::min())
widget->move(pos_x, pos_y);
widget->show();
return widget;
}
void AuxiliaryDisplayWidget::destroy()
{
m_destroying = true;
QWidget* container = static_cast<QWidget*>(parent());
if (!container)
container = this;
container->close();
container->deleteLater();
}

View File

@ -89,3 +89,28 @@ protected:
private:
DisplayWidget* m_display_widget = nullptr;
};
class AuxiliaryDisplayWidget final : public QWidget
{
Q_OBJECT
public:
explicit AuxiliaryDisplayWidget(QWidget* parent, u32 width, u32 height, const QString& title, void* userdata);
~AuxiliaryDisplayWidget();
QPaintEngine* paintEngine() const override;
static AuxiliaryDisplayWidget* create(s32 pos_x, s32 pos_y, u32 width, u32 height, const QString& title,
const QString& icon_name, void* userdata);
void destroy();
protected:
bool event(QEvent* event) override;
private:
void* m_userdata = nullptr;
u32 m_last_window_width = 0;
u32 m_last_window_height = 0;
float m_last_window_scale = 1.0f;
bool m_destroying = false;
};

View File

@ -2044,6 +2044,10 @@ void MainWindow::connectSignals()
connect(g_emu_thread, &EmuThread::achievementsChallengeModeChanged, this,
&MainWindow::onAchievementsChallengeModeChanged);
connect(g_emu_thread, &EmuThread::onCoverDownloaderOpenRequested, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(g_emu_thread, &EmuThread::onCreateAuxiliaryRenderWindow, this, &MainWindow::onCreateAuxiliaryRenderWindow,
Qt::BlockingQueuedConnection);
connect(g_emu_thread, &EmuThread::onDestroyAuxiliaryRenderWindow, this, &MainWindow::onDestroyAuxiliaryRenderWindow,
Qt::BlockingQueuedConnection);
// These need to be queued connections to stop crashing due to menus opening/closing and switching focus.
connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress);
@ -2642,6 +2646,36 @@ void MainWindow::onAchievementsChallengeModeChanged(bool enabled)
updateEmulationActions(false, System::IsValid(), enabled);
}
bool MainWindow::onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width, quint32 height, const QString& title,
const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error)
{
AuxiliaryDisplayWidget* widget = AuxiliaryDisplayWidget::create(x, y, width, height, title, icon_name, userdata);
if (!widget)
return false;
const std::optional<WindowInfo> owi = QtUtils::GetWindowInfoForWidget(widget, error);
if (!owi.has_value())
{
widget->destroy();
return false;
}
*handle = widget;
*wi = owi.value();
return true;
}
void MainWindow::onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size)
{
AuxiliaryDisplayWidget* widget = static_cast<AuxiliaryDisplayWidget*>(handle);
DebugAssert(widget);
*pos = widget->pos();
*size = widget->size();
widget->destroy();
}
void MainWindow::onToolsMemoryCardEditorTriggered()
{
openMemoryCardEditor(QString(), QString());

View File

@ -10,6 +10,7 @@
#include "core/types.h"
#include "util/imgui_manager.h"
#include "util/window_info.h"
#include <QtCore/QThread>
@ -144,6 +145,11 @@ private Q_SLOTS:
void onMediaCaptureStopped();
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
void onAchievementsChallengeModeChanged(bool enabled);
bool onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width, quint32 height, const QString& title,
const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error);
void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size);
void onApplicationStateChanged(Qt::ApplicationState state);
void onStartFileActionTriggered();

View File

@ -1700,6 +1700,58 @@ void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directo
});
}
bool Host::CreateAuxiliaryRenderWindow(s32 x, s32 y, u32 width, u32 height, std::string_view title,
std::string_view icon_name, AuxiliaryRenderWindowUserData userdata,
AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error)
{
return emit g_emu_thread->onCreateAuxiliaryRenderWindow(x, y, width, height, QtUtils::StringViewToQString(title),
QtUtils::StringViewToQString(icon_name), userdata, handle, wi,
error);
}
void Host::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32* pos_x, s32* pos_y, u32* width,
u32* height)
{
QPoint pos;
QSize size;
emit g_emu_thread->onDestroyAuxiliaryRenderWindow(handle, &pos, &size);
if (pos_x)
*pos_x = pos.x();
if (pos_y)
*pos_y = pos.y();
if (width)
*width = size.width();
if (height)
*height = size.height();
// eat all pending events, to make sure we're not going to write input events back to a dead pointer
g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
}
void EmuThread::queueAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowEvent event,
Host::AuxiliaryRenderWindowEventParam param1,
Host::AuxiliaryRenderWindowEventParam param2,
Host::AuxiliaryRenderWindowEventParam param3)
{
DebugAssert(isOnUIThread());
QMetaObject::invokeMethod(this, "processAuxiliaryRenderWindowInputEvent", Qt::QueuedConnection,
Q_ARG(void*, userdata), Q_ARG(quint32, static_cast<quint32>(event)),
Q_ARG(quint32, param1.uint_param), Q_ARG(quint32, param2.uint_param),
Q_ARG(quint32, param3.uint_param));
}
void EmuThread::processAuxiliaryRenderWindowInputEvent(void* userdata, quint32 event, quint32 param1, quint32 param2,
quint32 param3)
{
DebugAssert(isOnThread());
ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(userdata, static_cast<Host::AuxiliaryRenderWindowEvent>(event),
Host::AuxiliaryRenderWindowEventParam{.uint_param = param1},
Host::AuxiliaryRenderWindowEventParam{.uint_param = param2},
Host::AuxiliaryRenderWindowEventParam{.uint_param = param3});
}
void EmuThread::doBackgroundControllerPoll()
{
System::Internal::IdlePollUpdate();

View File

@ -11,6 +11,7 @@
#include "core/types.h"
#include "util/gpu_device.h"
#include "util/imgui_manager.h"
#include "util/input_manager.h"
#include <QtCore/QByteArray>
@ -119,6 +120,13 @@ public:
/// This version is **only** for the system thread. UI thread should use the MainWindow variant.
SystemLock pauseAndLockSystem();
/// Queues an input event for an additional render window to the emu thread.
void queueAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowEvent event,
Host::AuxiliaryRenderWindowEventParam param1 = {},
Host::AuxiliaryRenderWindowEventParam param2 = {},
Host::AuxiliaryRenderWindowEventParam param3 = {});
Q_SIGNALS:
void errorReported(const QString& title, const QString& message);
bool messageConfirmed(const QString& title, const QString& message);
@ -151,6 +159,11 @@ Q_SIGNALS:
void mediaCaptureStarted();
void mediaCaptureStopped();
bool onCreateAuxiliaryRenderWindow(qint32 x, qint32 y, quint32 width, quint32 height, const QString& title,
const QString& icon_name, Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error);
void onDestroyAuxiliaryRenderWindow(Host::AuxiliaryRenderWindowHandle handle, QPoint* pos, QSize* size);
/// Big Picture UI requests.
void onCoverDownloaderOpenRequested();
@ -210,6 +223,8 @@ private Q_SLOTS:
void onDisplayWindowTextEntered(const QString& text);
void doBackgroundControllerPoll();
void runOnEmuThread(std::function<void()> callback);
void processAuxiliaryRenderWindowInputEvent(void* userdata, quint32 event, quint32 param1, quint32 param2,
quint32 param3);
protected:
void run() override;

View File

@ -9,6 +9,9 @@
#include "imgui_glyph_ranges.inl"
#include "input_manager.h"
// TODO: Remove me when GPUDevice config is also cleaned up.
#include "core/host.h"
#include "common/assert.h"
#include "common/easing.h"
#include "common/error.h"
@ -64,7 +67,7 @@ struct OSDMessage
static_assert(std::is_same_v<WCharType, ImWchar>);
static void UpdateScale();
static void SetStyle();
static void SetStyle(ImGuiStyle& style, float scale);
static void SetKeyMap();
static bool LoadFontData();
static void ReloadFontDataIfActive();
@ -91,6 +94,7 @@ static std::string s_font_path;
static std::vector<WCharType> s_font_range;
static std::vector<WCharType> s_emoji_range;
static ImGuiContext* s_imgui_context;
static ImFont* s_standard_font;
static ImFont* s_osd_font;
static ImFont* s_fixed_font;
@ -235,9 +239,9 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
(g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetScale() : 1.0f) * global_scale, 1.0f);
s_scale_changed = false;
ImGui::CreateContext();
s_imgui_context = ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImGuiIO& io = s_imgui_context->IO;
io.IniFilename = nullptr;
io.BackendFlags |= ImGuiBackendFlags_HasGamepad | ImGuiBackendFlags_RendererHasVtxOffset;
io.BackendUsingLegacyKeyArrays = 0;
@ -259,7 +263,7 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
io.DisplaySize = ImVec2(s_window_width, s_window_height);
SetKeyMap();
SetStyle();
SetStyle(s_imgui_context->Style, s_global_scale);
if (!AddImGuiFonts(false) || !g_gpu_device->UpdateImGuiFontTexture())
{
@ -269,7 +273,7 @@ bool ImGuiManager::Initialize(float global_scale, Error* error)
}
// don't need the font data anymore, save some memory
ImGui::GetIO().Fonts->ClearTexData();
io.Fonts->ClearTexData();
NewFrame();
@ -281,8 +285,11 @@ void ImGuiManager::Shutdown()
{
DestroySoftwareCursorTextures();
if (ImGui::GetCurrentContext())
ImGui::DestroyContext();
if (s_imgui_context)
{
ImGui::DestroyContext(s_imgui_context);
s_imgui_context = nullptr;
}
s_standard_font = nullptr;
s_fixed_font = nullptr;
@ -291,6 +298,11 @@ void ImGuiManager::Shutdown()
ImGuiFullscreen::SetFonts(nullptr, nullptr);
}
ImGuiContext* ImGuiManager::GetMainContext()
{
return s_imgui_context;
}
float ImGuiManager::GetWindowWidth()
{
return s_window_width;
@ -327,11 +339,7 @@ void ImGuiManager::UpdateScale()
return;
s_global_scale = scale;
ImGui::GetStyle() = ImGuiStyle();
ImGui::GetStyle().WindowMinSize = ImVec2(1.0f, 1.0f);
SetStyle();
ImGui::GetStyle().ScaleAllSizes(scale);
SetStyle(s_imgui_context->Style, s_global_scale);
if (!AddImGuiFonts(HasFullscreenFonts()))
Panic("Failed to create ImGui font text");
@ -360,9 +368,8 @@ void ImGuiManager::NewFrame()
s_imgui_wants_mouse.store(io.WantCaptureMouse, std::memory_order_release);
}
void ImGuiManager::SetStyle()
void ImGuiManager::SetStyle(ImGuiStyle& style, float scale)
{
ImGuiStyle& style = ImGui::GetStyle();
style = ImGuiStyle();
style.WindowMinSize = ImVec2(1.0f, 1.0f);
@ -416,7 +423,7 @@ void ImGuiManager::SetStyle()
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
style.ScaleAllSizes(s_global_scale);
style.ScaleAllSizes(scale);
}
void ImGuiManager::SetKeyMap()
@ -713,7 +720,7 @@ bool ImGuiManager::AddImGuiFonts(bool fullscreen_fonts)
void ImGuiManager::ReloadFontDataIfActive()
{
if (!ImGui::GetCurrentContext())
if (!s_imgui_context)
return;
ImGui::EndFrame();
@ -1054,43 +1061,43 @@ bool ImGuiManager::WantsMouseInput()
void ImGuiManager::AddTextInput(std::string str)
{
if (!ImGui::GetCurrentContext())
if (!s_imgui_context)
return;
if (!s_imgui_wants_keyboard.load(std::memory_order_acquire))
return;
ImGui::GetIO().AddInputCharactersUTF8(str.c_str());
s_imgui_context->IO.AddInputCharactersUTF8(str.c_str());
}
void ImGuiManager::UpdateMousePosition(float x, float y)
{
if (!ImGui::GetCurrentContext())
if (!s_imgui_context)
return;
ImGui::GetIO().MousePos = ImVec2(x, y);
s_imgui_context->IO.MousePos = ImVec2(x, y);
std::atomic_thread_fence(std::memory_order_release);
}
bool ImGuiManager::ProcessPointerButtonEvent(InputBindingKey key, float value)
{
if (!ImGui::GetCurrentContext() || key.data >= std::size(ImGui::GetIO().MouseDown))
if (!s_imgui_context || key.data >= std::size(ImGui::GetIO().MouseDown))
return false;
// still update state anyway
ImGui::GetIO().AddMouseButtonEvent(key.data, value != 0.0f);
s_imgui_context->IO.AddMouseButtonEvent(key.data, value != 0.0f);
return s_imgui_wants_mouse.load(std::memory_order_acquire);
}
bool ImGuiManager::ProcessPointerAxisEvent(InputBindingKey key, float value)
{
if (!ImGui::GetCurrentContext() || key.data < static_cast<u32>(InputPointerAxis::WheelX))
if (!s_imgui_context || key.data < static_cast<u32>(InputPointerAxis::WheelX))
return false;
// still update state anyway
const bool horizontal = (key.data == static_cast<u32>(InputPointerAxis::WheelX));
ImGui::GetIO().AddMouseWheelEvent(horizontal ? value : 0.0f, horizontal ? 0.0f : value);
s_imgui_context->IO.AddMouseWheelEvent(horizontal ? value : 0.0f, horizontal ? 0.0f : value);
return s_imgui_wants_mouse.load(std::memory_order_acquire);
}
@ -1098,11 +1105,11 @@ bool ImGuiManager::ProcessPointerAxisEvent(InputBindingKey key, float value)
bool ImGuiManager::ProcessHostKeyEvent(InputBindingKey key, float value)
{
decltype(s_imgui_key_map)::iterator iter;
if (!ImGui::GetCurrentContext() || (iter = s_imgui_key_map.find(key.data)) == s_imgui_key_map.end())
if (!s_imgui_context || (iter = s_imgui_key_map.find(key.data)) == s_imgui_key_map.end())
return false;
// still update state anyway
ImGui::GetIO().AddKeyEvent(iter->second, value != 0.0);
s_imgui_context->IO.AddKeyEvent(iter->second, value != 0.0);
return s_imgui_wants_keyboard.load(std::memory_order_acquire);
}
@ -1138,13 +1145,13 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value
ImGuiKey_GamepadL2, // R2
};
if (!ImGui::GetCurrentContext())
if (!s_imgui_context)
return false;
if (static_cast<u32>(key) >= std::size(key_map) || key_map[static_cast<u32>(key)] == ImGuiKey_None)
return false;
ImGui::GetIO().AddKeyAnalogEvent(key_map[static_cast<u32>(key)], (value > 0.0f), value);
s_imgui_context->IO.AddKeyAnalogEvent(key_map[static_cast<u32>(key)], (value > 0.0f), value);
return s_imgui_wants_keyboard.load(std::memory_order_acquire);
}
@ -1275,3 +1282,213 @@ std::string ImGuiManager::StripIconCharacters(std::string_view str)
return result;
}
#ifndef __ANDROID__
bool ImGuiManager::CreateAuxiliaryRenderWindow(AuxiliaryRenderWindowState* state, std::string_view title,
std::string_view icon_name, const char* config_section,
const char* config_prefix, u32 default_width, u32 default_height,
Error* error)
{
constexpr s32 DEFAULT_POSITION = std::numeric_limits<s32>::min();
// figure out where to position it
s32 pos_x = DEFAULT_POSITION;
s32 pos_y = DEFAULT_POSITION;
u32 width = default_width;
u32 height = default_height;
if (config_prefix)
{
pos_x = Host::GetBaseIntSettingValue(config_section, TinyString::from_format("{}PositionX", config_prefix),
DEFAULT_POSITION);
pos_y = Host::GetBaseIntSettingValue(config_section, TinyString::from_format("{}PositionY", config_prefix),
DEFAULT_POSITION);
width =
Host::GetBaseUIntSettingValue(config_section, TinyString::from_format("{}Width", config_prefix), default_width);
height =
Host::GetBaseUIntSettingValue(config_section, TinyString::from_format("{}Height", config_prefix), default_height);
}
WindowInfo wi;
if (!Host::CreateAuxiliaryRenderWindow(pos_x, pos_y, width, height, title, icon_name, state, &state->window_handle,
&wi, error))
{
return false;
}
state->swap_chain = g_gpu_device->CreateSwapChain(wi, GPUVSyncMode::Disabled, false, nullptr, std::nullopt, error);
if (!state->swap_chain)
{
Host::DestroyAuxiliaryRenderWindow(state->window_handle);
state->window_handle = nullptr;
return false;
}
state->imgui_context = ImGui::CreateContext(s_imgui_context->IO.Fonts);
state->imgui_context->IO.DisplaySize =
ImVec2(static_cast<float>(state->swap_chain->GetWidth()), static_cast<float>(state->swap_chain->GetHeight()));
state->imgui_context->IO.IniFilename = nullptr;
state->imgui_context->IO.BackendFlags |= ImGuiBackendFlags_HasGamepad;
state->imgui_context->IO.BackendUsingLegacyKeyArrays = 0;
state->imgui_context->IO.BackendUsingLegacyNavInputArray = 0;
state->imgui_context->IO.KeyRepeatDelay = 0.5f;
state->imgui_context->IO.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
SetStyle(state->imgui_context->Style, state->swap_chain->GetScale());
state->imgui_context->Style.WindowBorderSize = 0.0f;
state->close_request = false;
return true;
}
void ImGuiManager::DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowState* state, const char* config_section,
const char* config_prefix)
{
constexpr s32 DEFAULT_POSITION = std::numeric_limits<s32>::min();
if (!state->window_handle)
return;
s32 old_pos_x = DEFAULT_POSITION, old_pos_y = DEFAULT_POSITION;
u32 old_width = 0, old_height = 0;
if (config_section)
{
old_pos_x = Host::GetBaseIntSettingValue(config_section, TinyString::from_format("{}PositionX", config_prefix),
DEFAULT_POSITION);
old_pos_y = Host::GetBaseIntSettingValue(config_section, TinyString::from_format("{}PositionY", config_prefix),
DEFAULT_POSITION);
old_width = Host::GetBaseUIntSettingValue(config_section, TinyString::from_format("{}Width", config_prefix), 0);
old_height = Host::GetBaseUIntSettingValue(config_section, TinyString::from_format("{}Height", config_prefix), 0);
}
ImGui::DestroyContext(state->imgui_context);
state->imgui_context = nullptr;
state->swap_chain.reset();
state->close_request = false;
// store positioning for config
s32 new_pos_x = old_pos_x, new_pos_y = old_pos_y;
u32 new_width = old_width, new_height = old_height;
Host::DestroyAuxiliaryRenderWindow(std::exchange(state->window_handle, nullptr), &new_pos_x, &new_pos_y, &new_width,
&new_height);
if (config_section)
{
// update config if the window was moved
if (old_pos_x != new_pos_x || old_pos_y != new_pos_y || old_width != new_width || old_height != new_height)
{
Host::SetBaseIntSettingValue(config_section, TinyString::from_format("{}PositionX", config_prefix), new_pos_x);
Host::SetBaseIntSettingValue(config_section, TinyString::from_format("{}PositionY", config_prefix), new_pos_y);
Host::SetBaseUIntSettingValue(config_section, TinyString::from_format("{}Width", config_prefix), new_width);
Host::SetBaseUIntSettingValue(config_section, TinyString::from_format("{}Height", config_prefix), new_height);
Host::CommitBaseSettingChanges();
}
}
}
bool ImGuiManager::RenderAuxiliaryRenderWindow(AuxiliaryRenderWindowState* state, void (*draw_callback)(float scale))
{
DebugAssert(state->window_handle);
if (state->close_request)
return false;
ImGui::SetCurrentContext(state->imgui_context);
ImGui::NewFrame();
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiCond_Always);
ImGui::SetNextWindowSize(state->imgui_context->IO.DisplaySize, ImGuiCond_Always);
if (ImGui::Begin("AuxRenderWindowMain", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse))
{
draw_callback(state->swap_chain->GetScale());
}
ImGui::End();
const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(state->swap_chain.get());
if (pres == GPUDevice::PresentResult::OK)
{
g_gpu_device->RenderImGui(state->swap_chain.get());
g_gpu_device->EndPresent(state->swap_chain.get(), false);
}
else
{
ImGui::EndFrame();
}
ImGui::SetCurrentContext(GetMainContext());
return true;
}
void ImGuiManager::ProcessAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowEvent event,
Host::AuxiliaryRenderWindowEventParam param1,
Host::AuxiliaryRenderWindowEventParam param2,
Host::AuxiliaryRenderWindowEventParam param3)
{
// we can get bogus events here after the user closes it, so check we're not being destroyed
AuxiliaryRenderWindowState* state = static_cast<AuxiliaryRenderWindowState*>(userdata);
if (!state->window_handle) [[unlikely]]
return;
ImGuiIO& io = state->imgui_context->IO;
switch (event)
{
case Host::AuxiliaryRenderWindowEvent::CloseRequest:
{
state->close_request = true;
}
break;
case Host::AuxiliaryRenderWindowEvent::Resized:
{
Error error;
if (!state->swap_chain->ResizeBuffers(param1.uint_param, param2.uint_param, param3.float_param, &error))
{
ERROR_LOG("Failed to resize aux window swap chain to {}x{}: {}", param1.uint_param, param2.uint_param,
error.GetDescription());
return;
}
state->imgui_context->IO.DisplaySize.x = static_cast<float>(param1.uint_param);
state->imgui_context->IO.DisplaySize.y = static_cast<float>(param2.uint_param);
}
break;
case Host::AuxiliaryRenderWindowEvent::KeyPressed:
case Host::AuxiliaryRenderWindowEvent::KeyReleased:
{
const auto iter = s_imgui_key_map.find(param1.uint_param);
if (iter != s_imgui_key_map.end())
io.AddKeyEvent(iter->second, (event == Host::AuxiliaryRenderWindowEvent::KeyPressed));
}
break;
case Host::AuxiliaryRenderWindowEvent::MouseMoved:
{
io.MousePos.x = param1.float_param;
io.MousePos.y = param2.float_param;
}
break;
case Host::AuxiliaryRenderWindowEvent::MousePressed:
case Host::AuxiliaryRenderWindowEvent::MouseReleased:
{
io.AddMouseButtonEvent(param1.uint_param, (event == Host::AuxiliaryRenderWindowEvent::MousePressed));
}
break;
case Host::AuxiliaryRenderWindowEvent::MouseWheel:
{
io.AddMouseWheelEvent(param1.float_param, param2.float_param);
}
break;
default:
break;
}
}
#endif // __ANDROID__

View File

@ -4,17 +4,51 @@
#pragma once
#include "common/types.h"
#include <memory>
#include <span>
#include <string>
#include <vector>
class Error;
struct WindowInfo;
class GPUSwapChain;
struct ImGuiContext;
struct ImFont;
union InputBindingKey;
enum class GenericInputBinding : u8;
namespace Host {
/// Handle representing an auxiliary render window in the host.
using AuxiliaryRenderWindowHandle = void*;
using AuxiliaryRenderWindowUserData = void*;
enum class AuxiliaryRenderWindowEvent : u8
{
CloseRequest,
Resized,
KeyPressed,
KeyReleased,
MouseMoved,
MousePressed,
MouseReleased,
MouseWheel,
MaxCount,
};
union AuxiliaryRenderWindowEventParam
{
s32 int_param;
u32 uint_param;
float float_param;
};
} // namespace Host
namespace ImGuiManager {
using WCharType = u32;
@ -42,6 +76,9 @@ bool Initialize(float global_scale, Error* error);
/// Frees all ImGui resources.
void Shutdown();
/// Returns main ImGui context.
ImGuiContext* GetMainContext();
/// Returns the size of the display window. Can be safely called from any thread.
float GetWindowWidth();
float GetWindowHeight();
@ -124,9 +161,45 @@ void RenderSoftwareCursors();
/// Strips icon characters from a string.
std::string StripIconCharacters(std::string_view str);
#ifndef __ANDROID__
/// Auxiliary imgui windows.
struct AuxiliaryRenderWindowState
{
Host::AuxiliaryRenderWindowHandle window_handle = nullptr;
std::unique_ptr<GPUSwapChain> swap_chain;
ImGuiContext* imgui_context = nullptr;
bool close_request = false;
};
/// Create a new aux render window. This creates the window itself, swap chain, and imgui context.
/// Window position and dimensions are restored from the configuration file, under the specified section/key.
bool CreateAuxiliaryRenderWindow(AuxiliaryRenderWindowState* state, std::string_view title, std::string_view icon_name,
const char* config_section, const char* config_prefix, u32 default_width,
u32 default_height, Error* error);
/// Destroys a previously-created aux render window, optionally saving its position information.
void DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowState* state, const char* config_section = nullptr,
const char* config_prefix = nullptr);
/// Renders the specified aux render window. draw_callback will be invoked if the window is not hidden.
/// Returns false if the user has closed the window.
bool RenderAuxiliaryRenderWindow(AuxiliaryRenderWindowState* state, void (*draw_callback)(float scale));
/// Processes input events from the host.
void ProcessAuxiliaryRenderWindowInputEvent(Host::AuxiliaryRenderWindowUserData userdata,
Host::AuxiliaryRenderWindowEvent event,
Host::AuxiliaryRenderWindowEventParam param1,
Host::AuxiliaryRenderWindowEventParam param2,
Host::AuxiliaryRenderWindowEventParam param3);
#endif
} // namespace ImGuiManager
namespace Host {
/// Typical durations for OSD messages.
static constexpr float OSD_CRITICAL_ERROR_DURATION = 20.0f;
static constexpr float OSD_ERROR_DURATION = 15.0f;
@ -143,4 +216,16 @@ void AddIconOSDWarning(std::string key, const char* icon, std::string message, f
void RemoveKeyedOSDMessage(std::string key);
void RemoveKeyedOSDWarning(std::string key);
void ClearOSDMessages(bool clear_warnings);
#ifndef __ANDROID__
/// Auxiliary window management.
bool CreateAuxiliaryRenderWindow(s32 x, s32 y, u32 width, u32 height, std::string_view title,
std::string_view icon_name, AuxiliaryRenderWindowUserData userdata,
AuxiliaryRenderWindowHandle* handle, WindowInfo* wi, Error* error);
void DestroyAuxiliaryRenderWindow(AuxiliaryRenderWindowHandle handle, s32* pos_x = nullptr, s32* pos_y = nullptr,
u32* width = nullptr, u32* height = nullptr);
#endif
} // namespace Host