mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-04 05:04:33 +00:00
Qt: Cache window handle for size updates
Apparently calling winId() during window resizes ends up with a bad m_surface pointer inside the QWindowsWindow... I don't feel like debugging Qt any further, so this will be sufficient. Plus it's faster.
This commit is contained in:
@@ -51,15 +51,38 @@ DisplayWidget::DisplayWidget(QWidget* parent) : QWidget(parent)
|
||||
|
||||
DisplayWidget::~DisplayWidget() = default;
|
||||
|
||||
std::optional<WindowInfo> DisplayWidget::getWindowInfo(RenderAPI render_api, Error* error)
|
||||
const std::optional<WindowInfo>& DisplayWidget::getWindowInfo(RenderAPI render_api, Error* error)
|
||||
{
|
||||
std::optional<WindowInfo> ret = QtUtils::GetWindowInfoForWidget(this, render_api, error);
|
||||
if (ret.has_value())
|
||||
if (!m_window_info.has_value())
|
||||
m_window_info = QtUtils::GetWindowInfoForWidget(this, render_api, error);
|
||||
|
||||
return m_window_info;
|
||||
}
|
||||
|
||||
void DisplayWidget::clearWindowInfo()
|
||||
{
|
||||
m_window_info.reset();
|
||||
}
|
||||
|
||||
void DisplayWidget::checkForSizeChange()
|
||||
{
|
||||
if (!m_window_info.has_value())
|
||||
return;
|
||||
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
const u16 prev_width = m_window_info->surface_width;
|
||||
const u16 prev_height = m_window_info->surface_height;
|
||||
const float prev_scale = m_window_info->surface_scale;
|
||||
QtUtils::UpdateSurfaceSize(this, &m_window_info.value());
|
||||
if (prev_width != m_window_info->surface_width || prev_height != m_window_info->surface_height ||
|
||||
prev_scale != m_window_info->surface_scale)
|
||||
{
|
||||
m_last_window_size = QSize(ret->surface_width, ret->surface_height);
|
||||
m_last_window_scale = ret->surface_scale;
|
||||
DEV_LOG("Display widget resized to {}x{} (Qt {}x{}) DPR={}", m_window_info->surface_width,
|
||||
m_window_info->surface_height, width(), height(), m_window_info->surface_scale);
|
||||
emit windowResizedEvent(m_window_info->surface_width, m_window_info->surface_height, m_window_info->surface_scale);
|
||||
}
|
||||
return ret;
|
||||
|
||||
updateCenterPos();
|
||||
}
|
||||
|
||||
void DisplayWidget::updateRelativeMode(bool enabled)
|
||||
@@ -165,22 +188,6 @@ bool DisplayWidget::isActuallyFullscreen() const
|
||||
return container ? container->isFullScreen() : isFullScreen();
|
||||
}
|
||||
|
||||
void DisplayWidget::checkForSizeChange()
|
||||
{
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
const auto& [size, dpr] = QtUtils::GetPixelSizeForWidget(this);
|
||||
if (m_last_window_size != size || m_last_window_scale != dpr)
|
||||
{
|
||||
DEV_LOG("Display widget resized to {}x{} (Qt {}x{}) DPR={}", size.width(), size.height(), width(), height(), dpr);
|
||||
|
||||
m_last_window_size = size;
|
||||
m_last_window_scale = dpr;
|
||||
emit windowResizedEvent(size.width(), size.height(), static_cast<float>(dpr));
|
||||
}
|
||||
|
||||
updateCenterPos();
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCenterPos()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -272,9 +279,11 @@ bool DisplayWidget::event(QEvent* event)
|
||||
{
|
||||
if (!m_relative_mouse_enabled)
|
||||
{
|
||||
const float surface_scale =
|
||||
m_window_info.has_value() ? m_window_info->surface_scale : static_cast<float>(devicePixelRatio());
|
||||
const QPoint mouse_pos = static_cast<QMouseEvent*>(event)->pos();
|
||||
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * m_last_window_scale);
|
||||
const float scaled_y = static_cast<float>(static_cast<qreal>(mouse_pos.y()) * m_last_window_scale);
|
||||
const float scaled_x = static_cast<float>(mouse_pos.x()) * surface_scale;
|
||||
const float scaled_y = static_cast<float>(mouse_pos.y()) * surface_scale;
|
||||
InputManager::UpdatePointerAbsolutePosition(0, scaled_x, scaled_y);
|
||||
}
|
||||
else
|
||||
@@ -411,6 +420,19 @@ bool DisplayWidget::event(QEvent* event)
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::WinIdChange:
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
if (m_window_info.has_value())
|
||||
{
|
||||
ERROR_LOG("Window ID changed while we had a valid WindowInfo. This is NOT expected, please report.");
|
||||
clearWindowInfo();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return QWidget::event(event);
|
||||
}
|
||||
@@ -490,6 +512,38 @@ QPaintEngine* AuxiliaryDisplayWidget::paintEngine() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::optional<WindowInfo>& AuxiliaryDisplayWidget::getWindowInfo(RenderAPI render_api, Error* error)
|
||||
{
|
||||
if (!m_window_info.has_value())
|
||||
m_window_info = QtUtils::GetWindowInfoForWidget(this, render_api, error);
|
||||
|
||||
return m_window_info;
|
||||
}
|
||||
|
||||
void AuxiliaryDisplayWidget::checkForSizeChange()
|
||||
{
|
||||
if (!m_window_info.has_value())
|
||||
return;
|
||||
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
const u16 prev_width = m_window_info->surface_width;
|
||||
const u16 prev_height = m_window_info->surface_height;
|
||||
const float prev_scale = m_window_info->surface_scale;
|
||||
QtUtils::UpdateSurfaceSize(this, &m_window_info.value());
|
||||
if (prev_width != m_window_info->surface_width || prev_height != m_window_info->surface_height ||
|
||||
prev_scale != m_window_info->surface_scale)
|
||||
{
|
||||
DEV_LOG("Auxiliary display widget resized to {}x{} (Qt {}x{}) DPR={}", m_window_info->surface_width,
|
||||
m_window_info->surface_height, width(), height(), m_window_info->surface_scale);
|
||||
|
||||
g_core_thread->queueAuxiliaryRenderWindowInputEvent(
|
||||
m_userdata, Host::AuxiliaryRenderWindowEvent::Resized,
|
||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = static_cast<u32>(m_window_info->surface_width)},
|
||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = static_cast<u32>(m_window_info->surface_height)},
|
||||
Host::AuxiliaryRenderWindowEventParam{.float_param = static_cast<float>(m_window_info->surface_scale)});
|
||||
}
|
||||
}
|
||||
|
||||
bool AuxiliaryDisplayWidget::event(QEvent* event)
|
||||
{
|
||||
const QEvent::Type type = event->type();
|
||||
@@ -531,8 +585,10 @@ bool AuxiliaryDisplayWidget::event(QEvent* event)
|
||||
case QEvent::MouseMove:
|
||||
{
|
||||
const QPoint mouse_pos = static_cast<QMouseEvent*>(event)->pos();
|
||||
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * m_last_window_scale);
|
||||
const float scaled_y = static_cast<float>(static_cast<qreal>(mouse_pos.y()) * m_last_window_scale);
|
||||
const float surface_scale =
|
||||
m_window_info.has_value() ? m_window_info->surface_scale : static_cast<float>(devicePixelRatio());
|
||||
const float scaled_x = static_cast<float>(mouse_pos.x()) * surface_scale;
|
||||
const float scaled_y = static_cast<float>(mouse_pos.y()) * surface_scale;
|
||||
|
||||
g_core_thread->queueAuxiliaryRenderWindowInputEvent(
|
||||
m_userdata, Host::AuxiliaryRenderWindowEvent::MouseMoved,
|
||||
@@ -588,20 +644,19 @@ bool AuxiliaryDisplayWidget::event(QEvent* event)
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
const auto& [size, dpr] = QtUtils::GetPixelSizeForWidget(this);
|
||||
if (m_last_window_size != size || m_last_window_scale != dpr)
|
||||
{
|
||||
DEV_LOG("Auxiliary widget resized to {}x{} (Qt {}x{}) DPR={}", size.width(), size.height(), width(), height(),
|
||||
dpr);
|
||||
checkForSizeChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_last_window_size = size;
|
||||
m_last_window_scale = dpr;
|
||||
g_core_thread->queueAuxiliaryRenderWindowInputEvent(
|
||||
m_userdata, Host::AuxiliaryRenderWindowEvent::Resized,
|
||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = static_cast<u32>(size.width())},
|
||||
Host::AuxiliaryRenderWindowEventParam{.uint_param = static_cast<u32>(size.height())},
|
||||
Host::AuxiliaryRenderWindowEventParam{.float_param = static_cast<float>(dpr)});
|
||||
case QEvent::WinIdChange:
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
if (m_window_info.has_value())
|
||||
{
|
||||
ERROR_LOG("Auxiliary display widget window ID changed while we had a valid WindowInfo. This is NOT expected, "
|
||||
"please report.");
|
||||
m_window_info.reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -30,7 +30,8 @@ public:
|
||||
|
||||
QPaintEngine* paintEngine() const override;
|
||||
|
||||
std::optional<WindowInfo> getWindowInfo(RenderAPI render_api, Error* error);
|
||||
const std::optional<WindowInfo>& getWindowInfo(RenderAPI render_api, Error* error);
|
||||
void clearWindowInfo();
|
||||
|
||||
void updateRelativeMode(bool enabled);
|
||||
void updateCursor(bool hidden);
|
||||
@@ -65,9 +66,7 @@ private:
|
||||
|
||||
std::vector<int> m_keys_pressed_with_modifiers;
|
||||
|
||||
QSize m_last_window_size;
|
||||
u32 m_last_window_height = 0;
|
||||
qreal m_last_window_scale = 1;
|
||||
std::optional<WindowInfo> m_window_info;
|
||||
|
||||
const char* m_window_position_key = nullptr;
|
||||
};
|
||||
@@ -96,6 +95,8 @@ public:
|
||||
|
||||
QPaintEngine* paintEngine() const override;
|
||||
|
||||
const std::optional<WindowInfo>& getWindowInfo(RenderAPI render_api, Error* error);
|
||||
|
||||
static AuxiliaryDisplayWidget* create(s32 pos_x, s32 pos_y, u32 width, u32 height, const QString& title,
|
||||
const QString& icon_name, void* userdata);
|
||||
void destroy();
|
||||
@@ -104,8 +105,9 @@ protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
void checkForSizeChange();
|
||||
|
||||
void* m_userdata = nullptr;
|
||||
QSize m_last_window_size;
|
||||
qreal m_last_window_scale = 1;
|
||||
std::optional<WindowInfo> m_window_info;
|
||||
bool m_destroying = false;
|
||||
};
|
||||
|
||||
@@ -247,6 +247,9 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(RenderAPI render_api,
|
||||
DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
|
||||
m_exclusive_fullscreen_requested = exclusive_fullscreen;
|
||||
|
||||
// in case it gets a new native handle
|
||||
m_display_widget->clearWindowInfo();
|
||||
|
||||
// ensure it's resizable when changing size, we'll fix it up later in updateWindowState()
|
||||
QtUtils::SetWindowResizeable(container, true);
|
||||
|
||||
@@ -295,7 +298,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(RenderAPI render_api,
|
||||
QGuiApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
#endif
|
||||
|
||||
const std::optional<WindowInfo> wi = m_display_widget->getWindowInfo(render_api, error);
|
||||
const std::optional<WindowInfo>& wi = m_display_widget->getWindowInfo(render_api, error);
|
||||
if (!wi.has_value())
|
||||
{
|
||||
destroyDisplayWidget();
|
||||
@@ -438,6 +441,8 @@ void MainWindow::destroyDisplayWidget()
|
||||
{
|
||||
if (m_display_widget)
|
||||
{
|
||||
m_display_widget->clearWindowInfo();
|
||||
|
||||
if (!isRenderingFullscreen() && !isRenderingToMain())
|
||||
saveDisplayWindowGeometryToConfig();
|
||||
|
||||
@@ -3267,7 +3272,7 @@ bool MainWindow::onCreateAuxiliaryRenderWindow(RenderAPI render_api, qint32 x, q
|
||||
PlatformMisc::SetWindowRoundedCornerState(reinterpret_cast<void*>(widget->winId()), false);
|
||||
#endif
|
||||
|
||||
const std::optional<WindowInfo> owi = QtUtils::GetWindowInfoForWidget(widget, render_api, error);
|
||||
const std::optional<WindowInfo>& owi = widget->getWindowInfo(render_api, error);
|
||||
if (!owi.has_value())
|
||||
{
|
||||
widget->destroy();
|
||||
|
||||
@@ -25,44 +25,6 @@
|
||||
|
||||
LOG_CHANNEL(Host);
|
||||
|
||||
std::pair<QSize, qreal> QtUtils::GetPixelSizeForWidget(const QWidget* widget)
|
||||
{
|
||||
// Why this nonsense? Qt's device independent sizes are integer, and fractional scaling is lossy.
|
||||
// We can't get back the "real" size of the window. So we have to platform natively query the actual client size.
|
||||
#if defined(_WIN32)
|
||||
if (RECT rc; GetClientRect(reinterpret_cast<HWND>(widget->winId()), &rc))
|
||||
{
|
||||
const qreal device_pixel_ratio = widget->devicePixelRatio();
|
||||
return std::make_pair(QSize(static_cast<int>(rc.right - rc.left), static_cast<int>(rc.bottom - rc.top)),
|
||||
device_pixel_ratio);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
if (Core::GetBaseBoolSettingValue("Main", "UseFractionalWindowScale", true))
|
||||
{
|
||||
if (const std::optional<double> real_device_pixel_ratio =
|
||||
CocoaTools::GetViewRealScalingFactor(reinterpret_cast<void*>(widget->winId())))
|
||||
{
|
||||
const qreal device_pixel_ratio = static_cast<qreal>(real_device_pixel_ratio.value());
|
||||
return std::make_pair(ApplyDevicePixelRatioToSize(widget->size(), device_pixel_ratio), device_pixel_ratio);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (std::optional<std::pair<int, int>> size =
|
||||
CocoaTools::GetViewSizeInPixels(reinterpret_cast<void*>(widget->winId())))
|
||||
{
|
||||
const qreal device_pixel_ratio = widget->devicePixelRatio();
|
||||
return std::make_pair(QSize(size->first, size->second), device_pixel_ratio);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// On Linux, fuck you, enjoy round trip to the X server, and on Wayland you can't query it in the first place...
|
||||
// I ain't dealing with this crap OS. Enjoy your mismatched sizes and shit experience.
|
||||
const qreal device_pixel_ratio = widget->devicePixelRatio();
|
||||
return std::make_pair(ApplyDevicePixelRatioToSize(widget->size(), device_pixel_ratio), device_pixel_ratio);
|
||||
}
|
||||
|
||||
WindowInfoType QtUtils::GetWindowInfoType()
|
||||
{
|
||||
// Windows and Apple are easy here since there's no display connection.
|
||||
@@ -91,7 +53,7 @@ WindowInfoType QtUtils::GetWindowInfoType()
|
||||
|
||||
std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error)
|
||||
{
|
||||
WindowInfo wi;
|
||||
WindowInfo wi = {};
|
||||
|
||||
// Windows and Apple are easy here since there's no display connection.
|
||||
#if defined(_WIN32)
|
||||
@@ -134,10 +96,7 @@ std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Rende
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto& [size, dpr] = GetPixelSizeForWidget(widget);
|
||||
wi.surface_width = static_cast<u16>(size.width());
|
||||
wi.surface_height = static_cast<u16>(size.height());
|
||||
wi.surface_scale = static_cast<float>(dpr);
|
||||
UpdateSurfaceSize(widget, &wi);
|
||||
|
||||
// Query refresh rate, we need it for sync.
|
||||
Error refresh_rate_error;
|
||||
@@ -160,3 +119,51 @@ std::optional<WindowInfo> QtUtils::GetWindowInfoForWidget(QWidget* widget, Rende
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
void QtUtils::UpdateSurfaceSize(QWidget* widget, WindowInfo* wi)
|
||||
{
|
||||
// Why this nonsense? Qt's device independent sizes are integer, and fractional scaling is lossy.
|
||||
// We can't get back the "real" size of the window. So we have to platform natively query the actual client size.
|
||||
#if defined(_WIN32)
|
||||
if (RECT rc; GetClientRect(static_cast<HWND>(wi->window_handle), &rc))
|
||||
{
|
||||
const qreal device_pixel_ratio = widget->devicePixelRatio();
|
||||
wi->surface_width = static_cast<u16>(rc.right - rc.left);
|
||||
wi->surface_height = static_cast<u16>(rc.bottom - rc.top);
|
||||
wi->surface_scale = static_cast<float>(device_pixel_ratio);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
if (Core::GetBaseBoolSettingValue("Main", "UseFractionalWindowScale", true))
|
||||
{
|
||||
if (const std::optional<double> real_device_pixel_ratio = CocoaTools::GetViewRealScalingFactor(wi->window_handle))
|
||||
{
|
||||
const qreal device_pixel_ratio = static_cast<qreal>(real_device_pixel_ratio.value());
|
||||
const QSize scaled_size = ApplyDevicePixelRatioToSize(widget->size(), device_pixel_ratio);
|
||||
wi->surface_width = static_cast<u16>(scaled_size.width());
|
||||
wi->surface_height = static_cast<u16>(scaled_size.height());
|
||||
wi->surface_scale = static_cast<float>(device_pixel_ratio);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (std::optional<std::pair<int, int>> size =
|
||||
CocoaTools::GetViewSizeInPixels(reinterpret_cast<void*>(widget->winId())))
|
||||
{
|
||||
const qreal device_pixel_ratio = widget->devicePixelRatio();
|
||||
wi->surface_width = static_cast<u16>(size->first);
|
||||
wi->surface_height = static_cast<u16>(size->second);
|
||||
wi->surface_scale = static_cast<float>(device_pixel_ratio);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// On Linux, fuck you, enjoy round trip to the X server, and on Wayland you can't query it in the first place...
|
||||
// I ain't dealing with this crap OS. Enjoy your mismatched sizes and shit experience.
|
||||
const qreal device_pixel_ratio = widget->devicePixelRatio();
|
||||
const QSize scaled_size = ApplyDevicePixelRatioToSize(widget->size(), device_pixel_ratio);
|
||||
wi->surface_width = static_cast<u16>(scaled_size.width());
|
||||
wi->surface_height = static_cast<u16>(scaled_size.height());
|
||||
wi->surface_scale = static_cast<float>(device_pixel_ratio);
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ class QWidget;
|
||||
|
||||
namespace QtUtils {
|
||||
|
||||
/// Returns the pixel size (real geometry) for a widget.
|
||||
/// Also returns the "real" DPR scale for the widget, ignoring any operating-system level downsampling.
|
||||
std::pair<QSize, qreal> GetPixelSizeForWidget(const QWidget* widget);
|
||||
|
||||
/// Returns the window type for the current Qt platform.
|
||||
WindowInfoType GetWindowInfoType();
|
||||
|
||||
/// Returns the common window info structure for a Qt widget.
|
||||
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget, RenderAPI render_api, Error* error = nullptr);
|
||||
|
||||
/// Calculates the pixel size (real geometry) for a widget.
|
||||
/// Also sets the "real" DPR scale for the widget, ignoring any operating-system level downsampling.
|
||||
void UpdateSurfaceSize(QWidget* widget, WindowInfo* wi);
|
||||
|
||||
} // namespace QtUtils
|
||||
|
||||
Reference in New Issue
Block a user