Qt: Rework display widget swapping

This commit is contained in:
Connor McLaughlin 2022-05-06 21:03:16 +10:00 committed by refractionpcsx2
parent 233195b020
commit 98b537575f
8 changed files with 188 additions and 83 deletions

View File

@ -17,6 +17,7 @@
#include "DisplayWidget.h"
#include "EmuThread.h"
#include "MainWindow.h"
#include "QtHost.h"
#include "pcsx2/GS/GSIntrin.h" // _BitScanForward
@ -242,7 +243,13 @@ bool DisplayWidget::event(QEvent* event)
case QEvent::Close:
{
emit windowClosedEvent();
if (!g_main_window->requestShutdown())
{
// abort the window close
event->ignore();
return true;
}
QWidget::event(event);
return true;
}
@ -317,18 +324,19 @@ DisplayWidget* DisplayContainer::removeDisplayWidget()
bool DisplayContainer::event(QEvent* event)
{
if (event->type() == QEvent::Close && !g_main_window->requestShutdown())
{
// abort the window close
event->ignore();
return true;
}
const bool res = QStackedWidget::event(event);
if (!m_display_widget)
return res;
switch (event->type())
{
case QEvent::Close:
{
emit m_display_widget->windowClosedEvent();
}
break;
case QEvent::WindowStateChange:
{
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)

View File

@ -42,7 +42,6 @@ Q_SIGNALS:
void windowFocusEvent();
void windowResizedEvent(int width, int height, float scale);
void windowRestoredEvent();
void windowClosedEvent();
void windowKeyEvent(int key_code, bool pressed);
void windowMouseMoveEvent(int x, int y);
void windowMouseButtonEvent(int button, bool pressed);

View File

@ -108,6 +108,7 @@ void EmuThread::startVM(std::shared_ptr<VMBootParameters> boot_params)
// create the display, this may take a while...
m_is_fullscreen = boot_params->fullscreen.value_or(QtHost::GetBaseBoolSettingValue("UI", "StartFullscreen", false));
m_is_rendering_to_main = QtHost::GetBaseBoolSettingValue("UI", "RenderToMainWindow", true);
m_is_surfaceless = false;
if (!VMManager::Initialize(*boot_params))
return;
@ -134,6 +135,10 @@ void EmuThread::setVMPaused(bool paused)
return;
}
// if we were surfaceless (view->game list, system->unpause), get our display widget back
if (m_is_surfaceless)
setSurfaceless(false);
VMManager::SetPaused(paused);
}
@ -346,6 +351,23 @@ void EmuThread::setFullscreen(bool fullscreen)
GetMTGS().WaitGS();
}
void EmuThread::setSurfaceless(bool surfaceless)
{
if (!isOnEmuThread())
{
QMetaObject::invokeMethod(this, "setSurfaceless", Qt::QueuedConnection, Q_ARG(bool, surfaceless));
return;
}
if (!VMManager::HasValidVM() || m_is_surfaceless == surfaceless)
return;
// This will call back to us on the MTGS thread.
m_is_surfaceless = surfaceless;
GetMTGS().UpdateDisplayWindow();
GetMTGS().WaitGS();
}
void EmuThread::applySettings()
{
if (!isOnEmuThread())
@ -545,7 +567,6 @@ void EmuThread::connectDisplaySignals(DisplayWidget* widget)
connect(widget, &DisplayWidget::windowFocusEvent, this, &EmuThread::onDisplayWindowFocused);
connect(widget, &DisplayWidget::windowResizedEvent, this, &EmuThread::onDisplayWindowResized);
// connect(widget, &DisplayWidget::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow);
connect(widget, &DisplayWidget::windowClosedEvent, []() { g_main_window->requestShutdown(); });
connect(widget, &DisplayWidget::windowKeyEvent, this, &EmuThread::onDisplayWindowKeyEvent);
connect(widget, &DisplayWidget::windowMouseMoveEvent, this, &EmuThread::onDisplayWindowMouseMoveEvent);
connect(widget, &DisplayWidget::windowMouseButtonEvent, this, &EmuThread::onDisplayWindowMouseButtonEvent);
@ -590,8 +611,8 @@ void EmuThread::updateDisplay()
display->DoneRenderContextCurrent();
// but we should get it back after this call
DisplayWidget* widget = onUpdateDisplayRequested(m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main);
if (!widget || !display->MakeRenderContextCurrent())
onUpdateDisplayRequested(m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main, m_is_surfaceless);
if (!display->MakeRenderContextCurrent())
{
pxFailRel("Failed to recreate context after updating");
return;
@ -851,9 +872,11 @@ void Host::RunOnCPUThread(std::function<void()> function, bool block /* = false
Q_ARG(const std::function<void()>&, std::move(function)));
}
ScopedVMPause::ScopedVMPause(bool was_paused)
: m_was_paused(was_paused)
ScopedVMPause::ScopedVMPause(bool was_paused, bool was_fullscreen)
: m_was_paused(was_paused), m_was_fullscreen(was_fullscreen)
{
if (was_fullscreen)
g_emu_thread->setFullscreen(false);
if (!m_was_paused)
g_emu_thread->setVMPaused(true);
}
@ -862,6 +885,8 @@ ScopedVMPause::~ScopedVMPause()
{
if (!m_was_paused)
g_emu_thread->setVMPaused(false);
if (m_was_fullscreen)
g_emu_thread->setFullscreen(true);
}
alignas(16) static SysMtgsThread s_mtgs_thread;

View File

@ -66,6 +66,7 @@ public Q_SLOTS:
void saveStateToSlot(qint32 slot);
void toggleFullscreen();
void setFullscreen(bool fullscreen);
void setSurfaceless(bool surfaceless);
void applySettings();
void reloadGameSettings();
void toggleSoftwareRendering();
@ -81,7 +82,7 @@ public Q_SLOTS:
Q_SIGNALS:
DisplayWidget* onCreateDisplayRequested(bool fullscreen, bool render_to_main);
DisplayWidget* onUpdateDisplayRequested(bool fullscreen, bool render_to_main);
DisplayWidget* onUpdateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless);
void onResizeDisplayRequested(qint32 width, qint32 height);
void onDestroyDisplayRequested();
@ -157,6 +158,7 @@ private:
bool m_verbose_status = false;
bool m_is_rendering_to_main = false;
bool m_is_fullscreen = false;
bool m_is_surfaceless = false;
float m_last_speed = 0.0f;
float m_last_game_fps = 0.0f;
@ -171,11 +173,15 @@ private:
class ScopedVMPause
{
public:
ScopedVMPause(bool was_paused);
ScopedVMPause(bool was_paused, bool was_fullscreen);
~ScopedVMPause();
__fi bool WasPaused() const { return m_was_paused; }
__fi bool WasFullscreen() const { return m_was_fullscreen; }
private:
bool m_was_paused;
bool m_was_fullscreen;
};
extern EmuThread* g_emu_thread;

View File

@ -104,11 +104,10 @@ void MainWindow::setupAdditionalUi()
m_ui.actionViewStatusBar->setChecked(status_bar_visible);
m_ui.statusBar->setVisible(status_bar_visible);
m_game_list_widget = new GameListWidget(m_ui.mainContainer);
m_game_list_widget = new GameListWidget(this);
m_game_list_widget->initialize();
m_ui.mainContainer->insertWidget(0, m_game_list_widget);
m_ui.mainContainer->setCurrentIndex(0);
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
setCentralWidget(m_game_list_widget);
m_status_progress_widget = new QProgressBar(m_ui.statusBar);
m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
@ -578,6 +577,9 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
m_ui.actionViewGameProperties->setEnabled(running);
m_game_list_widget->setDisabled(starting && !running);
if (!starting && !running)
m_ui.actionPause->setChecked(false);
}
void MainWindow::updateStatusBarWidgetVisibility()
@ -603,26 +605,28 @@ void MainWindow::updateStatusBarWidgetVisibility()
void MainWindow::updateWindowTitle()
{
QString title;
if (!m_vm_valid || m_current_game_name.isEmpty())
{
#if defined(_DEBUG)
title = QStringLiteral("PCSX2 [Debug] %1").arg(GIT_REV);
QString main_title(QStringLiteral("PCSX2 [Debug] %1").arg(GIT_REV));
QString display_title(QStringLiteral("%1 [Debug]").arg(m_current_game_name));
#else
title = QStringLiteral("PCSX2 %1").arg(GIT_REV);
QString main_title(QStringLiteral("PCSX2 %1").arg(GIT_REV));
QString display_title(m_current_game_name);
#endif
}
else
{
#if defined(_DEBUG)
title = QStringLiteral("%1 [Debug]").arg(m_current_game_name);
#else
title = m_current_game_name;
#endif
}
if (windowTitle() != title)
setWindowTitle(title);
if (!m_vm_valid || m_current_game_name.isEmpty())
display_title = main_title;
else if (isRenderingToMain())
main_title = display_title;
if (windowTitle() != main_title)
setWindowTitle(main_title);
if (m_display_widget && !isRenderingToMain())
{
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
if (container->windowTitle() != display_title)
container->setWindowTitle(display_title);
}
}
void MainWindow::setProgressBar(int current, int total)
@ -648,38 +652,64 @@ void MainWindow::clearProgressBar()
bool MainWindow::isShowingGameList() const
{
return m_ui.mainContainer->currentIndex() == 0;
return (centralWidget() == m_game_list_widget);
}
bool MainWindow::isRenderingFullscreen() const
{
HostDisplay* display = Host::GetHostDisplay();
if (!display || !m_display_widget)
return false;
return (m_display_widget->parent() != this && (m_display_widget->isFullScreen() || display->IsFullscreen()));
}
bool MainWindow::isRenderingToMain() const
{
return (m_display_widget && m_display_widget->parent() == this);
}
void MainWindow::switchToGameListView()
{
if ((m_display_widget && !m_display_widget->parent()) || m_ui.mainContainer->currentIndex() == 0)
if (centralWidget() == m_game_list_widget)
{
m_game_list_widget->setFocus();
return;
}
if (m_vm_valid)
{
m_was_focused_on_container_switch = m_vm_paused;
m_was_paused_on_surface_loss = m_vm_paused;
if (!m_vm_paused)
g_emu_thread->setVMPaused(true);
// switch to surfaceless. we have to wait until the display widget is gone before we swap over.
g_emu_thread->setSurfaceless(true);
while (m_display_widget)
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
}
m_ui.mainContainer->setCurrentIndex(0);
pxAssertMsg(!centralWidget(), "Should not have a central widget at game list switch time");
takeCentralWidget();
setCentralWidget(m_game_list_widget);
m_game_list_widget->setVisible(true);
m_game_list_widget->setFocus();
}
void MainWindow::switchToEmulationView()
{
if (!m_display_widget || !m_display_widget->parent() || m_ui.mainContainer->currentIndex() == 1)
if (!m_vm_valid || (m_display_widget && centralWidget() == m_display_widget))
return;
if (m_vm_valid)
{
m_ui.mainContainer->setCurrentIndex(1);
if (m_vm_paused && !m_was_focused_on_container_switch)
g_emu_thread->setVMPaused(false);
}
// we're no longer surfaceless! this will call back to UpdateDisplay(), which will swap the widget out.
g_emu_thread->setSurfaceless(false);
m_display_widget->setFocus();
// resume if we weren't paused at switch time
if (m_vm_paused && !m_was_paused_on_surface_loss)
g_emu_thread->setVMPaused(false);
if (m_display_widget)
m_display_widget->setFocus();
}
void MainWindow::refreshGameList(bool invalidate_cache)
@ -710,8 +740,8 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav
// only confirm on UI thread because we need to display a msgbox
if (allow_confirm && !GSDumpReplayer::IsReplayingDump() && QtHost::GetBaseBoolSettingValue("UI", "ConfirmShutdown", true))
{
ScopedVMPause pauser(m_vm_paused);
if (QMessageBox::question(g_main_window, tr("Confirm Shutdown"),
ScopedVMPause pauser(m_vm_paused, isRenderingFullscreen());
if (QMessageBox::question(m_display_widget, tr("Confirm Shutdown"),
tr("Are you sure you want to shut down the virtual machine?\n\nAll unsaved progress will be lost.")) != QMessageBox::Yes)
{
return false;
@ -882,7 +912,7 @@ void MainWindow::onStartBIOSActionTriggered()
void MainWindow::onChangeDiscFromFileActionTriggered()
{
ScopedVMPause pauser(m_vm_paused);
ScopedVMPause pauser(m_vm_paused, isRenderingFullscreen());
QString filename = QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr);
if (filename.isEmpty())
@ -1114,6 +1144,8 @@ void MainWindow::closeEvent(QCloseEvent* event)
DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
{
DevCon.WriteLn("createDisplay(%u, %u)", static_cast<u32>(fullscreen), static_cast<u32>(render_to_main));
HostDisplay* host_display = Host::GetHostDisplay();
if (!host_display)
return nullptr;
@ -1131,12 +1163,15 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
}
else
{
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr);
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? this : nullptr);
container = m_display_widget;
}
container->setWindowTitle(windowTitle());
container->setWindowIcon(windowIcon());
if (fullscreen || !render_to_main)
{
container->setWindowTitle(windowTitle());
container->setWindowIcon(windowIcon());
}
if (fullscreen)
{
@ -1152,8 +1187,9 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
}
else
{
m_ui.mainContainer->insertWidget(1, container);
switchToEmulationView();
m_game_list_widget->setVisible(false);
takeCentralWidget();
setCentralWidget(m_display_widget);
}
// we need the surface visible.. this might be able to be replaced with something else
@ -1180,26 +1216,32 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
updateWindowTitle();
m_display_widget->setFocus();
host_display->DoneRenderContextCurrent();
return m_display_widget;
}
DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main)
DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless)
{
DevCon.WriteLn("updateDisplay(%u, %u, %u)", static_cast<u32>(fullscreen), static_cast<u32>(render_to_main), static_cast<u32>(surfaceless));
HostDisplay* host_display = Host::GetHostDisplay();
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
const bool is_fullscreen = container->isFullScreen();
const bool is_rendering_to_main = (!is_fullscreen && container->parent());
const bool is_fullscreen = (container && container->isFullScreen());
const bool is_rendering_to_main = (!is_fullscreen && container && container->parent());
const std::string fullscreen_mode(QtHost::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", ""));
const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && host_display->SupportsFullscreen());
if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main)
const bool changing_surfaceless = (!m_display_widget != surfaceless);
if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless)
return m_display_widget;
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
// .. except on Wayland, where everything tends to break if you don't recreate.
const bool has_container = (m_display_container != nullptr);
const bool needs_container = DisplayContainer::IsNeeded(fullscreen, render_to_main);
if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container && !needs_container)
if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container && !needs_container && !changing_surfaceless)
{
Console.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
if (host_display->IsFullscreen())
@ -1223,6 +1265,10 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main)
destroyDisplayWidget();
// if we're going to surfaceless, we're done here
if (surfaceless)
return nullptr;
if (DisplayContainer::IsNeeded(fullscreen, render_to_main))
{
m_display_container = new DisplayContainer();
@ -1232,12 +1278,22 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main)
}
else
{
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr);
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? this : nullptr);
container = m_display_widget;
}
container->setWindowTitle(windowTitle());
container->setWindowIcon(windowIcon());
if (fullscreen || !render_to_main)
{
container->setWindowTitle(windowTitle());
container->setWindowIcon(windowIcon());
// make sure the game list widget is still visible
if (centralWidget() != m_game_list_widget && !fullscreen)
{
setCentralWidget(m_game_list_widget);
m_game_list_widget->setVisible(true);
}
}
if (fullscreen)
{
@ -1253,8 +1309,10 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main)
}
else
{
m_ui.mainContainer->insertWidget(1, container);
switchToEmulationView();
m_game_list_widget->setVisible(false);
takeCentralWidget();
setCentralWidget(m_display_widget);
m_display_widget->setFocus();
}
// we need the surface visible.. this might be able to be replaced with something else
@ -1276,6 +1334,7 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main)
if (is_exclusive_fullscreen)
setDisplayFullscreen(fullscreen_mode);
updateWindowTitle();
m_display_widget->setFocus();
QSignalBlocker blocker(m_ui.actionFullscreen);
@ -1308,11 +1367,20 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height)
void MainWindow::destroyDisplay()
{
destroyDisplayWidget();
// switch back to game list view, we're not going back to display, so we can't use switchToGameListView().
if (centralWidget() != m_game_list_widget)
{
takeCentralWidget();
setCentralWidget(m_game_list_widget);
m_game_list_widget->setVisible(true);
m_game_list_widget->setFocus();
}
}
void MainWindow::focusDisplayWidget()
{
if (m_ui.mainContainer->currentIndex() != 1)
if (!m_display_widget || centralWidget() != m_display_widget)
return;
m_display_widget->setFocus();
@ -1355,18 +1423,20 @@ void MainWindow::destroyDisplayWidget()
if (m_display_container)
m_display_container->removeDisplayWidget();
if (m_display_widget->parent())
if (m_display_widget == centralWidget())
takeCentralWidget();
if (m_display_widget)
{
m_ui.mainContainer->removeWidget(m_display_widget);
m_ui.mainContainer->setCurrentIndex(0);
m_game_list_widget->setFocus();
m_display_widget->deleteLater();
m_display_widget = nullptr;
}
delete m_display_widget;
m_display_widget = nullptr;
delete m_display_container;
m_display_container = nullptr;
if (m_display_container)
{
m_display_container->deleteLater();
m_display_container = nullptr;
}
}
void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode)

View File

@ -62,7 +62,7 @@ public Q_SLOTS:
private Q_SLOTS:
DisplayWidget* createDisplay(bool fullscreen, bool render_to_main);
DisplayWidget* updateDisplay(bool fullscreen, bool render_to_main);
DisplayWidget* updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless);
void displayResizeRequested(qint32 width, qint32 height);
void destroyDisplay();
void focusDisplayWidget();
@ -134,6 +134,8 @@ private:
void clearProgressBar();
bool isShowingGameList() const;
bool isRenderingFullscreen() const;
bool isRenderingToMain() const;
void switchToGameListView();
void switchToEmulationView();
@ -181,7 +183,7 @@ private:
bool m_vm_valid = false;
bool m_vm_paused = false;
bool m_save_states_invalidated = false;
bool m_was_focused_on_container_switch = false;
bool m_was_paused_on_surface_loss = false;
QString m_last_fps_status;
};

View File

@ -20,13 +20,6 @@
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/AppIcon.png</normaloff>:/icons/AppIcon.png</iconset>
</property>
<widget class="QStackedWidget" name="mainContainer">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page"/>
<widget class="QWidget" name="page_2"/>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>

View File

@ -117,6 +117,7 @@ void GSinitConfig()
void GSshutdown()
{
#ifndef PCSX2_CORE
if (g_gs_renderer)
{
g_gs_renderer->Destroy();
@ -129,6 +130,7 @@ void GSshutdown()
}
Host::ReleaseHostDisplay();
#endif
#ifdef _WIN32
if (SUCCEEDED(s_hr))