improve Linux runtime behavior

This commit is contained in:
mchughalex 2026-06-04 18:36:22 +01:00
parent 32a6743554
commit 2c21bd516b
11 changed files with 349 additions and 54 deletions

View file

@ -112,6 +112,8 @@ bool IsFinalized();
void SaveConfig(const std::filesystem::path& config_path); void SaveConfig(const std::filesystem::path& config_path);
void SaveConfigValues(const std::filesystem::path& config_path, void SaveConfigValues(const std::filesystem::path& config_path,
std::initializer_list<std::string_view> names); std::initializer_list<std::string_view> names);
void SaveConfigValues(const std::filesystem::path& config_path,
const std::vector<std::string_view>& names);
//============================================================================= //=============================================================================
// Flag Registry // Flag Registry

View file

@ -57,6 +57,7 @@ class MnkInputDriver final : public InputDriver,
void UpdateMouseCapture(); void UpdateMouseCapture();
void SetKeyState(uint16_t vk, bool down); void SetKeyState(uint16_t vk, bool down);
void EnqueueKeystroke(uint16_t vk_pad, bool down); void EnqueueKeystroke(uint16_t vk_pad, bool down);
void ClearStateLocked();
rex::ui::Window* attached_window_ = nullptr; rex::ui::Window* attached_window_ = nullptr;

View file

@ -44,6 +44,8 @@ class ImGuiDialog {
ImGuiDrawer* imgui_drawer() const { return imgui_drawer_; } ImGuiDrawer* imgui_drawer() const { return imgui_drawer_; }
ImGuiIO& GetIO(); ImGuiIO& GetIO();
void SetDrawActive(bool active);
bool IsDrawActive() const { return is_draw_active_; }
// Closes the dialog and returns to any waiters. // Closes the dialog and returns to any waiters.
void Close(); void Close();
@ -54,6 +56,7 @@ class ImGuiDialog {
private: private:
ImGuiDrawer* imgui_drawer_ = nullptr; ImGuiDrawer* imgui_drawer_ = nullptr;
bool is_draw_active_ = true;
bool has_close_pending_ = false; bool has_close_pending_ = false;
std::vector<rex::thread::Fence*> waiting_fences_; std::vector<rex::thread::Fence*> waiting_fences_;
}; };

View file

@ -38,12 +38,14 @@ class GTKWindow : public Window {
GtkWidget* window() const { return window_; } GtkWidget* window() const { return window_; }
protected: protected:
uint32_t GetLatestDpiImpl() const override;
bool OpenImpl() override; bool OpenImpl() override;
void RequestCloseImpl() override; void RequestCloseImpl() override;
void ApplyNewFullscreen() override; void ApplyNewFullscreen() override;
void ApplyNewTitle() override; void ApplyNewTitle() override;
void ApplyNewMainMenu(MenuItem* old_main_menu) override; void ApplyNewMainMenu(MenuItem* old_main_menu) override;
void ApplyNewCursorVisibility(CursorVisibility old_cursor_visibility) override;
// Mouse capture seems to happen implicitly compared to Windows. // Mouse capture seems to happen implicitly compared to Windows.
void FocusImpl() override; void FocusImpl() override;
@ -51,6 +53,7 @@ class GTKWindow : public Window {
void RequestPaintImpl() override; void RequestPaintImpl() override;
private: private:
void UpdateDpi(WindowDestructionReceiver* destruction_receiver = nullptr);
void HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver); void HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver);
// For updating multiple factors that may influence the window size at once, // For updating multiple factors that may influence the window size at once,
// without handling the configure event multiple times (that may not only // without handling the configure event multiple times (that may not only
@ -64,6 +67,9 @@ class GTKWindow : public Window {
bool HandleKeyboard(GdkEventKey* event, WindowDestructionReceiver& destruction_receiver); bool HandleKeyboard(GdkEventKey* event, WindowDestructionReceiver& destruction_receiver);
gboolean WindowEventHandler(GdkEvent* event); gboolean WindowEventHandler(GdkEvent* event);
static gboolean WindowEventHandlerThunk(GtkWidget* widget, GdkEvent* event, gpointer user_data); static gboolean WindowEventHandlerThunk(GtkWidget* widget, GdkEvent* event, gpointer user_data);
void ApplyCursor();
void SetCursorAutoHideTimer();
static gboolean AutoHideCursorTimerCallback(gpointer user_data);
// Handling events related specifically to the drawing (client) area. // Handling events related specifically to the drawing (client) area.
gboolean DrawingAreaEventHandler(GdkEvent* event); gboolean DrawingAreaEventHandler(GdkEvent* event);
@ -75,6 +81,10 @@ class GTKWindow : public Window {
GtkWidget* window_ = nullptr; GtkWidget* window_ = nullptr;
GtkWidget* box_ = nullptr; GtkWidget* box_ = nullptr;
GtkWidget* drawing_area_ = nullptr; GtkWidget* drawing_area_ = nullptr;
GdkCursor* blank_cursor_ = nullptr;
guint cursor_auto_hide_timer_ = 0;
bool cursor_currently_auto_hidden_ = false;
uint32_t dpi_ = 96;
uint32_t batched_size_update_depth_ = 0; uint32_t batched_size_update_depth_ = 0;
bool batched_size_update_contained_configure_ = false; bool batched_size_update_contained_configure_ = false;

View file

@ -606,25 +606,16 @@ bool IsFinalized() {
void SaveConfig(const std::filesystem::path& config_path) { void SaveConfig(const std::filesystem::path& config_path) {
try { try {
toml::table config; toml::table config;
if (std::filesystem::exists(config_path)) {
config = toml::parse_file(config_path.string());
}
{ {
std::lock_guard lock(GetRegistryMutex()); std::lock_guard lock(GetRegistryMutex());
bool modified = false;
for (const auto& entry : GetRegistryStorage()) { for (const auto& entry : GetRegistryStorage()) {
if (entry.getter() == entry.default_value) { if (entry.getter() == entry.default_value) {
continue; continue;
} }
const std::string value = entry.getter(); const std::string value = entry.getter();
modified |= WriteConfigValue(config, entry, value); WriteConfigValue(config, entry, value);
}
if (!modified) {
REXLOG_DEBUG("SaveConfig: no modified flags to save");
return;
} }
} }
@ -638,6 +629,11 @@ void SaveConfig(const std::filesystem::path& config_path) {
void SaveConfigValues(const std::filesystem::path& config_path, void SaveConfigValues(const std::filesystem::path& config_path,
std::initializer_list<std::string_view> names) { std::initializer_list<std::string_view> names) {
SaveConfigValues(config_path, std::vector<std::string_view>(names));
}
void SaveConfigValues(const std::filesystem::path& config_path,
const std::vector<std::string_view>& names) {
try { try {
toml::table config; toml::table config;
if (std::filesystem::exists(config_path)) { if (std::filesystem::exists(config_path)) {

View file

@ -22,8 +22,20 @@
#include <rex/input/sdl/sdl_input_driver.h> #include <rex/input/sdl/sdl_input_driver.h>
#include <rex/input/xinput/xinput_input_driver.h> #include <rex/input/xinput/xinput_input_driver.h>
#include <rex/logging.h> #include <rex/logging.h>
#include <rex/platform.h>
REXCVAR_DEFINE_STRING(input_backend, "xinput", "Input", "Input backend: sdl, xinput") namespace {
constexpr const char* kDefaultInputBackend =
#if REX_PLATFORM_WIN32
"xinput";
#else
"sdl";
#endif
} // namespace
REXCVAR_DEFINE_STRING(input_backend, kDefaultInputBackend, "Input", "Input backend: sdl, xinput")
.allowed({"sdl", "xinput"}); .allowed({"sdl", "xinput"});
REXCVAR_DEFINE_BOOL(guide_button, false, "Input", "Enable guide button pass-through"); REXCVAR_DEFINE_BOOL(guide_button, false, "Input", "Enable guide button pass-through");

View file

@ -106,6 +106,12 @@ bool MnkInputDriver::IsEnabled() const {
return REXCVAR_GET(mnk_mode); return REXCVAR_GET(mnk_mode);
} }
void MnkInputDriver::ClearStateLocked() {
std::memset(key_down_, 0, sizeof(key_down_));
mouse_dx_ = 0;
mouse_dy_ = 0;
}
static bool IsBindPressed(const bool (&key_down)[256], const std::string& cvar_val) { static bool IsBindPressed(const bool (&key_down)[256], const std::string& cvar_val) {
VirtualKey vk = rex::ui::ParseVirtualKey(cvar_val); VirtualKey vk = rex::ui::ParseVirtualKey(cvar_val);
if (vk == VirtualKey::kNone) if (vk == VirtualKey::kNone)
@ -138,13 +144,20 @@ X_RESULT MnkInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
} }
X_RESULT MnkInputDriver::GetState(uint32_t user_index, X_INPUT_STATE* out_state) { X_RESULT MnkInputDriver::GetState(uint32_t user_index, X_INPUT_STATE* out_state) {
if (!IsEnabled() || user_index != UserIndex()) { if (!IsEnabled()) {
std::lock_guard lock(state_mutex_);
ClearStateLocked();
return X_ERROR_DEVICE_NOT_CONNECTED;
}
if (user_index != UserIndex()) {
return X_ERROR_DEVICE_NOT_CONNECTED; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
UpdateMouseCapture(); UpdateMouseCapture();
if (!is_active() || !has_focus_) { if (!is_active() || !has_focus_) {
std::lock_guard lock(state_mutex_);
ClearStateLocked();
if (out_state) { if (out_state) {
std::memset(out_state, 0, sizeof(*out_state)); std::memset(out_state, 0, sizeof(*out_state));
out_state->packet_number = packet_number_; out_state->packet_number = packet_number_;
@ -235,10 +248,25 @@ X_RESULT MnkInputDriver::SetState(uint32_t user_index, X_INPUT_VIBRATION* vibrat
X_RESULT MnkInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags, X_RESULT MnkInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
X_INPUT_KEYSTROKE* out_keystroke) { X_INPUT_KEYSTROKE* out_keystroke) {
if (!IsEnabled() || user_index != UserIndex()) { if (!IsEnabled()) {
std::lock_guard lock(state_mutex_);
ClearStateLocked();
while (!keystroke_queue_.empty()) {
keystroke_queue_.pop();
}
return X_ERROR_DEVICE_NOT_CONNECTED;
}
if (user_index != UserIndex()) {
return X_ERROR_DEVICE_NOT_CONNECTED; return X_ERROR_DEVICE_NOT_CONNECTED;
} }
std::lock_guard lock(state_mutex_); std::lock_guard lock(state_mutex_);
if (!is_active() || !has_focus_) {
ClearStateLocked();
while (!keystroke_queue_.empty()) {
keystroke_queue_.pop();
}
return X_ERROR_EMPTY;
}
if (keystroke_queue_.empty()) { if (keystroke_queue_.empty()) {
return X_ERROR_EMPTY; return X_ERROR_EMPTY;
} }
@ -308,8 +336,9 @@ void MnkInputDriver::SetKeyState(uint16_t vk, bool down) {
} }
void MnkInputDriver::OnKeyDown(rex::ui::KeyEvent& e) { void MnkInputDriver::OnKeyDown(rex::ui::KeyEvent& e) {
if (!IsEnabled() || !has_focus_) if (!IsEnabled() || !has_focus_ || !is_active()) {
return; return;
}
std::lock_guard lock(state_mutex_); std::lock_guard lock(state_mutex_);
uint16_t vk = static_cast<uint16_t>(e.virtual_key()); uint16_t vk = static_cast<uint16_t>(e.virtual_key());
SetKeyState(vk, true); SetKeyState(vk, true);
@ -324,7 +353,7 @@ void MnkInputDriver::OnKeyUp(rex::ui::KeyEvent& e) {
} }
void MnkInputDriver::OnMouseDown(rex::ui::MouseEvent& e) { void MnkInputDriver::OnMouseDown(rex::ui::MouseEvent& e) {
if (!IsEnabled() || !has_focus_) if (!IsEnabled() || !has_focus_ || !is_active())
return; return;
std::lock_guard lock(state_mutex_); std::lock_guard lock(state_mutex_);
switch (e.button()) { switch (e.button()) {
@ -362,7 +391,7 @@ void MnkInputDriver::OnMouseUp(rex::ui::MouseEvent& e) {
} }
void MnkInputDriver::OnMouseMove(rex::ui::MouseEvent& e) { void MnkInputDriver::OnMouseMove(rex::ui::MouseEvent& e) {
if (!IsEnabled() || !has_focus_) if (!IsEnabled() || !has_focus_ || !is_active())
return; return;
std::lock_guard lock(state_mutex_); std::lock_guard lock(state_mutex_);
int32_t x = e.x(); int32_t x = e.x();
@ -376,9 +405,7 @@ void MnkInputDriver::OnMouseMove(rex::ui::MouseEvent& e) {
void MnkInputDriver::OnLostFocus(rex::ui::UISetupEvent&) { void MnkInputDriver::OnLostFocus(rex::ui::UISetupEvent&) {
std::lock_guard lock(state_mutex_); std::lock_guard lock(state_mutex_);
has_focus_ = false; has_focus_ = false;
std::memset(key_down_, 0, sizeof(key_down_)); ClearStateLocked();
mouse_dx_ = 0;
mouse_dy_ = 0;
if (mouse_captured_ && attached_window_) { if (mouse_captured_ && attached_window_) {
mouse_captured_ = false; mouse_captured_ = false;
attached_window_->SetCursorVisibility(rex::ui::Window::CursorVisibility::kVisible); attached_window_->SetCursorVisibility(rex::ui::Window::CursorVisibility::kVisible);

View file

@ -23,7 +23,7 @@ ImGuiDialog::ImGuiDialog(ImGuiDrawer* imgui_drawer) : imgui_drawer_(imgui_drawer
} }
ImGuiDialog::~ImGuiDialog() { ImGuiDialog::~ImGuiDialog() {
imgui_drawer_->RemoveDialog(this); SetDrawActive(false);
for (auto fence : waiting_fences_) { for (auto fence : waiting_fences_) {
fence->Signal(); fence->Signal();
} }
@ -41,6 +41,18 @@ ImGuiIO& ImGuiDialog::GetIO() {
return imgui_drawer()->GetIO(); return imgui_drawer()->GetIO();
} }
void ImGuiDialog::SetDrawActive(bool active) {
if (is_draw_active_ == active) {
return;
}
is_draw_active_ = active;
if (active) {
imgui_drawer_->AddDialog(this);
} else {
imgui_drawer_->RemoveDialog(this);
}
}
void ImGuiDialog::Draw() { void ImGuiDialog::Draw() {
// Draw UI. // Draw UI.
OnDraw(GetIO()); OnDraw(GetIO());

View file

@ -23,18 +23,44 @@ constexpr std::array<double, 6> kFrameCapRates = {60.0, 90.0, 120.0, 144.0, 165.
constexpr std::array<const char*, 7> kFrameCapLabels = {"Unlimited", "60 FPS", "90 FPS", constexpr std::array<const char*, 7> kFrameCapLabels = {"Unlimited", "60 FPS", "90 FPS",
"120 FPS", "144 FPS", "165 FPS", "120 FPS", "144 FPS", "165 FPS",
"240 FPS"}; "240 FPS"};
constexpr std::array<std::string_view, 10> kSimpleSettingsCvars = { constexpr std::array<std::string_view, 7> kCoreSimpleSettingsCvars = {
"resolution_scale", "resolution_scale",
"draw_resolution_scale_x", "draw_resolution_scale_x",
"draw_resolution_scale_y", "draw_resolution_scale_y",
"d3d12_present_frame_limiter",
"d3d12_present_frame_limiter_fps",
"fullscreen", "fullscreen",
"vsync", "vsync",
"d3d12_allow_variable_refresh_rate_and_tearing",
"mnk_mode", "mnk_mode",
"mnk_capture_mouse"}; "mnk_capture_mouse"};
bool HasCvar(std::string_view name) {
return rex::cvar::GetFlagInfo(name) != nullptr;
}
std::vector<std::string_view> GetSimpleSettingsCvars() {
std::vector<std::string_view> cvars;
cvars.reserve(14);
cvars.insert(cvars.end(), kCoreSimpleSettingsCvars.begin(), kCoreSimpleSettingsCvars.end());
if (HasCvar("d3d12_present_frame_limiter")) {
cvars.push_back("d3d12_present_frame_limiter");
}
if (HasCvar("d3d12_present_frame_limiter_fps")) {
cvars.push_back("d3d12_present_frame_limiter_fps");
}
if (HasCvar("d3d12_allow_variable_refresh_rate_and_tearing")) {
cvars.push_back("d3d12_allow_variable_refresh_rate_and_tearing");
}
if (HasCvar("vulkan_allow_present_mode_immediate")) {
cvars.push_back("vulkan_allow_present_mode_immediate");
}
if (HasCvar("vulkan_allow_present_mode_mailbox")) {
cvars.push_back("vulkan_allow_present_mode_mailbox");
}
if (HasCvar("vulkan_allow_present_mode_fifo_relaxed")) {
cvars.push_back("vulkan_allow_present_mode_fifo_relaxed");
}
return cvars;
}
int ResolutionIndexFromCvar() { int ResolutionIndexFromCvar() {
int32_t current = rex::cvar::Query<int32_t>("resolution_scale"); int32_t current = rex::cvar::Query<int32_t>("resolution_scale");
int best = 0; int best = 0;
@ -50,7 +76,14 @@ int ResolutionIndexFromCvar() {
return best; return best;
} }
bool HasFrameCapCvars() {
return HasCvar("d3d12_present_frame_limiter") && HasCvar("d3d12_present_frame_limiter_fps");
}
int FrameCapIndexFromCvar() { int FrameCapIndexFromCvar() {
if (!HasFrameCapCvars()) {
return 0;
}
if (!rex::cvar::Query<bool>("d3d12_present_frame_limiter")) { if (!rex::cvar::Query<bool>("d3d12_present_frame_limiter")) {
return 0; return 0;
} }
@ -77,6 +110,31 @@ void SetBoolCvar(std::string_view name, bool value) {
rex::cvar::SetFlagByName(name, value ? "true" : "false"); rex::cvar::SetFlagByName(name, value ? "true" : "false");
} }
bool TearingFromCvar() {
if (HasCvar("d3d12_allow_variable_refresh_rate_and_tearing")) {
return rex::cvar::Query<bool>("d3d12_allow_variable_refresh_rate_and_tearing");
}
if (HasCvar("vulkan_allow_present_mode_immediate")) {
return rex::cvar::Query<bool>("vulkan_allow_present_mode_immediate");
}
return false;
}
void SetTearingCvars(bool value) {
if (HasCvar("d3d12_allow_variable_refresh_rate_and_tearing")) {
SetBoolCvar("d3d12_allow_variable_refresh_rate_and_tearing", value);
}
if (HasCvar("vulkan_allow_present_mode_immediate")) {
SetBoolCvar("vulkan_allow_present_mode_immediate", value);
}
if (HasCvar("vulkan_allow_present_mode_mailbox")) {
SetBoolCvar("vulkan_allow_present_mode_mailbox", value);
}
if (HasCvar("vulkan_allow_present_mode_fifo_relaxed")) {
SetBoolCvar("vulkan_allow_present_mode_fifo_relaxed", value);
}
}
void SectionTitle(const char* label) { void SectionTitle(const char* label) {
ImGui::PushFont(nullptr, 20.0f); ImGui::PushFont(nullptr, 20.0f);
ImGui::TextUnformatted(label); ImGui::TextUnformatted(label);
@ -103,17 +161,7 @@ bool PrimaryButton(const char* label) {
} // namespace } // namespace
void SaveSimpleSettingsConfig(const std::filesystem::path& config_path) { void SaveSimpleSettingsConfig(const std::filesystem::path& config_path) {
rex::cvar::SaveConfigValues(config_path, rex::cvar::SaveConfigValues(config_path, GetSimpleSettingsCvars());
{"resolution_scale",
"draw_resolution_scale_x",
"draw_resolution_scale_y",
"d3d12_present_frame_limiter",
"d3d12_present_frame_limiter_fps",
"fullscreen",
"vsync",
"d3d12_allow_variable_refresh_rate_and_tearing",
"mnk_mode",
"mnk_capture_mouse"});
} }
void EnsureSimpleSettingsConfig(const std::filesystem::path& config_path) { void EnsureSimpleSettingsConfig(const std::filesystem::path& config_path) {
@ -121,7 +169,7 @@ void EnsureSimpleSettingsConfig(const std::filesystem::path& config_path) {
if (!should_save) { if (!should_save) {
try { try {
auto config = toml::parse_file(config_path.string()); auto config = toml::parse_file(config_path.string());
for (std::string_view name : kSimpleSettingsCvars) { for (std::string_view name : GetSimpleSettingsCvars()) {
if (!config.contains(name)) { if (!config.contains(name)) {
should_save = true; should_save = true;
break; break;
@ -152,12 +200,14 @@ SimpleSettingsDialog::SimpleSettingsDialog(ImGuiDrawer* drawer, std::filesystem:
restart_game_(std::move(restart_game)) { restart_game_(std::move(restart_game)) {
ReloadProfiles(); ReloadProfiles();
LoadSettingsFromCvars(); LoadSettingsFromCvars();
SetDrawActive(false);
} }
SimpleSettingsDialog::~SimpleSettingsDialog() = default; SimpleSettingsDialog::~SimpleSettingsDialog() = default;
void SimpleSettingsDialog::Show() { void SimpleSettingsDialog::Show() {
visible_ = true; visible_ = true;
SetDrawActive(true);
ReloadProfiles(); ReloadProfiles();
LoadSettingsFromCvars(); LoadSettingsFromCvars();
} }
@ -167,7 +217,7 @@ void SimpleSettingsDialog::LoadSettingsFromCvars() {
frame_cap_index_ = FrameCapIndexFromCvar(); frame_cap_index_ = FrameCapIndexFromCvar();
fullscreen_ = rex::cvar::Query<bool>("fullscreen"); fullscreen_ = rex::cvar::Query<bool>("fullscreen");
vsync_ = rex::cvar::Query<bool>("vsync"); vsync_ = rex::cvar::Query<bool>("vsync");
tearing_ = rex::cvar::Query<bool>("d3d12_allow_variable_refresh_rate_and_tearing"); tearing_ = TearingFromCvar();
mnk_mode_ = rex::cvar::Query<bool>("mnk_mode"); mnk_mode_ = rex::cvar::Query<bool>("mnk_mode");
mnk_capture_mouse_ = rex::cvar::Query<bool>("mnk_capture_mouse"); mnk_capture_mouse_ = rex::cvar::Query<bool>("mnk_capture_mouse");
} }
@ -177,7 +227,7 @@ bool SimpleSettingsDialog::HasSettingsChanges() const {
frame_cap_index_ != FrameCapIndexFromCvar() || frame_cap_index_ != FrameCapIndexFromCvar() ||
fullscreen_ != rex::cvar::Query<bool>("fullscreen") || fullscreen_ != rex::cvar::Query<bool>("fullscreen") ||
vsync_ != rex::cvar::Query<bool>("vsync") || vsync_ != rex::cvar::Query<bool>("vsync") ||
tearing_ != rex::cvar::Query<bool>("d3d12_allow_variable_refresh_rate_and_tearing") || tearing_ != TearingFromCvar() ||
mnk_mode_ != rex::cvar::Query<bool>("mnk_mode") || mnk_mode_ != rex::cvar::Query<bool>("mnk_mode") ||
mnk_capture_mouse_ != rex::cvar::Query<bool>("mnk_capture_mouse"); mnk_capture_mouse_ != rex::cvar::Query<bool>("mnk_capture_mouse");
} }
@ -195,6 +245,7 @@ void SimpleSettingsDialog::Hide() {
return; return;
} }
visible_ = false; visible_ = false;
SetDrawActive(false);
if (close_settings_) { if (close_settings_) {
close_settings_(); close_settings_();
} }
@ -222,14 +273,16 @@ void SimpleSettingsDialog::SaveVideo() {
rex::cvar::SetFlagByName("resolution_scale", scale); rex::cvar::SetFlagByName("resolution_scale", scale);
rex::cvar::SetFlagByName("draw_resolution_scale_x", scale); rex::cvar::SetFlagByName("draw_resolution_scale_x", scale);
rex::cvar::SetFlagByName("draw_resolution_scale_y", scale); rex::cvar::SetFlagByName("draw_resolution_scale_y", scale);
SetBoolCvar("d3d12_present_frame_limiter", frame_cap_index_ != 0); if (HasFrameCapCvars()) {
if (frame_cap_index_ != 0) { SetBoolCvar("d3d12_present_frame_limiter", frame_cap_index_ != 0);
}
if (HasFrameCapCvars() && frame_cap_index_ != 0) {
rex::cvar::SetFlagByName("d3d12_present_frame_limiter_fps", rex::cvar::SetFlagByName("d3d12_present_frame_limiter_fps",
std::to_string(kFrameCapRates[frame_cap_index_ - 1])); std::to_string(kFrameCapRates[frame_cap_index_ - 1]));
} }
SetBoolCvar("fullscreen", fullscreen_); SetBoolCvar("fullscreen", fullscreen_);
SetBoolCvar("vsync", vsync_); SetBoolCvar("vsync", vsync_);
SetBoolCvar("d3d12_allow_variable_refresh_rate_and_tearing", tearing_); SetTearingCvars(tearing_);
SetBoolCvar("mnk_mode", mnk_mode_); SetBoolCvar("mnk_mode", mnk_mode_);
SetBoolCvar("mnk_capture_mouse", mnk_capture_mouse_); SetBoolCvar("mnk_capture_mouse", mnk_capture_mouse_);
SaveSimpleSettingsConfig(config_path_); SaveSimpleSettingsConfig(config_path_);
@ -341,10 +394,12 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) {
static_cast<int>(kResolutionLabels.size())); static_cast<int>(kResolutionLabels.size()));
EndFieldRow(); EndFieldRow();
BeginFieldRow("Framerate Cap"); if (HasFrameCapCvars()) {
ImGui::Combo("##frame_cap", &frame_cap_index_, kFrameCapLabels.data(), BeginFieldRow("Framerate Cap");
static_cast<int>(kFrameCapLabels.size())); ImGui::Combo("##frame_cap", &frame_cap_index_, kFrameCapLabels.data(),
EndFieldRow(); static_cast<int>(kFrameCapLabels.size()));
EndFieldRow();
}
ImGui::Separator(); ImGui::Separator();
ImGui::Spacing(); ImGui::Spacing();

View file

@ -18,6 +18,7 @@
#include <rex/filesystem.h> #include <rex/filesystem.h>
#include <rex/logging/sink.h> #include <rex/logging/sink.h>
#include <rex/logging.h> #include <rex/logging.h>
#include <rex/platform.h>
#include <rex/ui/overlay/console_overlay.h> #include <rex/ui/overlay/console_overlay.h>
#include <rex/ui/overlay/debug_overlay.h> #include <rex/ui/overlay/debug_overlay.h>
#include <rex/ui/overlay/settings_overlay.h> #include <rex/ui/overlay/settings_overlay.h>
@ -50,7 +51,18 @@
namespace rex { namespace rex {
REXCVAR_DEFINE_BOOL(advanced_settings_overlay_enabled, false, "UI/Advanced", namespace {
constexpr bool kBlockShaderStorageStartup =
#if REX_PLATFORM_WIN32
true;
#else
false;
#endif
} // namespace
REXCVAR_DEFINE_BOOL(advanced_settings_overlay_enabled, true, "UI/Advanced",
"Enable the developer cvar browser on F4"); "Enable the developer cvar browser on F4");
// --- ReXApp --- // --- ReXApp ---
@ -262,7 +274,8 @@ bool ReXApp::ConstructRuntime(const PathConfig& paths) {
input_sys->SetActiveCallback([this]() { input_sys->SetActiveCallback([this]() {
if (!debug_overlay_ && !console_overlay_ && !settings_overlay_) if (!debug_overlay_ && !console_overlay_ && !settings_overlay_)
return true; return true;
return !imgui_drawer_->GetIO().WantCaptureMouse; const auto& io = imgui_drawer_->GetIO();
return !io.WantCaptureMouse && !io.WantCaptureKeyboard;
}); });
} }
} }
@ -416,7 +429,8 @@ void ReXApp::LaunchModule() {
uint32_t title_id = runtime_->kernel_state()->title_id(); uint32_t title_id = runtime_->kernel_state()->title_id();
if (title_id != 0) { if (title_id != 0) {
REXLOG_INFO("Initializing shader storage for title {:08X}...", title_id); REXLOG_INFO("Initializing shader storage for title {:08X}...", title_id);
graphics_system->InitializeShaderStorage(runtime_->cache_root(), title_id, true); graphics_system->InitializeShaderStorage(runtime_->cache_root(), title_id,
kBlockShaderStorageStartup);
} }
} }

View file

@ -27,8 +27,50 @@
#include <gdk/gdkx.h> #include <gdk/gdkx.h>
#include <xcb/xcb.h> #include <xcb/xcb.h>
REXCVAR_DEFINE_DOUBLE(gtk_ui_scale, 1.25, "UI/GTK",
"GTK effective UI scale multiplier")
.range(0.75, 2.0)
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
namespace { namespace {
constexpr uint32_t kDefaultDpi = 96;
uint32_t CalculateMonitorDpi(GdkMonitor* monitor) {
if (!monitor) {
return kDefaultDpi;
}
// GDK monitor geometry plus physical millimeter size gives EDID physical
// density, not the desktop UI scale. Match Windows DPI behavior by exposing
// effective UI DPI instead.
const int scale_factor = std::max(1, gdk_monitor_get_scale_factor(monitor));
const auto dpi =
uint32_t(double(kDefaultDpi * uint32_t(scale_factor)) * REXCVAR_GET(gtk_ui_scale) + 0.5);
return std::clamp(dpi, uint32_t(48), uint32_t(384));
}
uint32_t QueryWidgetDpi(GtkWidget* widget) {
GdkDisplay* display = widget ? gtk_widget_get_display(widget) : gdk_display_get_default();
if (!display) {
return kDefaultDpi;
}
GdkMonitor* monitor = nullptr;
if (widget) {
GdkWindow* gdk_window = gtk_widget_get_window(widget);
if (gdk_window) {
monitor = gdk_display_get_monitor_at_window(display, gdk_window);
}
}
if (!monitor) {
monitor = gdk_display_get_primary_monitor(display);
}
if (!monitor && gdk_display_get_n_monitors(display) > 0) {
monitor = gdk_display_get_monitor(display, 0);
}
return CalculateMonitorDpi(monitor);
}
uint32_t ResolveWindowWidth(uint32_t requested_width) { uint32_t ResolveWindowWidth(uint32_t requested_width) {
if (REXCVAR_GET(window_width) > 0) { if (REXCVAR_GET(window_width) > 0) {
return uint32_t(REXCVAR_GET(window_width)); return uint32_t(REXCVAR_GET(window_width));
@ -313,10 +355,20 @@ std::unique_ptr<Window> Window::Create(WindowedAppContext& app_context,
GTKWindow::GTKWindow(WindowedAppContext& app_context, const std::string_view title, GTKWindow::GTKWindow(WindowedAppContext& app_context, const std::string_view title,
uint32_t desired_logical_width, uint32_t desired_logical_height) uint32_t desired_logical_width, uint32_t desired_logical_height)
: Window(app_context, title, desired_logical_width, desired_logical_height) {} : Window(app_context, title, desired_logical_width, desired_logical_height) {
dpi_ = QueryWidgetDpi(nullptr);
}
GTKWindow::~GTKWindow() { GTKWindow::~GTKWindow() {
EnterDestructor(); EnterDestructor();
if (cursor_auto_hide_timer_) {
g_source_remove(cursor_auto_hide_timer_);
cursor_auto_hide_timer_ = 0;
}
if (blank_cursor_) {
g_object_unref(blank_cursor_);
blank_cursor_ = nullptr;
}
if (window_) { if (window_) {
// Set window_ to null to ignore events from now on since this ui::GTKWindow // Set window_ to null to ignore events from now on since this ui::GTKWindow
// is entering an indeterminate state. // is entering an indeterminate state.
@ -329,8 +381,26 @@ GTKWindow::~GTKWindow() {
} }
} }
uint32_t GTKWindow::GetLatestDpiImpl() const {
return dpi_;
}
void GTKWindow::UpdateDpi(WindowDestructionReceiver* destruction_receiver) {
const uint32_t new_dpi = QueryWidgetDpi(drawing_area_ ? drawing_area_ : window_);
if (!new_dpi || new_dpi == dpi_) {
return;
}
dpi_ = new_dpi;
if (destruction_receiver) {
UISetupEvent e(this);
OnDpiChanged(e, *destruction_receiver);
}
}
bool GTKWindow::OpenImpl() { bool GTKWindow::OpenImpl() {
window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
blank_cursor_ = gdk_cursor_new_for_display(gtk_widget_get_display(window_), GDK_BLANK_CURSOR);
gtk_window_set_title(GTK_WINDOW(window_), GetTitle().c_str()); gtk_window_set_title(GTK_WINDOW(window_), GetTitle().c_str());
@ -345,6 +415,10 @@ bool GTKWindow::OpenImpl() {
if (main_menu_widget) { if (main_menu_widget) {
gtk_box_pack_start(GTK_BOX(box_), main_menu_widget, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(box_), main_menu_widget, FALSE, FALSE, 0);
} }
const bool initial_fullscreen = IsFullscreen();
if (initial_fullscreen && main_menu_widget) {
gtk_container_remove(GTK_CONTAINER(box_), main_menu_widget);
}
// Create the drawing area for creating the surface for, which will be the // Create the drawing area for creating the surface for, which will be the
// client area of the window occupying all the window space not taken by the // client area of the window occupying all the window space not taken by the
@ -371,8 +445,22 @@ bool GTKWindow::OpenImpl() {
g_signal_connect(G_OBJECT(drawing_area_), "draw", G_CALLBACK(DrawHandler), g_signal_connect(G_OBJECT(drawing_area_), "draw", G_CALLBACK(DrawHandler),
reinterpret_cast<gpointer>(this)); reinterpret_cast<gpointer>(this));
if (initial_fullscreen) {
gtk_window_fullscreen(GTK_WINDOW(window_));
g_idle_add(
[](gpointer user_data) -> gboolean {
auto* gtk_window = reinterpret_cast<GTKWindow*>(user_data);
if (gtk_window->window_ && gtk_window->IsFullscreen()) {
gtk_window_fullscreen(GTK_WINDOW(gtk_window->window_));
}
return G_SOURCE_REMOVE;
},
this);
}
// Finally show all the widgets in the window, including the main menu. // Finally show all the widgets in the window, including the main menu.
gtk_widget_show_all(window_); gtk_widget_show_all(window_);
UpdateDpi();
// Remove the size request after finishing the initial layout because it makes // Remove the size request after finishing the initial layout because it makes
// it impossible to make the window smaller. // it impossible to make the window smaller.
@ -393,12 +481,10 @@ bool GTKWindow::OpenImpl() {
// After setting up the initial layout for non-fullscreen, enter fullscreen if // After setting up the initial layout for non-fullscreen, enter fullscreen if
// requested. // requested.
if (IsFullscreen()) { if (initial_fullscreen) {
if (main_menu_widget) {
gtk_container_remove(GTK_CONTAINER(box_), main_menu_widget);
}
gtk_window_fullscreen(GTK_WINDOW(window_)); gtk_window_fullscreen(GTK_WINDOW(window_));
} }
ApplyNewCursorVisibility(CursorVisibility::kVisible);
// Make sure the initial state after opening is reported to the common Window // Make sure the initial state after opening is reported to the common Window
// class no matter how GTK sends the events. // class no matter how GTK sends the events.
@ -556,6 +642,20 @@ void GTKWindow::ApplyNewMainMenu(MenuItem* old_main_menu) {
} }
} }
void GTKWindow::ApplyNewCursorVisibility(CursorVisibility old_cursor_visibility) {
CursorVisibility new_cursor_visibility = GetCursorVisibility();
if (cursor_auto_hide_timer_) {
g_source_remove(cursor_auto_hide_timer_);
cursor_auto_hide_timer_ = 0;
}
cursor_currently_auto_hidden_ = new_cursor_visibility != CursorVisibility::kVisible;
ApplyCursor();
if (new_cursor_visibility == CursorVisibility::kAutoHidden &&
old_cursor_visibility == CursorVisibility::kAutoHidden) {
SetCursorAutoHideTimer();
}
}
void GTKWindow::FocusImpl() { void GTKWindow::FocusImpl() {
gtk_window_activate_focus(GTK_WINDOW(window_)); gtk_window_activate_focus(GTK_WINDOW(window_));
} }
@ -594,10 +694,17 @@ void GTKWindow::HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver
return; return;
} }
// TODO(Triang3l): Report the desired client area size. UpdateDpi(&destruction_receiver);
if (destruction_receiver.IsWindowDestroyedOrClosed()) {
return;
}
GtkAllocation drawing_area_allocation; GtkAllocation drawing_area_allocation;
gtk_widget_get_allocation(drawing_area_, &drawing_area_allocation); gtk_widget_get_allocation(drawing_area_, &drawing_area_allocation);
if (!IsFullscreen()) {
OnDesiredLogicalSizeUpdate(SizeToLogical(uint32_t(drawing_area_allocation.width)),
SizeToLogical(uint32_t(drawing_area_allocation.height)));
}
OnActualSizeUpdate(uint32_t(drawing_area_allocation.width), OnActualSizeUpdate(uint32_t(drawing_area_allocation.width),
uint32_t(drawing_area_allocation.height), destruction_receiver); uint32_t(drawing_area_allocation.height), destruction_receiver);
if (destruction_receiver.IsWindowDestroyedOrClosed()) { if (destruction_receiver.IsWindowDestroyedOrClosed()) {
@ -757,6 +864,14 @@ gboolean GTKWindow::WindowEventHandler(GdkEvent* event) {
// at some point in closing anyway. // at some point in closing anyway.
GtkWidget* window = window_; GtkWidget* window = window_;
window_ = nullptr; window_ = nullptr;
if (cursor_auto_hide_timer_) {
g_source_remove(cursor_auto_hide_timer_);
cursor_auto_hide_timer_ = 0;
}
if (blank_cursor_) {
g_object_unref(blank_cursor_);
blank_cursor_ = nullptr;
}
// Destroying the top-level window also destroys its children. // Destroying the top-level window also destroys its children.
drawing_area_ = nullptr; drawing_area_ = nullptr;
box_ = nullptr; box_ = nullptr;
@ -773,6 +888,7 @@ gboolean GTKWindow::WindowEventHandler(GdkEvent* event) {
if (destruction_receiver.IsWindowDestroyedOrClosed()) { if (destruction_receiver.IsWindowDestroyedOrClosed()) {
break; break;
} }
ApplyCursor();
} break; } break;
case GDK_KEY_PRESS: case GDK_KEY_PRESS:
@ -805,12 +921,59 @@ gboolean GTKWindow::WindowEventHandlerThunk(GtkWidget* widget, GdkEvent* event,
return window->WindowEventHandler(event); return window->WindowEventHandler(event);
} }
void GTKWindow::ApplyCursor() {
if (!drawing_area_) {
return;
}
GdkWindow* drawing_area_window = gtk_widget_get_window(drawing_area_);
if (!drawing_area_window) {
return;
}
GdkCursor* cursor = nullptr;
switch (GetCursorVisibility()) {
case CursorVisibility::kVisible:
cursor = nullptr;
break;
case CursorVisibility::kAutoHidden:
cursor = cursor_currently_auto_hidden_ ? blank_cursor_ : nullptr;
break;
case CursorVisibility::kHidden:
cursor = blank_cursor_;
break;
}
gdk_window_set_cursor(drawing_area_window, cursor);
}
void GTKWindow::SetCursorAutoHideTimer() {
if (cursor_auto_hide_timer_) {
g_source_remove(cursor_auto_hide_timer_);
cursor_auto_hide_timer_ = 0;
}
cursor_auto_hide_timer_ =
g_timeout_add(kDefaultCursorAutoHideMilliseconds, AutoHideCursorTimerCallback, this);
}
gboolean GTKWindow::AutoHideCursorTimerCallback(gpointer user_data) {
auto* gtk_window = reinterpret_cast<GTKWindow*>(user_data);
gtk_window->cursor_auto_hide_timer_ = 0;
if (gtk_window->GetCursorVisibility() == CursorVisibility::kAutoHidden) {
gtk_window->cursor_currently_auto_hidden_ = true;
gtk_window->ApplyCursor();
}
return G_SOURCE_REMOVE;
}
gboolean GTKWindow::DrawingAreaEventHandler(GdkEvent* event) { gboolean GTKWindow::DrawingAreaEventHandler(GdkEvent* event) {
switch (event->type) { switch (event->type) {
case GDK_MOTION_NOTIFY: case GDK_MOTION_NOTIFY:
case GDK_BUTTON_PRESS: case GDK_BUTTON_PRESS:
case GDK_BUTTON_RELEASE: case GDK_BUTTON_RELEASE:
case GDK_SCROLL: { case GDK_SCROLL: {
if (GetCursorVisibility() == CursorVisibility::kAutoHidden) {
cursor_currently_auto_hidden_ = false;
ApplyCursor();
SetCursorAutoHideTimer();
}
WindowDestructionReceiver destruction_receiver(this); WindowDestructionReceiver destruction_receiver(this);
HandleMouse(event, destruction_receiver); HandleMouse(event, destruction_receiver);
if (destruction_receiver.IsWindowDestroyedOrClosed()) { if (destruction_receiver.IsWindowDestroyedOrClosed()) {