frontend: qt: bootmanager: OpenGL: Implement separate presentation thread.

This commit is contained in:
bunnei 2020-02-17 15:53:21 -05:00
parent b2a38cce4e
commit 14877b8f35
2 changed files with 262 additions and 230 deletions

View file

@ -9,6 +9,9 @@
#include <QKeyEvent> #include <QKeyEvent>
#include <QMessageBox> #include <QMessageBox>
#include <QOffscreenSurface> #include <QOffscreenSurface>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLFunctions_4_3_Core>
#include <QOpenGLWindow> #include <QOpenGLWindow>
#include <QPainter> #include <QPainter>
#include <QScreen> #include <QScreen>
@ -23,6 +26,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "core/core.h" #include "core/core.h"
#include "core/frontend/framebuffer_layout.h" #include "core/frontend/framebuffer_layout.h"
#include "core/frontend/scope_acquire_context.h" #include "core/frontend/scope_acquire_context.h"
@ -35,15 +39,32 @@
#include "yuzu/bootmanager.h" #include "yuzu/bootmanager.h"
#include "yuzu/main.h" #include "yuzu/main.h"
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} EmuThread::EmuThread(Core::Frontend::GraphicsContext& core_context) : core_context(core_context) {}
EmuThread::~EmuThread() = default; EmuThread::~EmuThread() = default;
void EmuThread::run() { static GMainWindow* GetMainWindow() {
render_window->MakeCurrent(); for (QWidget* w : qApp->topLevelWidgets()) {
if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) {
return main;
}
}
return nullptr;
}
void EmuThread::run() {
MicroProfileOnThreadCreate("EmuThread"); MicroProfileOnThreadCreate("EmuThread");
// Acquire render context for duration of the thread if this is the rendering thread
if (!Settings::values.use_asynchronous_gpu_emulation) {
core_context.MakeCurrent();
}
SCOPE_EXIT({
if (!Settings::values.use_asynchronous_gpu_emulation) {
core_context.DoneCurrent();
}
});
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
@ -53,11 +74,6 @@ void EmuThread::run() {
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
if (Settings::values.use_asynchronous_gpu_emulation) {
// Release OpenGL context for the GPU thread
render_window->DoneCurrent();
}
// Holds whether the cpu was running during the last iteration, // Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the // so that the DebugModeLeft signal can be emitted before the
// next execution step // next execution step
@ -98,190 +114,157 @@ void EmuThread::run() {
#if MICROPROFILE_ENABLED #if MICROPROFILE_ENABLED
MicroProfileOnThreadExit(); MicroProfileOnThreadExit();
#endif #endif
render_window->moveContext();
} }
class GGLContext : public Core::Frontend::GraphicsContext { class GGLContext : public Core::Frontend::GraphicsContext {
public: public:
explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} { explicit GGLContext(QOpenGLContext* shared_context)
context.setFormat(shared_context->format()); : context(new QOpenGLContext(shared_context->parent())),
context.setShareContext(shared_context); surface(new QOffscreenSurface(nullptr)) {
context.create();
// disable vsync for any shared contexts
auto format = shared_context->format();
format.setSwapInterval(0);
context->setShareContext(shared_context);
context->setFormat(format);
context->create();
surface->setParent(shared_context->parent());
surface->setFormat(format);
surface->create();
} }
void MakeCurrent() override { void MakeCurrent() override {
context.makeCurrent(shared_context->surface()); context->makeCurrent(surface);
} }
void DoneCurrent() override { void DoneCurrent() override {
context.doneCurrent(); context->doneCurrent();
}
void SwapBuffers() override {}
private:
QOpenGLContext* shared_context;
QOpenGLContext context;
};
class GWidgetInternal : public QWindow {
public:
GWidgetInternal(GRenderWindow* parent) : parent(parent) {}
virtual ~GWidgetInternal() = default;
void resizeEvent(QResizeEvent* ev) override {
parent->OnClientAreaResized(ev->size().width(), ev->size().height());
parent->OnFramebufferSizeChanged();
}
void keyPressEvent(QKeyEvent* event) override {
InputCommon::GetKeyboard()->PressKey(event->key());
}
void keyReleaseEvent(QKeyEvent* event) override {
InputCommon::GetKeyboard()->ReleaseKey(event->key());
}
void mousePressEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchBeginEvent
const auto pos{event->pos()};
if (event->button() == Qt::LeftButton) {
const auto [x, y] = parent->ScaleTouch(pos);
parent->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
}
}
void mouseMoveEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchUpdateEvent
const auto pos{event->pos()};
const auto [x, y] = parent->ScaleTouch(pos);
parent->TouchMoved(x, y);
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
}
void mouseReleaseEvent(QMouseEvent* event) override {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchEndEvent
if (event->button() == Qt::LeftButton)
parent->TouchReleased();
else if (event->button() == Qt::RightButton)
InputCommon::GetMotionEmu()->EndTilt();
}
void DisablePainting() {
do_painting = false;
}
void EnablePainting() {
do_painting = true;
}
std::pair<unsigned, unsigned> GetSize() const {
return std::make_pair(width(), height());
}
protected:
bool IsPaintingEnabled() const {
return do_painting;
} }
private: private:
GRenderWindow* parent; QOpenGLContext* context;
bool do_painting = false; QOffscreenSurface* surface;
}; };
// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
// context. : QWindow(parent), event_handler(event_handler),
// The corresponding functionality is handled in EmuThread instead context(new QOpenGLContext(shared_context->parent())) {
class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow {
public:
GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
: GWidgetInternal(parent), QOpenGLWindow(shared_context) {}
~GGLWidgetInternal() override = default;
void paintEvent(QPaintEvent* ev) override { // disable vsync for any shared contexts
if (IsPaintingEnabled()) { auto format = shared_context->format();
QPainter painter(this); format.setSwapInterval(Settings::values.use_vsync ? 1 : 0);
} this->setFormat(format);
context->setShareContext(shared_context);
context->setScreen(this->screen());
context->setFormat(format);
context->create();
setSurfaceType(QWindow::OpenGLSurface);
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
}
OpenGLWindow::~OpenGLWindow() {
context->doneCurrent();
}
void OpenGLWindow::Present() {
if (!isExposed())
return;
context->makeCurrent(this);
Core::System::GetInstance().Renderer().TryPresent(100);
context->swapBuffers(this);
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
f->glFinish();
QWindow::requestUpdate();
}
bool OpenGLWindow::event(QEvent* event) {
switch (event->type()) {
case QEvent::UpdateRequest:
Present();
return true;
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::FocusIn:
case QEvent::FocusOut:
case QEvent::FocusAboutToChange:
case QEvent::Enter:
case QEvent::Leave:
case QEvent::Wheel:
case QEvent::TabletMove:
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletEnterProximity:
case QEvent::TabletLeaveProximity:
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd:
case QEvent::InputMethodQuery:
case QEvent::TouchCancel:
return QCoreApplication::sendEvent(event_handler, event);
case QEvent::Drop:
GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
return true;
case QEvent::DragResponse:
case QEvent::DragEnter:
case QEvent::DragLeave:
case QEvent::DragMove:
GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
return true;
default:
return QWindow::event(event);
} }
}; }
#ifdef HAS_VULKAN void OpenGLWindow::exposeEvent(QExposeEvent* event) {
class GVKWidgetInternal final : public GWidgetInternal { QWindow::requestUpdate();
public: QWindow::exposeEvent(event);
GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) { }
setSurfaceType(QSurface::SurfaceType::VulkanSurface);
setVulkanInstance(instance);
}
~GVKWidgetInternal() override = default;
};
#endif
GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
: QWidget(parent), emu_thread(emu_thread) { : QWidget(parent_), emu_thread(emu_thread) {
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name), .arg(QString::fromUtf8(Common::g_build_name),
QString::fromUtf8(Common::g_scm_branch), QString::fromUtf8(Common::g_scm_branch),
QString::fromUtf8(Common::g_scm_desc))); QString::fromUtf8(Common::g_scm_desc)));
setAttribute(Qt::WA_AcceptTouchEvents); setAttribute(Qt::WA_AcceptTouchEvents);
auto layout = new QHBoxLayout(this);
layout->setMargin(0);
setLayout(layout);
InputCommon::Init(); InputCommon::Init();
GMainWindow* parent = GetMainWindow();
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
} }
GRenderWindow::~GRenderWindow() { GRenderWindow::~GRenderWindow() {
InputCommon::Shutdown(); InputCommon::Shutdown();
// Avoid an unordered destruction that generates a segfault
delete child;
} }
void GRenderWindow::moveContext() { void GRenderWindow::MakeCurrent() {
if (!context) { core_context->MakeCurrent();
return;
}
DoneCurrent();
// If the thread started running, move the GL Context to the new thread. Otherwise, move it
// back.
auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
? emu_thread
: qApp->thread();
context->moveToThread(thread);
} }
void GRenderWindow::SwapBuffers() { void GRenderWindow::DoneCurrent() {
if (context) { core_context->DoneCurrent();
context->swapBuffers(child); }
}
void GRenderWindow::PollEvents() {
if (!first_frame) { if (!first_frame) {
first_frame = true; first_frame = true;
emit FirstFrameDisplayed(); emit FirstFrameDisplayed();
} }
} }
void GRenderWindow::MakeCurrent() {
if (context) {
context->makeCurrent(child);
}
}
void GRenderWindow::DoneCurrent() {
if (context) {
context->doneCurrent();
}
}
void GRenderWindow::PollEvents() {}
bool GRenderWindow::IsShown() const { bool GRenderWindow::IsShown() const {
return !isMinimized(); return !isMinimized();
} }
@ -309,21 +292,10 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i
void GRenderWindow::OnFramebufferSizeChanged() { void GRenderWindow::OnFramebufferSizeChanged() {
// Screen changes potentially incur a change in screen DPI, hence we should update the // Screen changes potentially incur a change in screen DPI, hence we should update the
// framebuffer size // framebuffer size
const qreal pixelRatio{GetWindowPixelRatio()}; const qreal pixel_ratio = windowPixelRatio();
const auto size{child->GetSize()}; const u32 width = this->width() * pixel_ratio;
UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio); const u32 height = this->height() * pixel_ratio;
} UpdateCurrentFramebufferLayout(width, height);
void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
if (child) {
child->keyPressEvent(event);
}
}
void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
if (child) {
child->keyReleaseEvent(event);
}
} }
void GRenderWindow::BackupGeometry() { void GRenderWindow::BackupGeometry() {
@ -351,13 +323,12 @@ QByteArray GRenderWindow::saveGeometry() {
return geometry; return geometry;
} }
qreal GRenderWindow::GetWindowPixelRatio() const { qreal GRenderWindow::windowPixelRatio() const {
// windowHandle() might not be accessible until the window is displayed to screen. return devicePixelRatio();
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
} }
std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
const qreal pixel_ratio{GetWindowPixelRatio()}; const qreal pixel_ratio = windowPixelRatio();
return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
} }
@ -367,6 +338,47 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event); QWidget::closeEvent(event);
} }
void GRenderWindow::keyPressEvent(QKeyEvent* event) {
InputCommon::GetKeyboard()->PressKey(event->key());
}
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
InputCommon::GetKeyboard()->ReleaseKey(event->key());
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchBeginEvent
auto pos = event->pos();
if (event->button() == Qt::LeftButton) {
const auto [x, y] = ScaleTouch(pos);
this->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
}
}
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchUpdateEvent
auto pos = event->pos();
const auto [x, y] = ScaleTouch(pos);
this->TouchMoved(x, y);
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
}
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
if (event->source() == Qt::MouseEventSynthesizedBySystem)
return; // touch input is handled in TouchEndEvent
if (event->button() == Qt::LeftButton)
this->TouchReleased();
else if (event->button() == Qt::RightButton)
InputCommon::GetMotionEmu()->EndTilt();
}
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
// TouchBegin always has exactly one touch point, so take the .first() // TouchBegin always has exactly one touch point, so take the .first()
const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
@ -415,26 +427,20 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {
InputCommon::GetKeyboard()->ReleaseAllKeys(); InputCommon::GetKeyboard()->ReleaseAllKeys();
} }
void GRenderWindow::OnClientAreaResized(u32 width, u32 height) { void GRenderWindow::resizeEvent(QResizeEvent* event) {
NotifyClientAreaSizeChanged(std::make_pair(width, height)); QWidget::resizeEvent(event);
OnFramebufferSizeChanged();
} }
std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
return std::make_unique<GGLContext>(context.get()); if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
return std::make_unique<GGLContext>(QOpenGLContext::globalShareContext());
}
return {};
} }
bool GRenderWindow::InitRenderTarget() { bool GRenderWindow::InitRenderTarget() {
shared_context.reset(); ReleaseRenderTarget();
context.reset();
if (child) {
delete child;
}
if (container) {
delete container;
}
if (layout()) {
delete layout();
}
first_frame = false; first_frame = false;
@ -451,13 +457,6 @@ bool GRenderWindow::InitRenderTarget() {
break; break;
} }
container = QWidget::createWindowContainer(child, this);
QBoxLayout* layout = new QHBoxLayout(this);
layout->addWidget(container);
layout->setMargin(0);
setLayout(layout);
// Reset minimum required size to avoid resizing issues on the main window after restarting. // Reset minimum required size to avoid resizing issues on the main window after restarting.
setMinimumSize(1, 1); setMinimumSize(1, 1);
@ -467,14 +466,9 @@ bool GRenderWindow::InitRenderTarget() {
hide(); hide();
resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged(); OnFramebufferSizeChanged();
NotifyClientAreaSizeChanged(child->GetSize());
BackupGeometry(); BackupGeometry();
if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
@ -486,6 +480,14 @@ bool GRenderWindow::InitRenderTarget() {
return true; return true;
} }
void GRenderWindow::ReleaseRenderTarget() {
if (child_widget) {
layout()->removeWidget(child_widget);
delete child_widget;
child_widget = nullptr;
}
}
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
auto& renderer = Core::System::GetInstance().Renderer(); auto& renderer = Core::System::GetInstance().Renderer();
@ -521,16 +523,20 @@ bool GRenderWindow::InitializeOpenGL() {
fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
// TODO: expose a setting for buffer value (ie default/single/double/triple) // TODO: expose a setting for buffer value (ie default/single/double/triple)
fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
shared_context = std::make_unique<QOpenGLContext>(); fmt.setSwapInterval(0);
shared_context->setFormat(fmt); QSurfaceFormat::setDefaultFormat(fmt);
shared_context->create();
context = std::make_unique<QOpenGLContext>(); GMainWindow* parent = GetMainWindow();
context->setShareContext(shared_context.get()); QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
context->setFormat(fmt); child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
context->create(); child_window->create();
fmt.setSwapInterval(false); child_widget = createWindowContainer(child_window, this);
child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
layout()->addWidget(child_widget);
core_context = CreateSharedContext();
resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
child = new GGLWidgetInternal(this, shared_context.get());
return true; return true;
} }
@ -621,12 +627,10 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
this->emu_thread = emu_thread; this->emu_thread = emu_thread;
child->DisablePainting();
} }
void GRenderWindow::OnEmulationStopping() { void GRenderWindow::OnEmulationStopping() {
emu_thread = nullptr; emu_thread = nullptr;
child->EnablePainting();
} }
void GRenderWindow::showEvent(QShowEvent* event) { void GRenderWindow::showEvent(QShowEvent* event) {

View file

@ -11,6 +11,7 @@
#include <QImage> #include <QImage>
#include <QThread> #include <QThread>
#include <QWidget> #include <QWidget>
#include <QWindow>
#include "common/thread.h" #include "common/thread.h"
#include "core/core.h" #include "core/core.h"
@ -42,7 +43,7 @@ class EmuThread final : public QThread {
Q_OBJECT Q_OBJECT
public: public:
explicit EmuThread(GRenderWindow* render_window); explicit EmuThread(Core::Frontend::GraphicsContext& context);
~EmuThread() override; ~EmuThread() override;
/** /**
@ -96,7 +97,7 @@ private:
std::mutex running_mutex; std::mutex running_mutex;
std::condition_variable running_cv; std::condition_variable running_cv;
GRenderWindow* render_window; Core::Frontend::GraphicsContext& core_context;
signals: signals:
/** /**
@ -122,15 +123,32 @@ signals:
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
}; };
class OpenGLWindow : public QWindow {
Q_OBJECT
public:
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context);
~OpenGLWindow();
void Present();
protected:
bool event(QEvent* event) override;
void exposeEvent(QExposeEvent* event) override;
private:
QOpenGLContext* context;
QWidget* event_handler;
};
class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
Q_OBJECT Q_OBJECT
public: public:
GRenderWindow(GMainWindow* parent, EmuThread* emu_thread); GRenderWindow(QWidget* parent, EmuThread* emu_thread);
~GRenderWindow() override; ~GRenderWindow() override;
// EmuWindow implementation // EmuWindow implementation.
void SwapBuffers() override;
void MakeCurrent() override; void MakeCurrent() override;
void DoneCurrent() override; void DoneCurrent() override;
void PollEvents() override; void PollEvents() override;
@ -139,30 +157,36 @@ public:
void* surface) const override; void* surface) const override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
void ForwardKeyPressEvent(QKeyEvent* event);
void ForwardKeyReleaseEvent(QKeyEvent* event);
void BackupGeometry(); void BackupGeometry();
void RestoreGeometry(); void RestoreGeometry();
void restoreGeometry(const QByteArray& geometry); // overridden void restoreGeometry(const QByteArray& geometry); // overridden
QByteArray saveGeometry(); // overridden QByteArray saveGeometry(); // overridden
qreal GetWindowPixelRatio() const; qreal windowPixelRatio() const;
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
void closeEvent(QCloseEvent* event) override; void closeEvent(QCloseEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
bool event(QEvent* event) override; bool event(QEvent* event) override;
void focusOutEvent(QFocusEvent* event) override; void focusOutEvent(QFocusEvent* event) override;
void OnClientAreaResized(u32 width, u32 height);
bool InitRenderTarget(); bool InitRenderTarget();
/// Destroy the previous run's child_widget which should also destroy the child_window
void ReleaseRenderTarget();
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
public slots: public slots:
void moveContext(); // overridden
void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStarting(EmuThread* emu_thread);
void OnEmulationStopping(); void OnEmulationStopping();
void OnFramebufferSizeChanged(); void OnFramebufferSizeChanged();
@ -173,6 +197,7 @@ signals:
void FirstFrameDisplayed(); void FirstFrameDisplayed();
private: private:
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
void TouchBeginEvent(const QTouchEvent* event); void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event); void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent(); void TouchEndEvent();
@ -184,15 +209,9 @@ private:
bool LoadOpenGL(); bool LoadOpenGL();
QStringList GetUnsupportedGLExtensions() const; QStringList GetUnsupportedGLExtensions() const;
QWidget* container = nullptr;
GWidgetInternal* child = nullptr;
EmuThread* emu_thread; EmuThread* emu_thread;
// Context that backs the GGLWidgetInternal (and will be used by core to render)
std::unique_ptr<QOpenGLContext> context; std::unique_ptr<GraphicsContext> core_context;
// Context that will be shared between all newly created contexts. This should never be made
// current
std::unique_ptr<QOpenGLContext> shared_context;
#ifdef HAS_VULKAN #ifdef HAS_VULKAN
std::unique_ptr<QVulkanInstance> vk_instance; std::unique_ptr<QVulkanInstance> vk_instance;
@ -202,6 +221,15 @@ private:
QImage screenshot_image; QImage screenshot_image;
QByteArray geometry; QByteArray geometry;
/// Native window handle that backs this presentation widget
QWindow* child_window = nullptr;
/// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
/// put the child_window into a widget then add it to the layout. This child_widget can be
/// parented to GRenderWindow and use Qt's lifetime system
QWidget* child_widget = nullptr;
bool first_frame = false; bool first_frame = false;
protected: protected: