Qt: Refactor render widget state transitions

Recreate widget each time. Fixes fullscreen mode switches on D3D11 and
hopefully Wayland.
This commit is contained in:
Connor McLaughlin 2020-04-22 21:13:51 +10:00
parent d7aa514f14
commit ea3c0b65cf
16 changed files with 550 additions and 459 deletions

View file

@ -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()

View file

@ -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 <array>
#include <dxgi1_5.h>
#include <imgui.h>
#include <imgui_impl_dx11.h>
Log_SetChannel(D3D11DisplayWidget);
Log_SetChannel(D3D11HostDisplay);
class D3D11DisplayWidgetTexture : public HostDisplayTexture
{
@ -61,50 +62,32 @@ 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<QWidget*>(static_cast<const QWidget*>(this));
}
void D3D11DisplayWidget::ChangeRenderWindow(void* new_window)
{
Panic("Not supported");
}
std::unique_ptr<HostDisplayTexture> D3D11DisplayWidget::CreateTexture(u32 width, u32 height, const void* initial_data,
std::unique_ptr<HostDisplayTexture> 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,
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<D3D11DisplayWidgetTexture*>(texture);
@ -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<ID3D11ShaderResourceView*>(static_cast<const ID3D11ShaderResourceView*>(texture_handle));
@ -159,17 +142,17 @@ bool D3D11DisplayWidget::DownloadTexture(const void* texture_handle, u32 x, u32
static_cast<u32*>(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<bool>(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;
return true;
}
if (!QtDisplayWidget::createDeviceContext(worker_thread, debug_device))
void D3D11HostDisplay::destroyDeviceContext()
{
QtHostDisplay::destroyDeviceContext();
m_swap_chain.Reset();
m_context.Reset();
m_device.Reset();
}
return true;
}
bool D3D11DisplayWidget::initializeDeviceContext(bool debug_device)
{
if (!createSwapChainRTV())
return false;
if (!QtDisplayWidget::initializeDeviceContext(debug_device))
return false;
return true;
}
void D3D11DisplayWidget::destroyDeviceContext()
{
QtDisplayWidget::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<HWND>(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<HWND>(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<ID3D11Texture2D> 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<float, 4> 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);

View file

@ -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 <d3d11.h>
#include <dxgi.h>
#include <memory>
#include <wrl/client.h>
class D3D11DisplayWidget final : public QtDisplayWidget, private HostDisplay
class D3D11HostDisplay final : public QtHostDisplay
{
Q_OBJECT
public:
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
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<HostDisplayTexture> 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();

View file

@ -37,7 +37,7 @@
<ItemGroup>
<ClCompile Include="audiosettingswidget.cpp" />
<ClCompile Include="consolesettingswidget.cpp" />
<ClCompile Include="d3d11displaywidget.cpp" />
<ClCompile Include="d3d11hostdisplay.cpp" />
<ClCompile Include="generalsettingswidget.cpp" />
<ClCompile Include="gpusettingswidget.cpp" />
<ClCompile Include="hotkeysettingswidget.cpp" />
@ -47,8 +47,9 @@
<ClCompile Include="gamelistwidget.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="mainwindow.cpp" />
<ClCompile Include="opengldisplaywidget.cpp" />
<ClCompile Include="openglhostdisplay.cpp" />
<ClCompile Include="portsettingswidget.cpp" />
<ClCompile Include="qthostdisplay.cpp" />
<ClCompile Include="qthostinterface.cpp" />
<ClCompile Include="qtprogresscallback.cpp" />
<ClCompile Include="qtsettingsinterface.cpp" />
@ -63,14 +64,15 @@
<QtMoc Include="gpusettingswidget.h" />
<QtMoc Include="hotkeysettingswidget.h" />
<QtMoc Include="inputbindingwidgets.h" />
<QtMoc Include="d3d11displaywidget.h" />
<ClInclude Include="d3d11hostdisplay.h" />
<QtMoc Include="qtprogresscallback.h" />
<ClInclude Include="qthostdisplay.h" />
<ClInclude Include="settingwidgetbinder.h" />
<QtMoc Include="consolesettingswidget.h" />
<QtMoc Include="gamelistsettingswidget.h" />
<QtMoc Include="gamelistwidget.h" />
<QtMoc Include="mainwindow.h" />
<QtMoc Include="opengldisplaywidget.h" />
<ClInclude Include="openglhostdisplay.h" />
<QtMoc Include="qthostinterface.h" />
<ClInclude Include="qtsettingsinterface.h" />
<ClInclude Include="qtutils.h" />
@ -127,7 +129,6 @@
<ItemGroup>
<ClCompile Include="$(IntDir)moc_audiosettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_d3d11displaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistwidget.cpp" />
<ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp" />
@ -135,7 +136,6 @@
<ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
<ClCompile Include="$(IntDir)moc_opengldisplaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_portsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_qtdisplaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_qthostinterface.cpp" />

View file

@ -27,8 +27,6 @@
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
<ClCompile Include="audiosettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_audiosettingswidget.cpp" />
<ClCompile Include="opengldisplaywidget.cpp" />
<ClCompile Include="d3d11displaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_d3d11displaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_opengldisplaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_qtdisplaywidget.cpp" />
@ -37,11 +35,15 @@
<ClCompile Include="$(IntDir)moc_qtprogresscallback.cpp" />
<ClCompile Include="generalsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp" />
<ClCompile Include="qthostdisplay.cpp" />
<ClCompile Include="openglhostdisplay.cpp" />
<ClCompile Include="d3d11hostdisplay.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="qtsettingsinterface.h" />
<ClInclude Include="qtutils.h" />
<ClInclude Include="settingwidgetbinder.h" />
<ClInclude Include="qthostdisplay.h" />
</ItemGroup>
<ItemGroup>
<Filter Include="resources">
@ -63,11 +65,11 @@
<QtMoc Include="hotkeysettingswidget.h" />
<QtMoc Include="inputbindingwidgets.h" />
<QtMoc Include="audiosettingswidget.h" />
<QtMoc Include="opengldisplaywidget.h" />
<QtMoc Include="d3d11displaywidget.h" />
<QtMoc Include="qtdisplaywidget.h" />
<QtMoc Include="generalsettingswidget.h" />
<QtMoc Include="qtprogresscallback.h" />
<QtMoc Include="openglhostdisplay.h" />
<QtMoc Include="d3d11hostdisplay.h" />
</ItemGroup>
<ItemGroup>
<QtUi Include="consolesettingswidget.ui" />

View file

@ -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

View file

@ -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,74 +95,89 @@ 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);
}
void MainWindow::destroyDisplayWindow()
if (!m_host_display->createDeviceContext(use_debug_device))
{
DebugAssert(m_display_widget);
reportError(tr("Failed to create host display device context."));
return;
}
if (m_display_widget->isFullScreen())
m_display_widget->showNormal();
if (m_display_widget->parent())
if (!m_host_display->createSurface() || !m_host_display->makeDeviceContextCurrent())
{
m_ui.mainContainer->removeWidget(m_display_widget);
switchToGameListView();
reportError(tr("Failed to create host display surface."));
m_host_display->destroyDeviceContext();
return;
}
// recreate the display widget using the potentially-new renderer
delete m_display_widget;
m_display_widget = nullptr;
m_host_display->moveContextToThread(worker_thread);
}
void MainWindow::updateDisplayWindow(bool fullscreen, bool render_to_main)
void MainWindow::updateDisplay(QThread* worker_thread, 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)
{
m_host_display->moveContextToThread(worker_thread);
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();
}
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
else if (!render_to_main)
{
// 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);
switchToEmulationView();
}
m_display_widget->setCursor(QCursor());
}
// 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->windowResizedEvent(m_display_widget->scaledWindowWidth(), m_display_widget->scaledWindowHeight());
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())
{
m_ui.mainContainer->removeWidget(m_display_widget);
switchToGameListView();
}
m_host_display->destroyWidget();
m_display_widget = nullptr;
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);

View file

@ -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;

View file

@ -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 <QtGui/QKeyEvent>
#include <QtGui/QWindow>
#include <array>
#include <imgui_impl_opengl3.h>
#include <tuple>
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,59 +98,43 @@ 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<QWidget*>(static_cast<const QWidget*>(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<HostDisplayTexture> OpenGLDisplayWidget::CreateTexture(u32 width, u32 height, const void* initial_data,
std::unique_ptr<HostDisplayTexture> 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,
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<OpenGLDisplayWidgetTexture*>(texture);
@ -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, &current_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<bool>(m_gl_context);
}
bool OpenGLDisplayWidget::createDeviceContext(QThread* worker_thread, bool debug_device)
bool OpenGLHostDisplay::createDeviceContext(bool debug_device)
{
m_gl_context = std::make_unique<QOpenGLContext>();
@ -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);

View file

@ -12,33 +12,32 @@
#include "common/gl/texture.h"
#include "core/host_display.h"
#include "qtdisplaywidget.h"
#include "qthostdisplay.h"
#include <QtGui/QOpenGLContext>
#include <memory>
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<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* initial_data,
u32 initial_data_stride, bool dynamic) override;

View file

@ -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 <QtGui/QGuiApplication>
@ -10,47 +9,18 @@
#include <QtGui/QWindowStateChangeEvent>
#include <cmath>
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<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioFromScreen()));
}
bool QtDisplayWidget::createImGuiContext()
{
ImGui::CreateContext();
auto& io = ImGui::GetIO();
io.IniFilename = nullptr;
io.DisplaySize.x = static_cast<float>(scaledWindowWidth());
io.DisplaySize.y = static_cast<float>(scaledWindowHeight());
const float framebuffer_scale = static_cast<float>(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<float>(new_window_width);
io.DisplaySize.y = static_cast<float>(new_window_height);
}
QPaintEngine* QtDisplayWidget::paintEngine() const
{
return nullptr;
@ -132,7 +59,7 @@ bool QtDisplayWidget::event(QEvent* event)
{
QKeyEvent* key_event = static_cast<QKeyEvent*>(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;
}

View file

@ -2,49 +2,26 @@
#include "common/types.h"
#include <QtWidgets/QWidget>
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;
};

View file

@ -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 <cmath>
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<float>(m_window_width);
io.DisplaySize.y = static_cast<float>(m_window_height);
const float framebuffer_scale = static_cast<float>(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<float>(m_window_width);
io.DisplaySize.y = static_cast<float>(m_window_height);
}

View file

@ -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;
};

View file

@ -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<QtHostDisplay*>(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,
emit createDisplayRequested(m_worker_thread, m_settings.gpu_use_debug_device, m_is_fullscreen,
m_is_rendering_to_main);
if (!m_display_widget->hasDeviceContext())
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;
}

View file

@ -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<QSettings> 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;