forked from eden-emu/eden
		
	frontend: qt: bootmanager: OpenGL: Implement separate presentation thread.
This commit is contained in:
		
							parent
							
								
									b2a38cce4e
								
							
						
					
					
						commit
						14877b8f35
					
				
					 2 changed files with 262 additions and 230 deletions
				
			
		|  | @ -9,6 +9,9 @@ | |||
| #include <QKeyEvent> | ||||
| #include <QMessageBox> | ||||
| #include <QOffscreenSurface> | ||||
| #include <QOpenGLContext> | ||||
| #include <QOpenGLFunctions> | ||||
| #include <QOpenGLFunctions_4_3_Core> | ||||
| #include <QOpenGLWindow> | ||||
| #include <QPainter> | ||||
| #include <QScreen> | ||||
|  | @ -23,6 +26,7 @@ | |||
| #include "common/assert.h" | ||||
| #include "common/microprofile.h" | ||||
| #include "common/scm_rev.h" | ||||
| #include "common/scope_exit.h" | ||||
| #include "core/core.h" | ||||
| #include "core/frontend/framebuffer_layout.h" | ||||
| #include "core/frontend/scope_acquire_context.h" | ||||
|  | @ -35,15 +39,32 @@ | |||
| #include "yuzu/bootmanager.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; | ||||
| 
 | ||||
| void EmuThread::run() { | ||||
|     render_window->MakeCurrent(); | ||||
| static GMainWindow* GetMainWindow() { | ||||
|     for (QWidget* w : qApp->topLevelWidgets()) { | ||||
|         if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) { | ||||
|             return main; | ||||
|         } | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| void EmuThread::run() { | ||||
|     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); | ||||
| 
 | ||||
|     Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( | ||||
|  | @ -53,11 +74,6 @@ void EmuThread::run() { | |||
| 
 | ||||
|     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,
 | ||||
|     // so that the DebugModeLeft signal can be emitted before the
 | ||||
|     // next execution step
 | ||||
|  | @ -98,190 +114,157 @@ void EmuThread::run() { | |||
| #if MICROPROFILE_ENABLED | ||||
|     MicroProfileOnThreadExit(); | ||||
| #endif | ||||
| 
 | ||||
|     render_window->moveContext(); | ||||
| } | ||||
| 
 | ||||
| class GGLContext : public Core::Frontend::GraphicsContext { | ||||
| public: | ||||
|     explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} { | ||||
|         context.setFormat(shared_context->format()); | ||||
|         context.setShareContext(shared_context); | ||||
|         context.create(); | ||||
|     explicit GGLContext(QOpenGLContext* shared_context) | ||||
|         : context(new QOpenGLContext(shared_context->parent())), | ||||
|           surface(new QOffscreenSurface(nullptr)) { | ||||
| 
 | ||||
|         // 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 { | ||||
|         context.makeCurrent(shared_context->surface()); | ||||
|         context->makeCurrent(surface); | ||||
|     } | ||||
| 
 | ||||
|     void DoneCurrent() override { | ||||
|         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; | ||||
|         context->doneCurrent(); | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     GRenderWindow* parent; | ||||
|     bool do_painting = false; | ||||
|     QOpenGLContext* context; | ||||
|     QOffscreenSurface* surface; | ||||
| }; | ||||
| 
 | ||||
| // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
 | ||||
| // context.
 | ||||
| // The corresponding functionality is handled in EmuThread instead
 | ||||
| class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow { | ||||
| public: | ||||
|     GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) | ||||
|         : GWidgetInternal(parent), QOpenGLWindow(shared_context) {} | ||||
|     ~GGLWidgetInternal() override = default; | ||||
| OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) | ||||
|     : QWindow(parent), event_handler(event_handler), | ||||
|       context(new QOpenGLContext(shared_context->parent())) { | ||||
| 
 | ||||
|     void paintEvent(QPaintEvent* ev) override { | ||||
|         if (IsPaintingEnabled()) { | ||||
|             QPainter painter(this); | ||||
|         } | ||||
|     // disable vsync for any shared contexts
 | ||||
|     auto format = shared_context->format(); | ||||
|     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 | ||||
| class GVKWidgetInternal final : public GWidgetInternal { | ||||
| public: | ||||
|     GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) { | ||||
|         setSurfaceType(QSurface::SurfaceType::VulkanSurface); | ||||
|         setVulkanInstance(instance); | ||||
|     } | ||||
|     ~GVKWidgetInternal() override = default; | ||||
| }; | ||||
| #endif | ||||
| void OpenGLWindow::exposeEvent(QExposeEvent* event) { | ||||
|     QWindow::requestUpdate(); | ||||
|     QWindow::exposeEvent(event); | ||||
| } | ||||
| 
 | ||||
| GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) | ||||
|     : QWidget(parent), emu_thread(emu_thread) { | ||||
| GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread) | ||||
|     : QWidget(parent_), emu_thread(emu_thread) { | ||||
|     setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") | ||||
|                        .arg(QString::fromUtf8(Common::g_build_name), | ||||
|                             QString::fromUtf8(Common::g_scm_branch), | ||||
|                             QString::fromUtf8(Common::g_scm_desc))); | ||||
|     setAttribute(Qt::WA_AcceptTouchEvents); | ||||
| 
 | ||||
|     auto layout = new QHBoxLayout(this); | ||||
|     layout->setMargin(0); | ||||
|     setLayout(layout); | ||||
|     InputCommon::Init(); | ||||
| 
 | ||||
|     GMainWindow* parent = GetMainWindow(); | ||||
|     connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); | ||||
| } | ||||
| 
 | ||||
| GRenderWindow::~GRenderWindow() { | ||||
|     InputCommon::Shutdown(); | ||||
| 
 | ||||
|     // Avoid an unordered destruction that generates a segfault
 | ||||
|     delete child; | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::moveContext() { | ||||
|     if (!context) { | ||||
|         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::MakeCurrent() { | ||||
|     core_context->MakeCurrent(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::SwapBuffers() { | ||||
|     if (context) { | ||||
|         context->swapBuffers(child); | ||||
|     } | ||||
| void GRenderWindow::DoneCurrent() { | ||||
|     core_context->DoneCurrent(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::PollEvents() { | ||||
|     if (!first_frame) { | ||||
|         first_frame = true; | ||||
|         emit FirstFrameDisplayed(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::MakeCurrent() { | ||||
|     if (context) { | ||||
|         context->makeCurrent(child); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::DoneCurrent() { | ||||
|     if (context) { | ||||
|         context->doneCurrent(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::PollEvents() {} | ||||
| 
 | ||||
| bool GRenderWindow::IsShown() const { | ||||
|     return !isMinimized(); | ||||
| } | ||||
|  | @ -309,21 +292,10 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i | |||
| void GRenderWindow::OnFramebufferSizeChanged() { | ||||
|     // Screen changes potentially incur a change in screen DPI, hence we should update the
 | ||||
|     // framebuffer size
 | ||||
|     const qreal pixelRatio{GetWindowPixelRatio()}; | ||||
|     const auto size{child->GetSize()}; | ||||
|     UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { | ||||
|     if (child) { | ||||
|         child->keyPressEvent(event); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) { | ||||
|     if (child) { | ||||
|         child->keyReleaseEvent(event); | ||||
|     } | ||||
|     const qreal pixel_ratio = windowPixelRatio(); | ||||
|     const u32 width = this->width() * pixel_ratio; | ||||
|     const u32 height = this->height() * pixel_ratio; | ||||
|     UpdateCurrentFramebufferLayout(width, height); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::BackupGeometry() { | ||||
|  | @ -351,13 +323,12 @@ QByteArray GRenderWindow::saveGeometry() { | |||
|     return geometry; | ||||
| } | ||||
| 
 | ||||
| qreal GRenderWindow::GetWindowPixelRatio() const { | ||||
|     // windowHandle() might not be accessible until the window is displayed to screen.
 | ||||
|     return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; | ||||
| qreal GRenderWindow::windowPixelRatio() const { | ||||
|     return devicePixelRatio(); | ||||
| } | ||||
| 
 | ||||
| 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})), | ||||
|             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); | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
|     // TouchBegin always has exactly one touch point, so take the .first()
 | ||||
|     const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); | ||||
|  | @ -415,26 +427,20 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { | |||
|     InputCommon::GetKeyboard()->ReleaseAllKeys(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::OnClientAreaResized(u32 width, u32 height) { | ||||
|     NotifyClientAreaSizeChanged(std::make_pair(width, height)); | ||||
| void GRenderWindow::resizeEvent(QResizeEvent* event) { | ||||
|     QWidget::resizeEvent(event); | ||||
|     OnFramebufferSizeChanged(); | ||||
| } | ||||
| 
 | ||||
| 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() { | ||||
|     shared_context.reset(); | ||||
|     context.reset(); | ||||
|     if (child) { | ||||
|         delete child; | ||||
|     } | ||||
|     if (container) { | ||||
|         delete container; | ||||
|     } | ||||
|     if (layout()) { | ||||
|         delete layout(); | ||||
|     } | ||||
|     ReleaseRenderTarget(); | ||||
| 
 | ||||
|     first_frame = false; | ||||
| 
 | ||||
|  | @ -451,13 +457,6 @@ bool GRenderWindow::InitRenderTarget() { | |||
|         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.
 | ||||
|     setMinimumSize(1, 1); | ||||
| 
 | ||||
|  | @ -467,14 +466,9 @@ bool GRenderWindow::InitRenderTarget() { | |||
|     hide(); | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
|     OnFramebufferSizeChanged(); | ||||
|     NotifyClientAreaSizeChanged(child->GetSize()); | ||||
| 
 | ||||
|     BackupGeometry(); | ||||
| 
 | ||||
|     if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { | ||||
|  | @ -486,6 +480,14 @@ bool GRenderWindow::InitRenderTarget() { | |||
|     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) { | ||||
|     auto& renderer = Core::System::GetInstance().Renderer(); | ||||
| 
 | ||||
|  | @ -521,16 +523,20 @@ bool GRenderWindow::InitializeOpenGL() { | |||
|     fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); | ||||
|     // TODO: expose a setting for buffer value (ie default/single/double/triple)
 | ||||
|     fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); | ||||
|     shared_context = std::make_unique<QOpenGLContext>(); | ||||
|     shared_context->setFormat(fmt); | ||||
|     shared_context->create(); | ||||
|     context = std::make_unique<QOpenGLContext>(); | ||||
|     context->setShareContext(shared_context.get()); | ||||
|     context->setFormat(fmt); | ||||
|     context->create(); | ||||
|     fmt.setSwapInterval(false); | ||||
|     fmt.setSwapInterval(0); | ||||
|     QSurfaceFormat::setDefaultFormat(fmt); | ||||
| 
 | ||||
|     GMainWindow* parent = GetMainWindow(); | ||||
|     QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr; | ||||
|     child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext()); | ||||
|     child_window->create(); | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
|  | @ -621,12 +627,10 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { | |||
| 
 | ||||
| void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { | ||||
|     this->emu_thread = emu_thread; | ||||
|     child->DisablePainting(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::OnEmulationStopping() { | ||||
|     emu_thread = nullptr; | ||||
|     child->EnablePainting(); | ||||
| } | ||||
| 
 | ||||
| void GRenderWindow::showEvent(QShowEvent* event) { | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
| #include <QImage> | ||||
| #include <QThread> | ||||
| #include <QWidget> | ||||
| #include <QWindow> | ||||
| 
 | ||||
| #include "common/thread.h" | ||||
| #include "core/core.h" | ||||
|  | @ -42,7 +43,7 @@ class EmuThread final : public QThread { | |||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit EmuThread(GRenderWindow* render_window); | ||||
|     explicit EmuThread(Core::Frontend::GraphicsContext& context); | ||||
|     ~EmuThread() override; | ||||
| 
 | ||||
|     /**
 | ||||
|  | @ -96,7 +97,7 @@ private: | |||
|     std::mutex running_mutex; | ||||
|     std::condition_variable running_cv; | ||||
| 
 | ||||
|     GRenderWindow* render_window; | ||||
|     Core::Frontend::GraphicsContext& core_context; | ||||
| 
 | ||||
| signals: | ||||
|     /**
 | ||||
|  | @ -122,15 +123,32 @@ signals: | |||
|     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 { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     GRenderWindow(GMainWindow* parent, EmuThread* emu_thread); | ||||
|     GRenderWindow(QWidget* parent, EmuThread* emu_thread); | ||||
|     ~GRenderWindow() override; | ||||
| 
 | ||||
|     // EmuWindow implementation
 | ||||
|     void SwapBuffers() override; | ||||
|     // EmuWindow implementation.
 | ||||
|     void MakeCurrent() override; | ||||
|     void DoneCurrent() override; | ||||
|     void PollEvents() override; | ||||
|  | @ -139,30 +157,36 @@ public: | |||
|                                 void* surface) const override; | ||||
|     std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; | ||||
| 
 | ||||
|     void ForwardKeyPressEvent(QKeyEvent* event); | ||||
|     void ForwardKeyReleaseEvent(QKeyEvent* event); | ||||
| 
 | ||||
|     void BackupGeometry(); | ||||
|     void RestoreGeometry(); | ||||
|     void restoreGeometry(const QByteArray& geometry); // overridden
 | ||||
|     QByteArray saveGeometry();                        // overridden
 | ||||
| 
 | ||||
|     qreal GetWindowPixelRatio() const; | ||||
|     std::pair<u32, u32> ScaleTouch(QPointF pos) const; | ||||
|     qreal windowPixelRatio() const; | ||||
| 
 | ||||
|     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; | ||||
| 
 | ||||
|     void focusOutEvent(QFocusEvent* event) override; | ||||
| 
 | ||||
|     void OnClientAreaResized(u32 width, u32 height); | ||||
| 
 | ||||
|     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); | ||||
| 
 | ||||
| public slots: | ||||
|     void moveContext(); // overridden
 | ||||
| 
 | ||||
|     void OnEmulationStarting(EmuThread* emu_thread); | ||||
|     void OnEmulationStopping(); | ||||
|     void OnFramebufferSizeChanged(); | ||||
|  | @ -173,6 +197,7 @@ signals: | |||
|     void FirstFrameDisplayed(); | ||||
| 
 | ||||
| private: | ||||
|     std::pair<u32, u32> ScaleTouch(QPointF pos) const; | ||||
|     void TouchBeginEvent(const QTouchEvent* event); | ||||
|     void TouchUpdateEvent(const QTouchEvent* event); | ||||
|     void TouchEndEvent(); | ||||
|  | @ -184,15 +209,9 @@ private: | |||
|     bool LoadOpenGL(); | ||||
|     QStringList GetUnsupportedGLExtensions() const; | ||||
| 
 | ||||
|     QWidget* container = nullptr; | ||||
|     GWidgetInternal* child = nullptr; | ||||
| 
 | ||||
|     EmuThread* emu_thread; | ||||
|     // Context that backs the GGLWidgetInternal (and will be used by core to render)
 | ||||
|     std::unique_ptr<QOpenGLContext> context; | ||||
|     // Context that will be shared between all newly created contexts. This should never be made
 | ||||
|     // current
 | ||||
|     std::unique_ptr<QOpenGLContext> shared_context; | ||||
| 
 | ||||
|     std::unique_ptr<GraphicsContext> core_context; | ||||
| 
 | ||||
| #ifdef HAS_VULKAN | ||||
|     std::unique_ptr<QVulkanInstance> vk_instance; | ||||
|  | @ -202,6 +221,15 @@ private: | |||
|     QImage screenshot_image; | ||||
| 
 | ||||
|     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; | ||||
| 
 | ||||
| protected: | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bunnei
						bunnei