From ea3c0b65cfa7382180e38b20d406c874957a0702 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Wed, 22 Apr 2020 21:13:51 +1000 Subject: [PATCH] Qt: Refactor render widget state transitions Recreate widget each time. Fixes fullscreen mode switches on D3D11 and hopefully Wayland. --- src/duckstation-qt/CMakeLists.txt | 10 +- ...displaywidget.cpp => d3d11hostdisplay.cpp} | 180 +++++++----------- ...3d11displaywidget.h => d3d11hostdisplay.h} | 24 +-- src/duckstation-qt/duckstation-qt.vcxproj | 14 +- .../duckstation-qt.vcxproj.filters | 10 +- src/duckstation-qt/main.cpp | 2 +- src/duckstation-qt/mainwindow.cpp | 142 +++++++------- src/duckstation-qt/mainwindow.h | 9 +- ...isplaywidget.cpp => openglhostdisplay.cpp} | 149 +++++++-------- ...ngldisplaywidget.h => openglhostdisplay.h} | 21 +- src/duckstation-qt/qtdisplaywidget.cpp | 83 +------- src/duckstation-qt/qtdisplaywidget.h | 39 +--- src/duckstation-qt/qthostdisplay.cpp | 130 +++++++++++++ src/duckstation-qt/qthostdisplay.h | 46 +++++ src/duckstation-qt/qthostinterface.cpp | 128 ++++++++----- src/duckstation-qt/qthostinterface.h | 22 ++- 16 files changed, 550 insertions(+), 459 deletions(-) rename src/duckstation-qt/{d3d11displaywidget.cpp => d3d11hostdisplay.cpp} (82%) rename src/duckstation-qt/{d3d11displaywidget.h => d3d11hostdisplay.h} (79%) rename src/duckstation-qt/{opengldisplaywidget.cpp => openglhostdisplay.cpp} (79%) rename src/duckstation-qt/{opengldisplaywidget.h => openglhostdisplay.h} (79%) create mode 100644 src/duckstation-qt/qthostdisplay.cpp create mode 100644 src/duckstation-qt/qthostdisplay.h diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index 2c1c8e842..d66346237 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -29,10 +29,12 @@ add_executable(duckstation-qt mainwindow.cpp mainwindow.h mainwindow.ui - opengldisplaywidget.cpp - opengldisplaywidget.h + openglhostdisplay.cpp + openglhostdisplay.h portsettingswidget.cpp portsettingswidget.h + qthostdisplay.cpp + qthostdisplay.h qtdisplaywidget.cpp qtdisplaywidget.h qthostinterface.cpp @@ -52,8 +54,8 @@ target_link_libraries(duckstation-qt PRIVATE frontend-common core common imgui g if(WIN32) target_sources(duckstation-qt PRIVATE - d3d11displaywidget.cpp - d3d11displaywidget.h + d3d11hostdisplay.cpp + d3d11hostdisplay.h ) target_link_libraries(duckstation PRIVATE d3d11.lib dxgi.lib) endif() diff --git a/src/duckstation-qt/d3d11displaywidget.cpp b/src/duckstation-qt/d3d11hostdisplay.cpp similarity index 82% rename from src/duckstation-qt/d3d11displaywidget.cpp rename to src/duckstation-qt/d3d11hostdisplay.cpp index 756817057..f254fad2d 100644 --- a/src/duckstation-qt/d3d11displaywidget.cpp +++ b/src/duckstation-qt/d3d11hostdisplay.cpp @@ -1,14 +1,15 @@ -#include "d3d11displaywidget.h" +#include "d3d11hostdisplay.h" #include "common/assert.h" #include "common/d3d11/shader_compiler.h" #include "common/log.h" #include "frontend-common/display_ps.hlsl.h" #include "frontend-common/display_vs.hlsl.h" +#include "qtdisplaywidget.h" #include #include #include #include -Log_SetChannel(D3D11DisplayWidget); +Log_SetChannel(D3D11HostDisplay); class D3D11DisplayWidgetTexture : public HostDisplayTexture { @@ -61,51 +62,33 @@ private: bool m_dynamic; }; -D3D11DisplayWidget::D3D11DisplayWidget(QtHostInterface* host_interface, QWidget* parent) - : QtDisplayWidget(host_interface, parent) -{ -} +D3D11HostDisplay::D3D11HostDisplay(QtHostInterface* host_interface) : QtHostDisplay(host_interface) {} -D3D11DisplayWidget::~D3D11DisplayWidget() = default; +D3D11HostDisplay::~D3D11HostDisplay() = default; -HostDisplay* D3D11DisplayWidget::getHostDisplayInterface() -{ - return this; -} - -HostDisplay::RenderAPI D3D11DisplayWidget::GetRenderAPI() const +HostDisplay::RenderAPI D3D11HostDisplay::GetRenderAPI() const { return HostDisplay::RenderAPI::D3D11; } -void* D3D11DisplayWidget::GetRenderDevice() const +void* D3D11HostDisplay::GetRenderDevice() const { return m_device.Get(); } -void* D3D11DisplayWidget::GetRenderContext() const +void* D3D11HostDisplay::GetRenderContext() const { return m_context.Get(); } -void* D3D11DisplayWidget::GetRenderWindow() const -{ - return const_cast(static_cast(this)); -} - -void D3D11DisplayWidget::ChangeRenderWindow(void* new_window) -{ - Panic("Not supported"); -} - -std::unique_ptr D3D11DisplayWidget::CreateTexture(u32 width, u32 height, const void* initial_data, - u32 initial_data_stride, bool dynamic) +std::unique_ptr D3D11HostDisplay::CreateTexture(u32 width, u32 height, const void* initial_data, + u32 initial_data_stride, bool dynamic) { return D3D11DisplayWidgetTexture::Create(m_device.Get(), width, height, initial_data, initial_data_stride, dynamic); } -void D3D11DisplayWidget::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, - const void* texture_data, u32 texture_data_stride) +void D3D11HostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, + const void* texture_data, u32 texture_data_stride) { D3D11DisplayWidgetTexture* d3d11_texture = static_cast(texture); if (!d3d11_texture->IsDynamic()) @@ -141,8 +124,8 @@ void D3D11DisplayWidget::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y } } -bool D3D11DisplayWidget::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, - void* out_data, u32 out_data_stride) +bool D3D11HostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, + u32 out_data_stride) { ID3D11ShaderResourceView* srv = const_cast(static_cast(texture_handle)); @@ -159,17 +142,17 @@ bool D3D11DisplayWidget::DownloadTexture(const void* texture_handle, u32 x, u32 static_cast(out_data)); } -void D3D11DisplayWidget::SetVSync(bool enabled) +void D3D11HostDisplay::SetVSync(bool enabled) { m_vsync = enabled; } -bool D3D11DisplayWidget::hasDeviceContext() const +bool D3D11HostDisplay::hasDeviceContext() const { return static_cast(m_device); } -bool D3D11DisplayWidget::createDeviceContext(QThread* worker_thread, bool debug_device) +bool D3D11HostDisplay::createDeviceContext(bool debug_device) { UINT create_flags = 0; if (debug_device) @@ -231,49 +214,34 @@ bool D3D11DisplayWidget::createDeviceContext(QThread* worker_thread, bool debug_ m_allow_tearing_supported = (allow_tearing_supported == TRUE); } - if (!createSwapChain()) - return false; - - if (!QtDisplayWidget::createDeviceContext(worker_thread, debug_device)) - { - m_swap_chain.Reset(); - m_context.Reset(); - m_device.Reset(); - } - return true; } -bool D3D11DisplayWidget::initializeDeviceContext(bool debug_device) +void D3D11HostDisplay::destroyDeviceContext() { - if (!createSwapChainRTV()) - return false; - - if (!QtDisplayWidget::initializeDeviceContext(debug_device)) - return false; - - return true; -} - -void D3D11DisplayWidget::destroyDeviceContext() -{ - QtDisplayWidget::destroyDeviceContext(); + QtHostDisplay::destroyDeviceContext(); m_swap_chain.Reset(); m_context.Reset(); m_device.Reset(); } -bool D3D11DisplayWidget::shouldUseFlipModelSwapChain() const +bool D3D11HostDisplay::shouldUseFlipModelSwapChain() const { // For some reason DXGI gets stuck waiting for some kernel object when the Qt window has a parent (render-to-main) on // some computers, unless the window is completely occluded. The legacy swap chain mode does not have this problem. - return parent() == nullptr; + return m_widget->parent() == nullptr; } -bool D3D11DisplayWidget::createSwapChain() +bool D3D11HostDisplay::createSurface() { m_using_flip_model_swap_chain = shouldUseFlipModelSwapChain(); + const HWND window_hwnd = reinterpret_cast(m_widget->winId()); + RECT client_rc{}; + GetClientRect(window_hwnd, &client_rc); + m_window_width = client_rc.right - client_rc.left; + m_window_height = client_rc.bottom - client_rc.top; + DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; swap_chain_desc.BufferDesc.Width = m_window_width; swap_chain_desc.BufferDesc.Height = m_window_height; @@ -281,7 +249,7 @@ bool D3D11DisplayWidget::createSwapChain() swap_chain_desc.SampleDesc.Count = 1; swap_chain_desc.BufferCount = 3; swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swap_chain_desc.OutputWindow = reinterpret_cast(winId()); + swap_chain_desc.OutputWindow = window_hwnd; swap_chain_desc.Windowed = TRUE; swap_chain_desc.SwapEffect = m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; @@ -289,6 +257,10 @@ bool D3D11DisplayWidget::createSwapChain() if (m_using_allow_tearing) swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + Log_InfoPrintf("Creating a %dx%d %s %s swap chain", m_window_width, m_window_height, + m_using_flip_model_swap_chain ? "flip-discard" : "discard", + swap_chain_desc.Windowed ? "windowed" : "full-screen"); + HRESULT hr = m_dxgi_factory->CreateSwapChain(m_device.Get(), &swap_chain_desc, m_swap_chain.GetAddressOf()); if (FAILED(hr) && m_using_flip_model_swap_chain) { @@ -310,44 +282,14 @@ bool D3D11DisplayWidget::createSwapChain() if (FAILED(hr)) Log_WarningPrintf("MakeWindowAssociation() to disable ALT+ENTER failed"); + if (!createSwapChainRTV()) + return false; + + emit m_widget->windowResizedEvent(m_window_width, m_window_height); return true; } -void D3D11DisplayWidget::recreateSwapChain() -{ - m_swap_chain_rtv.Reset(); - m_swap_chain.Reset(); - - if (!createSwapChain() || !createSwapChainRTV()) - Panic("Failed to recreate swap chain"); -} - -void D3D11DisplayWidget::windowResized(s32 new_window_width, s32 new_window_height) -{ - QtDisplayWidget::windowResized(new_window_width, new_window_height); - HostDisplay::WindowResized(new_window_width, new_window_height); - - if (!m_swap_chain) - return; - - if (m_using_flip_model_swap_chain != shouldUseFlipModelSwapChain()) - { - recreateSwapChain(); - return; - } - - m_swap_chain_rtv.Reset(); - - HRESULT hr = m_swap_chain->ResizeBuffers(0, new_window_width, new_window_height, DXGI_FORMAT_UNKNOWN, - m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); - if (FAILED(hr)) - Log_ErrorPrintf("ResizeBuffers() failed: 0x%08X", hr); - - if (!createSwapChainRTV()) - Panic("Failed to recreate swap chain RTV after resize"); -} - -bool D3D11DisplayWidget::createSwapChainRTV() +bool D3D11HostDisplay::createSwapChainRTV() { ComPtr backbuffer; HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.GetAddressOf())); @@ -372,7 +314,32 @@ bool D3D11DisplayWidget::createSwapChainRTV() return true; } -bool D3D11DisplayWidget::createDeviceResources() +void D3D11HostDisplay::destroySurface() +{ + m_swap_chain_rtv.Reset(); + m_swap_chain.Reset(); + QtHostDisplay::destroySurface(); +} + +void D3D11HostDisplay::WindowResized(s32 new_window_width, s32 new_window_height) +{ + QtHostDisplay::WindowResized(new_window_width, new_window_height); + + if (!m_swap_chain) + return; + + m_swap_chain_rtv.Reset(); + + HRESULT hr = m_swap_chain->ResizeBuffers(0, new_window_width, new_window_height, DXGI_FORMAT_UNKNOWN, + m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); + if (FAILED(hr)) + Log_ErrorPrintf("ResizeBuffers() failed: 0x%08X", hr); + + if (!createSwapChainRTV()) + Panic("Failed to recreate swap chain RTV after resize"); +} + +bool D3D11HostDisplay::createDeviceResources() { HRESULT hr; @@ -418,9 +385,9 @@ bool D3D11DisplayWidget::createDeviceResources() return true; } -void D3D11DisplayWidget::destroyDeviceResources() +void D3D11HostDisplay::destroyDeviceResources() { - QtDisplayWidget::destroyDeviceResources(); + QtHostDisplay::destroyDeviceResources(); m_display_uniform_buffer.Release(); m_swap_chain_rtv.Reset(); @@ -433,9 +400,9 @@ void D3D11DisplayWidget::destroyDeviceResources() m_display_rasterizer_state.Reset(); } -bool D3D11DisplayWidget::createImGuiContext() +bool D3D11HostDisplay::createImGuiContext() { - if (!QtDisplayWidget::createImGuiContext()) + if (!QtHostDisplay::createImGuiContext()) return false; if (!ImGui_ImplDX11_Init(m_device.Get(), m_context.Get())) @@ -446,13 +413,13 @@ bool D3D11DisplayWidget::createImGuiContext() return true; } -void D3D11DisplayWidget::destroyImGuiContext() +void D3D11HostDisplay::destroyImGuiContext() { ImGui_ImplDX11_Shutdown(); - QtDisplayWidget::destroyImGuiContext(); + QtHostDisplay::destroyImGuiContext(); } -void D3D11DisplayWidget::Render() +void D3D11HostDisplay::Render() { static constexpr std::array clear_color = {}; m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), clear_color.data()); @@ -472,12 +439,13 @@ void D3D11DisplayWidget::Render() ImGui_ImplDX11_NewFrame(); } -void D3D11DisplayWidget::renderDisplay() +void D3D11HostDisplay::renderDisplay() { if (!m_display_texture_handle) return; - auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin); + auto [vp_left, vp_top, vp_width, vp_height] = + CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin); m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); m_context->VSSetShader(m_display_vertex_shader.Get(), nullptr, 0); diff --git a/src/duckstation-qt/d3d11displaywidget.h b/src/duckstation-qt/d3d11hostdisplay.h similarity index 79% rename from src/duckstation-qt/d3d11displaywidget.h rename to src/duckstation-qt/d3d11hostdisplay.h index 927c1d545..4659e92c3 100644 --- a/src/duckstation-qt/d3d11displaywidget.h +++ b/src/duckstation-qt/d3d11hostdisplay.h @@ -4,37 +4,31 @@ #include "common/d3d11/texture.h" #include "common/windows_headers.h" #include "core/host_display.h" -#include "qtdisplaywidget.h" +#include "qthostdisplay.h" #include #include #include #include -class D3D11DisplayWidget final : public QtDisplayWidget, private HostDisplay +class D3D11HostDisplay final : public QtHostDisplay { - Q_OBJECT - public: template using ComPtr = Microsoft::WRL::ComPtr; - D3D11DisplayWidget(QtHostInterface* host_interface, QWidget* parent); - ~D3D11DisplayWidget(); - - HostDisplay* getHostDisplayInterface() override; + D3D11HostDisplay(QtHostInterface* host_interface); + ~D3D11HostDisplay(); bool hasDeviceContext() const override; - bool createDeviceContext(QThread* worker_thread, bool debug_device) override; - bool initializeDeviceContext(bool debug_device) override; + bool createDeviceContext(bool debug_device) override; void destroyDeviceContext() override; + bool createSurface() override; + void destroySurface() override; RenderAPI GetRenderAPI() const override; void* GetRenderDevice() const override; void* GetRenderContext() const override; - void* GetRenderWindow() const override; - - void ChangeRenderWindow(void* new_window) override; - void windowResized(s32 new_window_width, s32 new_window_height) override; + void WindowResized(s32 new_window_width, s32 new_window_height) override; std::unique_ptr CreateTexture(u32 width, u32 height, const void* initial_data, u32 initial_data_stride, bool dynamic) override; @@ -56,9 +50,7 @@ private: void destroyDeviceResources() override; bool shouldUseFlipModelSwapChain() const; - bool createSwapChain(); bool createSwapChainRTV(); - void recreateSwapChain(); void renderDisplay(); diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 1175129e7..72ac3621c 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -37,7 +37,7 @@ - + @@ -47,8 +47,9 @@ - + + @@ -63,14 +64,15 @@ - + + - + @@ -127,7 +129,6 @@ - @@ -135,7 +136,6 @@ - @@ -477,4 +477,4 @@ - \ No newline at end of file + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index 2bb41ddde..8608bef30 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -27,8 +27,6 @@ - - @@ -37,11 +35,15 @@ + + + + @@ -63,11 +65,11 @@ - - + + diff --git a/src/duckstation-qt/main.cpp b/src/duckstation-qt/main.cpp index 7786decae..c32496a68 100644 --- a/src/duckstation-qt/main.cpp +++ b/src/duckstation-qt/main.cpp @@ -9,7 +9,7 @@ static void InitLogging() { // set log flags -#ifdef _DEBUG +#ifdef _DEBUGA Log::SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG); Log::SetFilterLevel(LOGLEVEL_DEBUG); #else diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 8e07f022e..4d93f1c3f 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -6,6 +6,7 @@ #include "gamelistsettingswidget.h" #include "gamelistwidget.h" #include "qtdisplaywidget.h" +#include "qthostdisplay.h" #include "qthostinterface.h" #include "qtsettingsinterface.h" #include "scmversion/scmversion.h" @@ -66,17 +67,15 @@ bool MainWindow::confirmMessage(const QString& message) return (result == QMessageBox::Yes); } -void MainWindow::createDisplayWindow(QThread* worker_thread, bool use_debug_device, bool fullscreen, - bool render_to_main) +void MainWindow::createDisplay(QThread* worker_thread, bool use_debug_device, bool fullscreen, bool render_to_main) { - DebugAssert(!m_display_widget); + Assert(!m_host_display && !m_display_widget); + Assert(!fullscreen || !render_to_main); - m_display_widget = m_host_interface->createDisplayWidget(); + m_host_display = m_host_interface->createHostDisplay(); + m_display_widget = m_host_display->createWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr); m_display_widget->setWindowTitle(windowTitle()); m_display_widget->setWindowIcon(windowIcon()); - DebugAssert(m_display_widget); - - m_display_widget->setFocusPolicy(Qt::StrongFocus); if (fullscreen) { @@ -96,15 +95,77 @@ void MainWindow::createDisplayWindow(QThread* worker_thread, bool use_debug_devi // we need the surface visible.. this might be able to be replaced with something else QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - m_display_widget->createDeviceContext(worker_thread, use_debug_device); + if (!m_host_display->createDeviceContext(use_debug_device)) + { + reportError(tr("Failed to create host display device context.")); + return; + } + + if (!m_host_display->createSurface() || !m_host_display->makeDeviceContextCurrent()) + { + reportError(tr("Failed to create host display surface.")); + m_host_display->destroyDeviceContext(); + return; + } + + m_host_display->moveContextToThread(worker_thread); } -void MainWindow::destroyDisplayWindow() +void MainWindow::updateDisplay(QThread* worker_thread, bool fullscreen, bool render_to_main) { - DebugAssert(m_display_widget); + const bool is_fullscreen = m_display_widget->isFullScreen(); + const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent()); + if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main) + { + m_host_display->moveContextToThread(worker_thread); + return; + } - if (m_display_widget->isFullScreen()) + m_host_display->destroySurface(); + + if (is_rendering_to_main) + { + switchToGameListView(); + m_ui.mainContainer->removeWidget(m_display_widget); + } + + m_host_display->destroyWidget(); + m_display_widget = m_host_display->createWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr); + m_display_widget->setWindowTitle(windowTitle()); + m_display_widget->setWindowIcon(windowIcon()); + + if (fullscreen) + { + m_display_widget->showFullScreen(); + m_display_widget->setCursor(Qt::BlankCursor); + } + else if (!render_to_main) + { m_display_widget->showNormal(); + } + else + { + m_ui.mainContainer->insertWidget(1, m_display_widget); + switchToEmulationView(); + } + + // we need the surface visible.. this might be able to be replaced with something else + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + if (!m_host_display->createSurface()) + Panic("Failed to recreate surface on new widget."); + + m_display_widget->setFocus(); + + QSignalBlocker blocker(m_ui.actionFullscreen); + m_ui.actionFullscreen->setChecked(fullscreen); + + m_host_display->moveContextToThread(worker_thread); +} + +void MainWindow::destroyDisplay() +{ + DebugAssert(m_host_display && m_display_widget); if (m_display_widget->parent()) { @@ -112,58 +173,11 @@ void MainWindow::destroyDisplayWindow() switchToGameListView(); } - // recreate the display widget using the potentially-new renderer - delete m_display_widget; + m_host_display->destroyWidget(); m_display_widget = nullptr; -} -void MainWindow::updateDisplayWindow(bool fullscreen, bool render_to_main) -{ - const bool is_fullscreen = m_display_widget->isFullScreen(); - const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent()); - if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main) - return; - - if (fullscreen || !render_to_main) - { - if (m_display_widget->parent()) - { - m_ui.mainContainer->setCurrentIndex(0); - m_ui.mainContainer->removeWidget(m_display_widget); - m_display_widget->setParent(nullptr); - switchToGameListView(); - } - - if (fullscreen) - { - m_display_widget->showFullScreen(); - m_display_widget->setCursor(Qt::BlankCursor); - } - else - { - // if we don't position it, it ends up in the top-left corner with the title bar obscured - m_display_widget->setCursor(QCursor()); - m_display_widget->showNormal(); - m_display_widget->move(pos()); - } - } - else - { - // render-to-main - if (!m_display_widget->parent()) - { - m_ui.mainContainer->insertWidget(1, m_display_widget); - m_ui.mainContainer->setCurrentIndex(1); - } - - m_display_widget->setCursor(QCursor()); - } - - m_display_widget->windowResizedEvent(m_display_widget->scaledWindowWidth(), m_display_widget->scaledWindowHeight()); - m_display_widget->setFocus(); - - QSignalBlocker blocker(m_ui.actionFullscreen); - m_ui.actionFullscreen->setChecked(fullscreen); + delete m_host_display; + m_host_display = nullptr; } void MainWindow::focusDisplayWidget() @@ -523,10 +537,10 @@ void MainWindow::connectSignals() connect(m_host_interface, &QtHostInterface::messageReported, this, &MainWindow::reportMessage); connect(m_host_interface, &QtHostInterface::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection); - connect(m_host_interface, &QtHostInterface::createDisplayWindowRequested, this, &MainWindow::createDisplayWindow, + connect(m_host_interface, &QtHostInterface::createDisplayRequested, this, &MainWindow::createDisplay, Qt::BlockingQueuedConnection); - connect(m_host_interface, &QtHostInterface::destroyDisplayWindowRequested, this, &MainWindow::destroyDisplayWindow); - connect(m_host_interface, &QtHostInterface::updateDisplayWindowRequested, this, &MainWindow::updateDisplayWindow, + connect(m_host_interface, &QtHostInterface::destroyDisplayRequested, this, &MainWindow::destroyDisplay); + connect(m_host_interface, &QtHostInterface::updateDisplayRequested, this, &MainWindow::updateDisplay, Qt::BlockingQueuedConnection); connect(m_host_interface, &QtHostInterface::focusDisplayWidgetRequested, this, &MainWindow::focusDisplayWidget); connect(m_host_interface, &QtHostInterface::emulationStarted, this, &MainWindow::onEmulationStarted); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index ebaa8a4ce..6701d6121 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -12,6 +12,7 @@ class QThread; class GameListWidget; class QtHostInterface; +class QtHostDisplay; class QtDisplayWidget; struct GameListEntry; @@ -28,9 +29,9 @@ private Q_SLOTS: void reportError(const QString& message); void reportMessage(const QString& message); bool confirmMessage(const QString& message); - void createDisplayWindow(QThread* worker_thread, bool use_debug_device, bool fullscreen, bool render_to_main); - void destroyDisplayWindow(); - void updateDisplayWindow(bool fullscreen, bool render_to_main); + void createDisplay(QThread* worker_thread, bool use_debug_device, bool fullscreen, bool render_to_main); + void updateDisplay(QThread* worker_thread, bool fullscreen, bool render_to_main); + void destroyDisplay(); void focusDisplayWidget(); void onEmulationStarted(); void onEmulationStopped(); @@ -73,6 +74,8 @@ private: QtHostInterface* m_host_interface = nullptr; GameListWidget* m_game_list_widget = nullptr; + + QtHostDisplay* m_host_display = nullptr; QtDisplayWidget* m_display_widget = nullptr; QLabel* m_status_speed_widget = nullptr; diff --git a/src/duckstation-qt/opengldisplaywidget.cpp b/src/duckstation-qt/openglhostdisplay.cpp similarity index 79% rename from src/duckstation-qt/opengldisplaywidget.cpp rename to src/duckstation-qt/openglhostdisplay.cpp index 0b698f065..8efd32310 100644 --- a/src/duckstation-qt/opengldisplaywidget.cpp +++ b/src/duckstation-qt/openglhostdisplay.cpp @@ -1,14 +1,15 @@ -#include "opengldisplaywidget.h" +#include "openglhostdisplay.h" #include "common/assert.h" #include "common/log.h" #include "imgui.h" +#include "qtdisplaywidget.h" #include "qthostinterface.h" #include #include #include #include #include -Log_SetChannel(OpenGLDisplayWidget); +Log_SetChannel(OpenGLHostDisplay); static thread_local QOpenGLContext* s_thread_gl_context; @@ -27,7 +28,7 @@ static void* GetProcAddressCallback(const char* name) /// Changes the swap interval on a window. Since Qt doesn't expose this functionality, we need to change it manually /// ourselves it by calling system-specific functions. Assumes the context is current. -static void SetSwapInterval(QWindow* window, QOpenGLContext* context, int interval) +static void SetSwapInterval(QOpenGLContext* context, int interval) { static QOpenGLContext* last_context = nullptr; @@ -97,60 +98,44 @@ private: u32 m_height; }; -OpenGLDisplayWidget::OpenGLDisplayWidget(QtHostInterface* host_interface, QWidget* parent) - : QtDisplayWidget(host_interface, parent) +OpenGLHostDisplay::OpenGLHostDisplay(QtHostInterface* host_interface) : QtHostDisplay(host_interface) {} + +OpenGLHostDisplay::~OpenGLHostDisplay() = default; + +QtDisplayWidget* OpenGLHostDisplay::createWidget(QWidget* parent) { - QWindow* native_window = windowHandle(); + QtDisplayWidget* widget = QtHostDisplay::createWidget(parent); + + QWindow* native_window = widget->windowHandle(); Assert(native_window); native_window->setSurfaceType(QWindow::OpenGLSurface); + + return widget; } -OpenGLDisplayWidget::~OpenGLDisplayWidget() = default; - -HostDisplay* OpenGLDisplayWidget::getHostDisplayInterface() -{ - return this; -} - -HostDisplay::RenderAPI OpenGLDisplayWidget::GetRenderAPI() const +HostDisplay::RenderAPI OpenGLHostDisplay::GetRenderAPI() const { return m_gl_context->isOpenGLES() ? HostDisplay::RenderAPI::OpenGLES : HostDisplay::RenderAPI::OpenGL; } -void* OpenGLDisplayWidget::GetRenderDevice() const +void* OpenGLHostDisplay::GetRenderDevice() const { return nullptr; } -void* OpenGLDisplayWidget::GetRenderContext() const +void* OpenGLHostDisplay::GetRenderContext() const { return m_gl_context.get(); } -void* OpenGLDisplayWidget::GetRenderWindow() const -{ - return const_cast(static_cast(this)); -} - -void OpenGLDisplayWidget::ChangeRenderWindow(void* new_window) -{ - Panic("Not implemented"); -} - -void OpenGLDisplayWidget::windowResized(s32 new_window_width, s32 new_window_height) -{ - QtDisplayWidget::windowResized(new_window_width, new_window_height); - HostDisplay::WindowResized(new_window_width, new_window_height); -} - -std::unique_ptr OpenGLDisplayWidget::CreateTexture(u32 width, u32 height, const void* initial_data, - u32 initial_data_stride, bool dynamic) +std::unique_ptr OpenGLHostDisplay::CreateTexture(u32 width, u32 height, const void* initial_data, + u32 initial_data_stride, bool dynamic) { return OpenGLDisplayWidgetTexture::Create(width, height, initial_data, initial_data_stride); } -void OpenGLDisplayWidget::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, - const void* texture_data, u32 texture_data_stride) +void OpenGLHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, + const void* texture_data, u32 texture_data_stride) { OpenGLDisplayWidgetTexture* tex = static_cast(texture); Assert((texture_data_stride % sizeof(u32)) == 0); @@ -171,8 +156,8 @@ void OpenGLDisplayWidget::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 glBindTexture(GL_TEXTURE_2D, old_texture_binding); } -bool OpenGLDisplayWidget::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, - void* out_data, u32 out_data_stride) +bool OpenGLHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, + u32 out_data_stride) { GLint old_alignment = 0, old_row_length = 0; glGetIntegerv(GL_PACK_ALIGNMENT, &old_alignment); @@ -189,17 +174,17 @@ bool OpenGLDisplayWidget::DownloadTexture(const void* texture_handle, u32 x, u32 return true; } -void OpenGLDisplayWidget::SetVSync(bool enabled) +void OpenGLHostDisplay::SetVSync(bool enabled) { // Window framebuffer has to be bound to call SetSwapInterval. GLint current_fbo = 0; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - SetSwapInterval(windowHandle(), m_gl_context.get(), enabled ? 1 : 0); + SetSwapInterval(m_gl_context.get(), enabled ? 1 : 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); } -const char* OpenGLDisplayWidget::GetGLSLVersionString() const +const char* OpenGLHostDisplay::GetGLSLVersionString() const { if (m_gl_context->isOpenGLES()) { @@ -217,7 +202,7 @@ const char* OpenGLDisplayWidget::GetGLSLVersionString() const } } -std::string OpenGLDisplayWidget::GetGLSLVersionHeader() const +std::string OpenGLHostDisplay::GetGLSLVersionHeader() const { std::string header = GetGLSLVersionString(); header += "\n\n"; @@ -250,12 +235,12 @@ static void APIENTRY GLDebugCallback(GLenum source, GLenum type, GLuint id, GLen } } -bool OpenGLDisplayWidget::hasDeviceContext() const +bool OpenGLHostDisplay::hasDeviceContext() const { return static_cast(m_gl_context); } -bool OpenGLDisplayWidget::createDeviceContext(QThread* worker_thread, bool debug_device) +bool OpenGLHostDisplay::createDeviceContext(bool debug_device) { m_gl_context = std::make_unique(); @@ -308,28 +293,12 @@ bool OpenGLDisplayWidget::createDeviceContext(QThread* worker_thread, bool debug Log_InfoPrintf("Got a %s %d.%d context", (m_gl_context->isOpenGLES() ? "OpenGL ES" : "desktop OpenGL"), surface_format.majorVersion(), surface_format.minorVersion()); - if (!m_gl_context->makeCurrent(windowHandle())) - { - Log_ErrorPrintf("Failed to make GL context current on UI thread"); - m_gl_context.reset(); - return false; - } - - if (!QtDisplayWidget::createDeviceContext(worker_thread, debug_device)) - { - m_gl_context->doneCurrent(); - m_gl_context.reset(); - return false; - } - - m_gl_context->doneCurrent(); - m_gl_context->moveToThread(worker_thread); return true; } -bool OpenGLDisplayWidget::initializeDeviceContext(bool debug_device) +bool OpenGLHostDisplay::initializeDeviceContext(bool debug_device) { - if (!m_gl_context->makeCurrent(windowHandle())) + if (!m_gl_context->makeCurrent(m_widget->windowHandle())) return false; s_thread_gl_context = m_gl_context.get(); @@ -352,7 +321,7 @@ bool OpenGLDisplayWidget::initializeDeviceContext(bool debug_device) glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); } - if (!QtDisplayWidget::initializeDeviceContext(debug_device)) + if (!QtHostDisplay::initializeDeviceContext(debug_device)) { s_thread_gl_context = nullptr; m_gl_context->doneCurrent(); @@ -362,20 +331,47 @@ bool OpenGLDisplayWidget::initializeDeviceContext(bool debug_device) return true; } -void OpenGLDisplayWidget::destroyDeviceContext() +bool OpenGLHostDisplay::makeDeviceContextCurrent() +{ + if (!m_gl_context->makeCurrent(m_widget->windowHandle())) + { + Log_ErrorPrintf("Failed to make GL context current"); + return false; + } + + return true; +} + +void OpenGLHostDisplay::moveContextToThread(QThread* new_thread) +{ + m_gl_context->doneCurrent(); + m_gl_context->moveToThread(new_thread); +} + +void OpenGLHostDisplay::destroyDeviceContext() { Assert(m_gl_context && s_thread_gl_context == m_gl_context.get()); - QtDisplayWidget::destroyDeviceContext(); + QtHostDisplay::destroyDeviceContext(); s_thread_gl_context = nullptr; m_gl_context->doneCurrent(); m_gl_context.reset(); } -bool OpenGLDisplayWidget::createImGuiContext() +bool OpenGLHostDisplay::createSurface() { - if (!QtDisplayWidget::createImGuiContext()) + m_window_width = m_widget->scaledWindowWidth(); + m_window_height = m_widget->scaledWindowHeight(); + emit m_widget->windowResizedEvent(m_window_width, m_window_height); + return true; +} + +void OpenGLHostDisplay::destroySurface() {} + +bool OpenGLHostDisplay::createImGuiContext() +{ + if (!QtHostDisplay::createImGuiContext()) return false; if (!ImGui_ImplOpenGL3_Init(GetGLSLVersionString())) @@ -386,14 +382,14 @@ bool OpenGLDisplayWidget::createImGuiContext() return true; } -void OpenGLDisplayWidget::destroyImGuiContext() +void OpenGLHostDisplay::destroyImGuiContext() { ImGui_ImplOpenGL3_Shutdown(); - QtDisplayWidget::destroyImGuiContext(); + QtHostDisplay::destroyImGuiContext(); } -bool OpenGLDisplayWidget::createDeviceResources() +bool OpenGLHostDisplay::createDeviceResources() { static constexpr char fullscreen_quad_vertex_shader[] = R"( uniform vec4 u_src_rect; @@ -453,9 +449,9 @@ void main() return true; } -void OpenGLDisplayWidget::destroyDeviceResources() +void OpenGLHostDisplay::destroyDeviceResources() { - QtDisplayWidget::destroyDeviceResources(); + QtHostDisplay::destroyDeviceResources(); if (m_display_vao != 0) glDeleteVertexArrays(1, &m_display_vao); @@ -467,7 +463,7 @@ void OpenGLDisplayWidget::destroyDeviceResources() m_display_program.Destroy(); } -void OpenGLDisplayWidget::Render() +void OpenGLHostDisplay::Render() { glDisable(GL_SCISSOR_TEST); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); @@ -479,7 +475,7 @@ void OpenGLDisplayWidget::Render() ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - QWindow* window_handle = windowHandle(); + QWindow* window_handle = m_widget->windowHandle(); m_gl_context->makeCurrent(window_handle); m_gl_context->swapBuffers(window_handle); @@ -489,12 +485,13 @@ void OpenGLDisplayWidget::Render() GL::Program::ResetLastProgram(); } -void OpenGLDisplayWidget::renderDisplay() +void OpenGLHostDisplay::renderDisplay() { if (!m_display_texture_handle) return; - const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin); + const auto [vp_left, vp_top, vp_width, vp_height] = + CalculateDrawRect(m_window_width, m_window_height, m_display_top_margin); glViewport(vp_left, m_window_height - (m_display_top_margin + vp_top) - vp_height, vp_width, vp_height); glDisable(GL_BLEND); diff --git a/src/duckstation-qt/opengldisplaywidget.h b/src/duckstation-qt/openglhostdisplay.h similarity index 79% rename from src/duckstation-qt/opengldisplaywidget.h rename to src/duckstation-qt/openglhostdisplay.h index a4820408a..f8728d2ef 100644 --- a/src/duckstation-qt/opengldisplaywidget.h +++ b/src/duckstation-qt/openglhostdisplay.h @@ -12,33 +12,32 @@ #include "common/gl/texture.h" #include "core/host_display.h" #include "qtdisplaywidget.h" +#include "qthostdisplay.h" #include #include class QtHostInterface; -class OpenGLDisplayWidget final : public QtDisplayWidget, public HostDisplay +class OpenGLHostDisplay final : public QtHostDisplay { - Q_OBJECT - public: - OpenGLDisplayWidget(QtHostInterface* host_interface, QWidget* parent); - ~OpenGLDisplayWidget(); + OpenGLHostDisplay(QtHostInterface* host_interface); + ~OpenGLHostDisplay(); - HostDisplay* getHostDisplayInterface() override; + QtDisplayWidget* createWidget(QWidget* parent) override; bool hasDeviceContext() const override; - bool createDeviceContext(QThread* worker_thread, bool debug_device) override; + bool createDeviceContext(bool debug_device) override; bool initializeDeviceContext(bool debug_device) override; + bool makeDeviceContextCurrent() override; + void moveContextToThread(QThread* new_thread) override; void destroyDeviceContext() override; + bool createSurface() override; + void destroySurface(); RenderAPI GetRenderAPI() const override; void* GetRenderDevice() const override; void* GetRenderContext() const override; - void* GetRenderWindow() const override; - - void ChangeRenderWindow(void* new_window) override; - void windowResized(s32 new_window_width, s32 new_window_height) override; std::unique_ptr CreateTexture(u32 width, u32 height, const void* initial_data, u32 initial_data_stride, bool dynamic) override; diff --git a/src/duckstation-qt/qtdisplaywidget.cpp b/src/duckstation-qt/qtdisplaywidget.cpp index 11e3a4aba..80c6fe9be 100644 --- a/src/duckstation-qt/qtdisplaywidget.cpp +++ b/src/duckstation-qt/qtdisplaywidget.cpp @@ -1,6 +1,5 @@ #include "qtdisplaywidget.h" -#include "frontend-common/imgui_styles.h" -#include "imgui.h" +#include "qthostdisplay.h" #include "qthostinterface.h" #include "qtutils.h" #include @@ -10,47 +9,18 @@ #include #include -QtDisplayWidget::QtDisplayWidget(QtHostInterface* host_interface, QWidget* parent) - : QWidget(parent), m_host_interface(host_interface) +QtDisplayWidget::QtDisplayWidget(QWidget* parent) : QWidget(parent) { // 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); + setFocusPolicy(Qt::StrongFocus); } QtDisplayWidget::~QtDisplayWidget() = default; -HostDisplay* QtDisplayWidget::getHostDisplayInterface() -{ - return nullptr; -} - -bool QtDisplayWidget::hasDeviceContext() const -{ - return true; -} - -bool QtDisplayWidget::createDeviceContext(QThread* worker_thread, bool debug_device) -{ - return true; -} - -bool QtDisplayWidget::initializeDeviceContext(bool debug_device) -{ - if (!createImGuiContext() || !createDeviceResources()) - return false; - - return true; -} - -void QtDisplayWidget::destroyDeviceContext() -{ - destroyImGuiContext(); - destroyDeviceResources(); -} - qreal QtDisplayWidget::devicePixelRatioFromScreen() const { QScreen* screen_for_ratio; @@ -75,49 +45,6 @@ int QtDisplayWidget::scaledWindowHeight() const return static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen())); } -bool QtDisplayWidget::createImGuiContext() -{ - ImGui::CreateContext(); - - auto& io = ImGui::GetIO(); - io.IniFilename = nullptr; - io.DisplaySize.x = static_cast(scaledWindowWidth()); - io.DisplaySize.y = static_cast(scaledWindowHeight()); - - const float framebuffer_scale = static_cast(devicePixelRatioFromScreen()); - io.DisplayFramebufferScale.x = framebuffer_scale; - io.DisplayFramebufferScale.y = framebuffer_scale; - ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); - - ImGui::StyleColorsDarker(); - ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale); - - return true; -} - -void QtDisplayWidget::destroyImGuiContext() -{ - ImGui::DestroyContext(); -} - -bool QtDisplayWidget::createDeviceResources() -{ - return true; -} - -void QtDisplayWidget::destroyDeviceResources() {} - -void QtDisplayWidget::windowResized(s32 new_window_width, s32 new_window_height) -{ - // imgui may not have been initialized yet - if (!ImGui::GetCurrentContext()) - return; - - auto& io = ImGui::GetIO(); - io.DisplaySize.x = static_cast(new_window_width); - io.DisplaySize.y = static_cast(new_window_height); -} - QPaintEngine* QtDisplayWidget::paintEngine() const { return nullptr; @@ -132,7 +59,7 @@ bool QtDisplayWidget::event(QEvent* event) { QKeyEvent* key_event = static_cast(event); if (!key_event->isAutoRepeat()) - m_host_interface->handleKeyEvent(QtUtils::KeyEventToInt(key_event), event->type() == QEvent::KeyPress); + emit windowKeyEvent(QtUtils::KeyEventToInt(key_event), event->type() == QEvent::KeyPress); return true; } @@ -147,7 +74,7 @@ bool QtDisplayWidget::event(QEvent* event) case QEvent::Close: { - m_host_interface->synchronousPowerOffSystem(); + emit windowClosedEvent(); QWidget::event(event); return true; } diff --git a/src/duckstation-qt/qtdisplaywidget.h b/src/duckstation-qt/qtdisplaywidget.h index 2c07fc29f..eeeaf8c13 100644 --- a/src/duckstation-qt/qtdisplaywidget.h +++ b/src/duckstation-qt/qtdisplaywidget.h @@ -2,49 +2,26 @@ #include "common/types.h" #include -class QKeyEvent; -class QResizeEvent; - -class HostDisplay; - -class QtHostInterface; - -class QtDisplayWidget : public QWidget +class QtDisplayWidget final : public QWidget { Q_OBJECT public: - QtDisplayWidget(QtHostInterface* host_interface, QWidget* parent); - virtual ~QtDisplayWidget(); + QtDisplayWidget(QWidget* parent); + ~QtDisplayWidget(); - virtual HostDisplay* getHostDisplayInterface(); - - virtual bool hasDeviceContext() const; - virtual bool createDeviceContext(QThread* worker_thread, bool debug_device); - virtual bool initializeDeviceContext(bool debug_device); - virtual void destroyDeviceContext(); - - // this comes back on the emu thread - virtual void windowResized(s32 new_window_width, s32 new_window_height); - - virtual QPaintEngine* paintEngine() const override; + QPaintEngine* paintEngine() const override; int scaledWindowWidth() const; int scaledWindowHeight() const; + qreal devicePixelRatioFromScreen() const; Q_SIGNALS: void windowResizedEvent(int width, int height); void windowRestoredEvent(); + void windowClosedEvent(); + void windowKeyEvent(int key_code, bool pressed); protected: - qreal devicePixelRatioFromScreen() const; - - virtual bool createImGuiContext(); - virtual void destroyImGuiContext(); - virtual bool createDeviceResources(); - virtual void destroyDeviceResources(); - - virtual bool event(QEvent* event) override; - - QtHostInterface* m_host_interface; + bool event(QEvent* event) override; }; diff --git a/src/duckstation-qt/qthostdisplay.cpp b/src/duckstation-qt/qthostdisplay.cpp new file mode 100644 index 000000000..b37c948e5 --- /dev/null +++ b/src/duckstation-qt/qthostdisplay.cpp @@ -0,0 +1,130 @@ +#include "qthostdisplay.h" +#include "common/assert.h" +#include "frontend-common/imgui_styles.h" +#include "imgui.h" +#include "qtdisplaywidget.h" +#include "qthostinterface.h" +#include + +QtHostDisplay::QtHostDisplay(QtHostInterface* host_interface) : m_host_interface(host_interface) {} + +QtHostDisplay::~QtHostDisplay() = default; + +QtDisplayWidget* QtHostDisplay::createWidget(QWidget* parent) +{ + Assert(!m_widget); + m_widget = new QtDisplayWidget(parent); + + // We want a native window for both D3D and OpenGL. + m_widget->setAutoFillBackground(false); + m_widget->setAttribute(Qt::WA_NativeWindow, true); + m_widget->setAttribute(Qt::WA_NoSystemBackground, true); + m_widget->setAttribute(Qt::WA_PaintOnScreen, true); + + return m_widget; +} + +void QtHostDisplay::destroyWidget() +{ + Assert(m_widget); + + delete m_widget; + m_widget = nullptr; +} + +bool QtHostDisplay::hasDeviceContext() const +{ + return false; +} + +bool QtHostDisplay::createDeviceContext(bool debug_device) +{ + return false; +} + +bool QtHostDisplay::initializeDeviceContext(bool debug_device) +{ + if (!createImGuiContext() || !createDeviceResources()) + return false; + + return true; +} + +bool QtHostDisplay::makeDeviceContextCurrent() +{ + return true; +} + +void QtHostDisplay::moveContextToThread(QThread* new_thread) {} + +void QtHostDisplay::destroyDeviceContext() +{ + destroyImGuiContext(); + destroyDeviceResources(); +} + +bool QtHostDisplay::createSurface() +{ + return false; +} + +void QtHostDisplay::destroySurface() {} + +void* QtHostDisplay::GetRenderWindow() const +{ + return m_widget; +} + +void QtHostDisplay::ChangeRenderWindow(void* new_window) +{ + Panic("Not implemented"); +} + +bool QtHostDisplay::createImGuiContext() +{ + ImGui::CreateContext(); + + auto& io = ImGui::GetIO(); + io.IniFilename = nullptr; + io.DisplaySize.x = static_cast(m_window_width); + io.DisplaySize.y = static_cast(m_window_height); + + const float framebuffer_scale = static_cast(m_widget->devicePixelRatioFromScreen()); + io.DisplayFramebufferScale.x = framebuffer_scale; + io.DisplayFramebufferScale.y = framebuffer_scale; + ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); + + ImGui::StyleColorsDarker(); + ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale); + + return true; +} + +void QtHostDisplay::destroyImGuiContext() +{ + ImGui::DestroyContext(); +} + +bool QtHostDisplay::createDeviceResources() +{ + return true; +} + +void QtHostDisplay::destroyDeviceResources() {} + +void QtHostDisplay::WindowResized(s32 new_window_width, s32 new_window_height) +{ + HostDisplay::WindowResized(new_window_width, new_window_height); + updateImGuiDisplaySize(); +} + +void QtHostDisplay::updateImGuiDisplaySize() +{ + // imgui may not have been initialized yet + if (!ImGui::GetCurrentContext()) + return; + + auto& io = ImGui::GetIO(); + io.DisplaySize.x = static_cast(m_window_width); + io.DisplaySize.y = static_cast(m_window_height); +} diff --git a/src/duckstation-qt/qthostdisplay.h b/src/duckstation-qt/qthostdisplay.h new file mode 100644 index 000000000..7840a3196 --- /dev/null +++ b/src/duckstation-qt/qthostdisplay.h @@ -0,0 +1,46 @@ +#pragma once +#include "common/types.h" +#include "core/host_display.h" + +class QThread; +class QWidget; + +class QtHostInterface; +class QtDisplayWidget; + +class QtHostDisplay : public HostDisplay +{ +public: + QtHostDisplay(QtHostInterface* host_interface); + virtual ~QtHostDisplay(); + + ALWAYS_INLINE bool hasWidget() const { return (m_widget != nullptr); } + ALWAYS_INLINE QtDisplayWidget* getWidget() const { return m_widget; } + + virtual QtDisplayWidget* createWidget(QWidget* parent); + virtual void destroyWidget(); + + virtual bool hasDeviceContext() const; + virtual bool createDeviceContext(bool debug_device); + virtual bool initializeDeviceContext(bool debug_device); + virtual bool makeDeviceContextCurrent(); + virtual void moveContextToThread(QThread* new_thread); + virtual void destroyDeviceContext(); + virtual bool createSurface(); + virtual void destroySurface(); + + virtual void* GetRenderWindow() const override; + virtual void ChangeRenderWindow(void* new_window) override; + virtual void WindowResized(s32 new_window_width, s32 new_window_height) override; + + void updateImGuiDisplaySize(); + +protected: + virtual bool createImGuiContext(); + virtual void destroyImGuiContext(); + virtual bool createDeviceResources(); + virtual void destroyDeviceResources(); + + QtHostInterface* m_host_interface; + QtDisplayWidget* m_widget = nullptr; +}; diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 900772d22..968447b12 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -11,7 +11,7 @@ #include "frontend-common/sdl_audio_stream.h" #include "frontend-common/sdl_controller_interface.h" #include "mainwindow.h" -#include "opengldisplaywidget.h" +#include "openglhostdisplay.h" #include "qtprogresscallback.h" #include "qtsettingsinterface.h" #include "qtutils.h" @@ -26,7 +26,7 @@ Log_SetChannel(QtHostInterface); #ifdef WIN32 -#include "d3d11displaywidget.h" +#include "d3d11hostdisplay.h" #endif QtHostInterface::QtHostInterface(QObject* parent) : QObject(parent), CommonHostInterface() @@ -36,7 +36,7 @@ QtHostInterface::QtHostInterface(QObject* parent) : QObject(parent), CommonHostI QtHostInterface::~QtHostInterface() { - Assert(!m_display_widget); + Assert(!getHostDisplay()); } const char* QtHostInterface::GetFrontendName() const @@ -162,10 +162,10 @@ void QtHostInterface::applySettings() // detect when render-to-main flag changes const bool render_to_main = m_qsettings->value("Main/RenderToMainWindow", true).toBool(); - if (m_system && m_display_widget && !m_is_fullscreen && render_to_main != m_is_rendering_to_main) + if (m_system && getHostDisplay() && !m_is_fullscreen && render_to_main != m_is_rendering_to_main) { m_is_rendering_to_main = render_to_main; - emit updateDisplayWindowRequested(false, render_to_main); + updateDisplayState(); } } @@ -188,23 +188,6 @@ void QtHostInterface::setMainWindow(MainWindow* window) m_main_window = window; } -QtDisplayWidget* QtHostInterface::createDisplayWidget() -{ - Assert(!m_display_widget); - -#ifdef WIN32 - if (m_settings.gpu_renderer == GPURenderer::HardwareOpenGL) - m_display_widget = new OpenGLDisplayWidget(this, nullptr); - else - m_display_widget = new D3D11DisplayWidget(this, nullptr); -#else - m_display_widget = new OpenGLDisplayWidget(this, nullptr); -#endif - connect(m_display_widget, &QtDisplayWidget::windowResizedEvent, this, &QtHostInterface::onDisplayWidgetResized); - connect(m_display_widget, &QtDisplayWidget::windowRestoredEvent, this, &QtHostInterface::redrawDisplayWindow); - return m_display_widget; -} - void QtHostInterface::bootSystem(const SystemBootParameters& params) { if (!isOnWorkerThread()) @@ -231,24 +214,19 @@ void QtHostInterface::resumeSystemFromState(const QString& filename, bool boot_o HostInterface::ResumeSystemFromState(filename.toStdString().c_str(), boot_on_failure); } -void QtHostInterface::handleKeyEvent(int key, bool pressed) +void QtHostInterface::onDisplayWindowKeyEvent(int key, bool pressed) { - if (!isOnWorkerThread()) - { - QMetaObject::invokeMethod(this, "handleKeyEvent", Qt::QueuedConnection, Q_ARG(int, key), Q_ARG(bool, pressed)); - return; - } - + DebugAssert(isOnWorkerThread()); HandleHostKeyEvent(key, pressed); } -void QtHostInterface::onDisplayWidgetResized(int width, int height) +void QtHostInterface::onHostDisplayWindowResized(int width, int height) { // this can be null if it was destroyed and the main thread is late catching up - if (!m_display_widget) + if (!getHostDisplay()) return; - m_display_widget->windowResized(width, height); + getHostDisplay()->WindowResized(width, height); // re-render the display, since otherwise it will be out of date and stretched if paused if (m_system) @@ -263,7 +241,7 @@ void QtHostInterface::redrawDisplayWindow() return; } - if (!m_display_widget || !m_system) + if (!getHostDisplay() || !m_system) return; renderDisplay(); @@ -280,39 +258,91 @@ void QtHostInterface::toggleFullscreen() SetFullscreen(!m_is_fullscreen); } +QtHostDisplay* QtHostInterface::getHostDisplay() +{ + return static_cast(m_display); +} + bool QtHostInterface::AcquireHostDisplay() { - DebugAssert(!m_display_widget); + Assert(!m_display); m_is_rendering_to_main = getSettingValue("Main/RenderToMainWindow", true).toBool(); - emit createDisplayWindowRequested(m_worker_thread, m_settings.gpu_use_debug_device, m_is_fullscreen, - m_is_rendering_to_main); - if (!m_display_widget->hasDeviceContext()) + emit createDisplayRequested(m_worker_thread, m_settings.gpu_use_debug_device, m_is_fullscreen, + m_is_rendering_to_main); + Assert(m_display); + + if (!getHostDisplay()->hasDeviceContext()) { - m_display_widget = nullptr; - emit destroyDisplayWindowRequested(); + emit destroyDisplayRequested(); + m_display = nullptr; return false; } - if (!m_display_widget->initializeDeviceContext(m_settings.gpu_use_debug_device)) + if (!getHostDisplay()->makeDeviceContextCurrent() || + !getHostDisplay()->initializeDeviceContext(m_settings.gpu_use_debug_device)) { - m_display_widget->destroyDeviceContext(); - m_display_widget = nullptr; - emit destroyDisplayWindowRequested(); + getHostDisplay()->destroyDeviceContext(); + emit destroyDisplayRequested(); + m_display = nullptr; return false; } - m_display = m_display_widget->getHostDisplayInterface(); + connectDisplaySignals(); return true; } +QtHostDisplay* QtHostInterface::createHostDisplay() +{ + Assert(!getHostDisplay()); + +#ifdef WIN32 + if (m_settings.gpu_renderer == GPURenderer::HardwareOpenGL) + m_display = new OpenGLHostDisplay(this); + else + m_display = new D3D11HostDisplay(this); +#else + m_display = new OpenGLHostDisplay(this); +#endif + + return getHostDisplay(); +} + +void QtHostInterface::connectDisplaySignals() +{ + QtDisplayWidget* widget = getHostDisplay()->getWidget(); + connect(widget, &QtDisplayWidget::windowResizedEvent, this, &QtHostInterface::onHostDisplayWindowResized); + connect(widget, &QtDisplayWidget::windowRestoredEvent, this, &QtHostInterface::redrawDisplayWindow); + connect(widget, &QtDisplayWidget::windowClosedEvent, this, &QtHostInterface::powerOffSystem, + Qt::BlockingQueuedConnection); + connect(widget, &QtDisplayWidget::windowKeyEvent, this, &QtHostInterface::onDisplayWindowKeyEvent); +} + +void QtHostInterface::disconnectDisplaySignals() +{ + getHostDisplay()->getWidget()->disconnect(this); +} + +void QtHostInterface::updateDisplayState() +{ + // this expects the context to get moved back to us afterwards + getHostDisplay()->moveContextToThread(m_original_thread); + emit updateDisplayRequested(m_worker_thread, m_is_fullscreen, m_is_rendering_to_main); + if (!getHostDisplay()->makeDeviceContextCurrent()) + Panic("Failed to make device context current after updating"); + + getHostDisplay()->updateImGuiDisplaySize(); + connectDisplaySignals(); + UpdateSpeedLimiterState(); +} + void QtHostInterface::ReleaseHostDisplay() { - DebugAssert(m_display_widget && m_display == m_display_widget->getHostDisplayInterface()); + Assert(m_display); + + getHostDisplay()->destroyDeviceContext(); + emit destroyDisplayRequested(); m_display = nullptr; - m_display_widget->destroyDeviceContext(); - m_display_widget = nullptr; - emit destroyDisplayWindowRequested(); } bool QtHostInterface::IsFullscreen() const @@ -326,7 +356,7 @@ bool QtHostInterface::SetFullscreen(bool enabled) return true; m_is_fullscreen = enabled; - emit updateDisplayWindowRequested(m_is_fullscreen, m_is_rendering_to_main); + updateDisplayState(); return true; } diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index ba226e755..cdfb12f1a 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -25,7 +25,8 @@ class QTimer; class GameList; class MainWindow; -class QtDisplayWidget; + +class QtHostDisplay; Q_DECLARE_METATYPE(SystemBootParameters); @@ -65,7 +66,7 @@ public: ALWAYS_INLINE MainWindow* getMainWindow() const { return m_main_window; } void setMainWindow(MainWindow* window); - QtDisplayWidget* createDisplayWidget(); + QtHostDisplay* createHostDisplay(); void populateSaveStateMenus(const char* game_code, QMenu* load_menu, QMenu* save_menu); @@ -87,11 +88,10 @@ Q_SIGNALS: void emulationPaused(bool paused); void stateSaved(const QString& game_code, bool global, qint32 slot); void gameListRefreshed(); - void createDisplayWindowRequested(QThread* worker_thread, bool use_debug_device, bool fullscreen, - bool render_to_main); - void destroyDisplayWindowRequested(); - void updateDisplayWindowRequested(bool fullscreen, bool render_to_main); + void createDisplayRequested(QThread* worker_thread, bool use_debug_device, bool fullscreen, bool render_to_main); + void updateDisplayRequested(QThread* worker_thread, bool fullscreen, bool render_to_main); void focusDisplayWidgetRequested(); + void destroyDisplayRequested(); void systemPerformanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time); void runningGameChanged(const QString& filename, const QString& game_code, const QString& game_title); @@ -103,7 +103,7 @@ public Q_SLOTS: void applySettings(); void updateInputMap(); void applyInputProfile(const QString& profile_path); - void handleKeyEvent(int key, bool pressed); + void onDisplayWindowKeyEvent(int key, bool pressed); void bootSystem(const SystemBootParameters& params); void resumeSystemFromState(const QString& filename, bool boot_on_failure); void powerOffSystem(); @@ -129,7 +129,7 @@ public Q_SLOTS: private Q_SLOTS: void doStopThread(); - void onDisplayWidgetResized(int width, int height); + void onHostDisplayWindowResized(int width, int height); void doBackgroundControllerPoll(); protected: @@ -180,6 +180,8 @@ private: Common::Event m_init_event; }; + QtHostDisplay* getHostDisplay(); + void createBackgroundControllerPollTimer(); void destroyBackgroundControllerPollTimer(); @@ -189,13 +191,15 @@ private: bool initializeOnThread(); void shutdownOnThread(); void renderDisplay(); + void connectDisplaySignals(); + void disconnectDisplaySignals(); + void updateDisplayState(); void wakeThread(); std::unique_ptr m_qsettings; std::recursive_mutex m_qsettings_mutex; MainWindow* m_main_window = nullptr; - QtDisplayWidget* m_display_widget = nullptr; QThread* m_original_thread = nullptr; Thread* m_worker_thread = nullptr; QEventLoop* m_worker_thread_event_loop = nullptr;