diff options
| author | 2020-02-17 15:53:21 -0500 | |
|---|---|---|
| committer | 2020-02-25 21:23:01 -0500 | |
| commit | 14877b8f3539c6bd2adf66f81cfb2cd052862b73 (patch) | |
| tree | 4353f89faf685cc77449e953b88c467fd0ce45e2 /src | |
| parent | frontent: qt: main: Various updates/refactoring for separate presentation thr... (diff) | |
| download | yuzu-14877b8f3539c6bd2adf66f81cfb2cd052862b73.tar.gz yuzu-14877b8f3539c6bd2adf66f81cfb2cd052862b73.tar.xz yuzu-14877b8f3539c6bd2adf66f81cfb2cd052862b73.zip | |
frontend: qt: bootmanager: OpenGL: Implement separate presentation thread.
Diffstat (limited to 'src')
| -rw-r--r-- | src/yuzu/bootmanager.cpp | 404 | ||||
| -rw-r--r-- | src/yuzu/bootmanager.h | 72 |
2 files changed, 254 insertions, 222 deletions
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 4982884f5..99e0ac61e 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp | |||
| @@ -9,6 +9,9 @@ | |||
| 9 | #include <QKeyEvent> | 9 | #include <QKeyEvent> |
| 10 | #include <QMessageBox> | 10 | #include <QMessageBox> |
| 11 | #include <QOffscreenSurface> | 11 | #include <QOffscreenSurface> |
| 12 | #include <QOpenGLContext> | ||
| 13 | #include <QOpenGLFunctions> | ||
| 14 | #include <QOpenGLFunctions_4_3_Core> | ||
| 12 | #include <QOpenGLWindow> | 15 | #include <QOpenGLWindow> |
| 13 | #include <QPainter> | 16 | #include <QPainter> |
| 14 | #include <QScreen> | 17 | #include <QScreen> |
| @@ -23,6 +26,7 @@ | |||
| 23 | #include "common/assert.h" | 26 | #include "common/assert.h" |
| 24 | #include "common/microprofile.h" | 27 | #include "common/microprofile.h" |
| 25 | #include "common/scm_rev.h" | 28 | #include "common/scm_rev.h" |
| 29 | #include "common/scope_exit.h" | ||
| 26 | #include "core/core.h" | 30 | #include "core/core.h" |
| 27 | #include "core/frontend/framebuffer_layout.h" | 31 | #include "core/frontend/framebuffer_layout.h" |
| 28 | #include "core/frontend/scope_acquire_context.h" | 32 | #include "core/frontend/scope_acquire_context.h" |
| @@ -35,15 +39,32 @@ | |||
| 35 | #include "yuzu/bootmanager.h" | 39 | #include "yuzu/bootmanager.h" |
| 36 | #include "yuzu/main.h" | 40 | #include "yuzu/main.h" |
| 37 | 41 | ||
| 38 | EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} | 42 | EmuThread::EmuThread(Core::Frontend::GraphicsContext& core_context) : core_context(core_context) {} |
| 39 | 43 | ||
| 40 | EmuThread::~EmuThread() = default; | 44 | EmuThread::~EmuThread() = default; |
| 41 | 45 | ||
| 42 | void EmuThread::run() { | 46 | static GMainWindow* GetMainWindow() { |
| 43 | render_window->MakeCurrent(); | 47 | for (QWidget* w : qApp->topLevelWidgets()) { |
| 48 | if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) { | ||
| 49 | return main; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | return nullptr; | ||
| 53 | } | ||
| 44 | 54 | ||
| 55 | void EmuThread::run() { | ||
| 45 | MicroProfileOnThreadCreate("EmuThread"); | 56 | MicroProfileOnThreadCreate("EmuThread"); |
| 46 | 57 | ||
| 58 | // Acquire render context for duration of the thread if this is the rendering thread | ||
| 59 | if (!Settings::values.use_asynchronous_gpu_emulation) { | ||
| 60 | core_context.MakeCurrent(); | ||
| 61 | } | ||
| 62 | SCOPE_EXIT({ | ||
| 63 | if (!Settings::values.use_asynchronous_gpu_emulation) { | ||
| 64 | core_context.DoneCurrent(); | ||
| 65 | } | ||
| 66 | }); | ||
| 67 | |||
| 47 | emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); | 68 | emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); |
| 48 | 69 | ||
| 49 | Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( | 70 | Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( |
| @@ -53,11 +74,6 @@ void EmuThread::run() { | |||
| 53 | 74 | ||
| 54 | emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); | 75 | emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); |
| 55 | 76 | ||
| 56 | if (Settings::values.use_asynchronous_gpu_emulation) { | ||
| 57 | // Release OpenGL context for the GPU thread | ||
| 58 | render_window->DoneCurrent(); | ||
| 59 | } | ||
| 60 | |||
| 61 | // Holds whether the cpu was running during the last iteration, | 77 | // Holds whether the cpu was running during the last iteration, |
| 62 | // so that the DebugModeLeft signal can be emitted before the | 78 | // so that the DebugModeLeft signal can be emitted before the |
| 63 | // next execution step | 79 | // next execution step |
| @@ -98,190 +114,157 @@ void EmuThread::run() { | |||
| 98 | #if MICROPROFILE_ENABLED | 114 | #if MICROPROFILE_ENABLED |
| 99 | MicroProfileOnThreadExit(); | 115 | MicroProfileOnThreadExit(); |
| 100 | #endif | 116 | #endif |
| 101 | |||
| 102 | render_window->moveContext(); | ||
| 103 | } | 117 | } |
| 104 | 118 | ||
| 105 | class GGLContext : public Core::Frontend::GraphicsContext { | 119 | class GGLContext : public Core::Frontend::GraphicsContext { |
| 106 | public: | 120 | public: |
| 107 | explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} { | 121 | explicit GGLContext(QOpenGLContext* shared_context) |
| 108 | context.setFormat(shared_context->format()); | 122 | : context(new QOpenGLContext(shared_context->parent())), |
| 109 | context.setShareContext(shared_context); | 123 | surface(new QOffscreenSurface(nullptr)) { |
| 110 | context.create(); | 124 | |
| 125 | // disable vsync for any shared contexts | ||
| 126 | auto format = shared_context->format(); | ||
| 127 | format.setSwapInterval(0); | ||
| 128 | |||
| 129 | context->setShareContext(shared_context); | ||
| 130 | context->setFormat(format); | ||
| 131 | context->create(); | ||
| 132 | surface->setParent(shared_context->parent()); | ||
| 133 | surface->setFormat(format); | ||
| 134 | surface->create(); | ||
| 111 | } | 135 | } |
| 112 | 136 | ||
| 113 | void MakeCurrent() override { | 137 | void MakeCurrent() override { |
| 114 | context.makeCurrent(shared_context->surface()); | 138 | context->makeCurrent(surface); |
| 115 | } | 139 | } |
| 116 | 140 | ||
| 117 | void DoneCurrent() override { | 141 | void DoneCurrent() override { |
| 118 | context.doneCurrent(); | 142 | context->doneCurrent(); |
| 119 | } | 143 | } |
| 120 | 144 | ||
| 121 | void SwapBuffers() override {} | ||
| 122 | |||
| 123 | private: | 145 | private: |
| 124 | QOpenGLContext* shared_context; | 146 | QOpenGLContext* context; |
| 125 | QOpenGLContext context; | 147 | QOffscreenSurface* surface; |
| 126 | }; | 148 | }; |
| 127 | 149 | ||
| 128 | class GWidgetInternal : public QWindow { | 150 | OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) |
| 129 | public: | 151 | : QWindow(parent), event_handler(event_handler), |
| 130 | GWidgetInternal(GRenderWindow* parent) : parent(parent) {} | 152 | context(new QOpenGLContext(shared_context->parent())) { |
| 131 | virtual ~GWidgetInternal() = default; | ||
| 132 | |||
| 133 | void resizeEvent(QResizeEvent* ev) override { | ||
| 134 | parent->OnClientAreaResized(ev->size().width(), ev->size().height()); | ||
| 135 | parent->OnFramebufferSizeChanged(); | ||
| 136 | } | ||
| 137 | |||
| 138 | void keyPressEvent(QKeyEvent* event) override { | ||
| 139 | InputCommon::GetKeyboard()->PressKey(event->key()); | ||
| 140 | } | ||
| 141 | |||
| 142 | void keyReleaseEvent(QKeyEvent* event) override { | ||
| 143 | InputCommon::GetKeyboard()->ReleaseKey(event->key()); | ||
| 144 | } | ||
| 145 | 153 | ||
| 146 | void mousePressEvent(QMouseEvent* event) override { | 154 | // disable vsync for any shared contexts |
| 147 | if (event->source() == Qt::MouseEventSynthesizedBySystem) | 155 | auto format = shared_context->format(); |
| 148 | return; // touch input is handled in TouchBeginEvent | 156 | format.setSwapInterval(Settings::values.use_vsync ? 1 : 0); |
| 157 | this->setFormat(format); | ||
| 149 | 158 | ||
| 150 | const auto pos{event->pos()}; | 159 | context->setShareContext(shared_context); |
| 151 | if (event->button() == Qt::LeftButton) { | 160 | context->setScreen(this->screen()); |
| 152 | const auto [x, y] = parent->ScaleTouch(pos); | 161 | context->setFormat(format); |
| 153 | parent->TouchPressed(x, y); | 162 | context->create(); |
| 154 | } else if (event->button() == Qt::RightButton) { | ||
| 155 | InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | void mouseMoveEvent(QMouseEvent* event) override { | ||
| 160 | if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||
| 161 | return; // touch input is handled in TouchUpdateEvent | ||
| 162 | |||
| 163 | const auto pos{event->pos()}; | ||
| 164 | const auto [x, y] = parent->ScaleTouch(pos); | ||
| 165 | parent->TouchMoved(x, y); | ||
| 166 | InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); | ||
| 167 | } | ||
| 168 | |||
| 169 | void mouseReleaseEvent(QMouseEvent* event) override { | ||
| 170 | if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||
| 171 | return; // touch input is handled in TouchEndEvent | ||
| 172 | |||
| 173 | if (event->button() == Qt::LeftButton) | ||
| 174 | parent->TouchReleased(); | ||
| 175 | else if (event->button() == Qt::RightButton) | ||
| 176 | InputCommon::GetMotionEmu()->EndTilt(); | ||
| 177 | } | ||
| 178 | |||
| 179 | void DisablePainting() { | ||
| 180 | do_painting = false; | ||
| 181 | } | ||
| 182 | 163 | ||
| 183 | void EnablePainting() { | 164 | setSurfaceType(QWindow::OpenGLSurface); |
| 184 | do_painting = true; | ||
| 185 | } | ||
| 186 | 165 | ||
| 187 | std::pair<unsigned, unsigned> GetSize() const { | 166 | // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, |
| 188 | return std::make_pair(width(), height()); | 167 | // WA_DontShowOnScreen, WA_DeleteOnClose |
| 189 | } | 168 | } |
| 190 | 169 | ||
| 191 | protected: | 170 | OpenGLWindow::~OpenGLWindow() { |
| 192 | bool IsPaintingEnabled() const { | 171 | context->doneCurrent(); |
| 193 | return do_painting; | 172 | } |
| 194 | } | ||
| 195 | 173 | ||
| 196 | private: | 174 | void OpenGLWindow::Present() { |
| 197 | GRenderWindow* parent; | 175 | if (!isExposed()) |
| 198 | bool do_painting = false; | 176 | return; |
| 199 | }; | ||
| 200 | 177 | ||
| 201 | // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL | 178 | context->makeCurrent(this); |
| 202 | // context. | 179 | Core::System::GetInstance().Renderer().TryPresent(100); |
| 203 | // The corresponding functionality is handled in EmuThread instead | 180 | context->swapBuffers(this); |
| 204 | class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow { | 181 | auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>(); |
| 205 | public: | 182 | f->glFinish(); |
| 206 | GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) | 183 | QWindow::requestUpdate(); |
| 207 | : GWidgetInternal(parent), QOpenGLWindow(shared_context) {} | 184 | } |
| 208 | ~GGLWidgetInternal() override = default; | ||
| 209 | 185 | ||
| 210 | void paintEvent(QPaintEvent* ev) override { | 186 | bool OpenGLWindow::event(QEvent* event) { |
| 211 | if (IsPaintingEnabled()) { | 187 | switch (event->type()) { |
| 212 | QPainter painter(this); | 188 | case QEvent::UpdateRequest: |
| 213 | } | 189 | Present(); |
| 190 | return true; | ||
| 191 | case QEvent::MouseButtonPress: | ||
| 192 | case QEvent::MouseButtonRelease: | ||
| 193 | case QEvent::MouseButtonDblClick: | ||
| 194 | case QEvent::MouseMove: | ||
| 195 | case QEvent::KeyPress: | ||
| 196 | case QEvent::KeyRelease: | ||
| 197 | case QEvent::FocusIn: | ||
| 198 | case QEvent::FocusOut: | ||
| 199 | case QEvent::FocusAboutToChange: | ||
| 200 | case QEvent::Enter: | ||
| 201 | case QEvent::Leave: | ||
| 202 | case QEvent::Wheel: | ||
| 203 | case QEvent::TabletMove: | ||
| 204 | case QEvent::TabletPress: | ||
| 205 | case QEvent::TabletRelease: | ||
| 206 | case QEvent::TabletEnterProximity: | ||
| 207 | case QEvent::TabletLeaveProximity: | ||
| 208 | case QEvent::TouchBegin: | ||
| 209 | case QEvent::TouchUpdate: | ||
| 210 | case QEvent::TouchEnd: | ||
| 211 | case QEvent::InputMethodQuery: | ||
| 212 | case QEvent::TouchCancel: | ||
| 213 | return QCoreApplication::sendEvent(event_handler, event); | ||
| 214 | case QEvent::Drop: | ||
| 215 | GetMainWindow()->DropAction(static_cast<QDropEvent*>(event)); | ||
| 216 | return true; | ||
| 217 | case QEvent::DragResponse: | ||
| 218 | case QEvent::DragEnter: | ||
| 219 | case QEvent::DragLeave: | ||
| 220 | case QEvent::DragMove: | ||
| 221 | GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event)); | ||
| 222 | return true; | ||
| 223 | default: | ||
| 224 | return QWindow::event(event); | ||
| 214 | } | 225 | } |
| 215 | }; | 226 | } |
| 216 | 227 | ||
| 217 | #ifdef HAS_VULKAN | 228 | void OpenGLWindow::exposeEvent(QExposeEvent* event) { |
| 218 | class GVKWidgetInternal final : public GWidgetInternal { | 229 | QWindow::requestUpdate(); |
| 219 | public: | 230 | QWindow::exposeEvent(event); |
| 220 | GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) { | 231 | } |
| 221 | setSurfaceType(QSurface::SurfaceType::VulkanSurface); | ||
| 222 | setVulkanInstance(instance); | ||
| 223 | } | ||
| 224 | ~GVKWidgetInternal() override = default; | ||
| 225 | }; | ||
| 226 | #endif | ||
| 227 | 232 | ||
| 228 | GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) | 233 | GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread) |
| 229 | : QWidget(parent), emu_thread(emu_thread) { | 234 | : QWidget(parent_), emu_thread(emu_thread) { |
| 230 | setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") | 235 | setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") |
| 231 | .arg(QString::fromUtf8(Common::g_build_name), | 236 | .arg(QString::fromUtf8(Common::g_build_name), |
| 232 | QString::fromUtf8(Common::g_scm_branch), | 237 | QString::fromUtf8(Common::g_scm_branch), |
| 233 | QString::fromUtf8(Common::g_scm_desc))); | 238 | QString::fromUtf8(Common::g_scm_desc))); |
| 234 | setAttribute(Qt::WA_AcceptTouchEvents); | 239 | setAttribute(Qt::WA_AcceptTouchEvents); |
| 235 | 240 | auto layout = new QHBoxLayout(this); | |
| 241 | layout->setMargin(0); | ||
| 242 | setLayout(layout); | ||
| 236 | InputCommon::Init(); | 243 | InputCommon::Init(); |
| 244 | |||
| 245 | GMainWindow* parent = GetMainWindow(); | ||
| 237 | connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); | 246 | connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); |
| 238 | } | 247 | } |
| 239 | 248 | ||
| 240 | GRenderWindow::~GRenderWindow() { | 249 | GRenderWindow::~GRenderWindow() { |
| 241 | InputCommon::Shutdown(); | 250 | InputCommon::Shutdown(); |
| 242 | |||
| 243 | // Avoid an unordered destruction that generates a segfault | ||
| 244 | delete child; | ||
| 245 | } | 251 | } |
| 246 | 252 | ||
| 247 | void GRenderWindow::moveContext() { | 253 | void GRenderWindow::MakeCurrent() { |
| 248 | if (!context) { | 254 | core_context->MakeCurrent(); |
| 249 | return; | 255 | } |
| 250 | } | ||
| 251 | DoneCurrent(); | ||
| 252 | 256 | ||
| 253 | // If the thread started running, move the GL Context to the new thread. Otherwise, move it | 257 | void GRenderWindow::DoneCurrent() { |
| 254 | // back. | 258 | core_context->DoneCurrent(); |
| 255 | auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) | ||
| 256 | ? emu_thread | ||
| 257 | : qApp->thread(); | ||
| 258 | context->moveToThread(thread); | ||
| 259 | } | 259 | } |
| 260 | 260 | ||
| 261 | void GRenderWindow::SwapBuffers() { | 261 | void GRenderWindow::PollEvents() { |
| 262 | if (context) { | ||
| 263 | context->swapBuffers(child); | ||
| 264 | } | ||
| 265 | if (!first_frame) { | 262 | if (!first_frame) { |
| 266 | first_frame = true; | 263 | first_frame = true; |
| 267 | emit FirstFrameDisplayed(); | 264 | emit FirstFrameDisplayed(); |
| 268 | } | 265 | } |
| 269 | } | 266 | } |
| 270 | 267 | ||
| 271 | void GRenderWindow::MakeCurrent() { | ||
| 272 | if (context) { | ||
| 273 | context->makeCurrent(child); | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | void GRenderWindow::DoneCurrent() { | ||
| 278 | if (context) { | ||
| 279 | context->doneCurrent(); | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | void GRenderWindow::PollEvents() {} | ||
| 284 | |||
| 285 | bool GRenderWindow::IsShown() const { | 268 | bool GRenderWindow::IsShown() const { |
| 286 | return !isMinimized(); | 269 | return !isMinimized(); |
| 287 | } | 270 | } |
| @@ -309,21 +292,10 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i | |||
| 309 | void GRenderWindow::OnFramebufferSizeChanged() { | 292 | void GRenderWindow::OnFramebufferSizeChanged() { |
| 310 | // Screen changes potentially incur a change in screen DPI, hence we should update the | 293 | // Screen changes potentially incur a change in screen DPI, hence we should update the |
| 311 | // framebuffer size | 294 | // framebuffer size |
| 312 | const qreal pixelRatio{GetWindowPixelRatio()}; | 295 | const qreal pixel_ratio = windowPixelRatio(); |
| 313 | const auto size{child->GetSize()}; | 296 | const u32 width = this->width() * pixel_ratio; |
| 314 | UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio); | 297 | const u32 height = this->height() * pixel_ratio; |
| 315 | } | 298 | UpdateCurrentFramebufferLayout(width, height); |
| 316 | |||
| 317 | void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { | ||
| 318 | if (child) { | ||
| 319 | child->keyPressEvent(event); | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) { | ||
| 324 | if (child) { | ||
| 325 | child->keyReleaseEvent(event); | ||
| 326 | } | ||
| 327 | } | 299 | } |
| 328 | 300 | ||
| 329 | void GRenderWindow::BackupGeometry() { | 301 | void GRenderWindow::BackupGeometry() { |
| @@ -351,13 +323,12 @@ QByteArray GRenderWindow::saveGeometry() { | |||
| 351 | return geometry; | 323 | return geometry; |
| 352 | } | 324 | } |
| 353 | 325 | ||
| 354 | qreal GRenderWindow::GetWindowPixelRatio() const { | 326 | qreal GRenderWindow::windowPixelRatio() const { |
| 355 | // windowHandle() might not be accessible until the window is displayed to screen. | 327 | return devicePixelRatio(); |
| 356 | return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; | ||
| 357 | } | 328 | } |
| 358 | 329 | ||
| 359 | std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { | 330 | std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { |
| 360 | const qreal pixel_ratio{GetWindowPixelRatio()}; | 331 | const qreal pixel_ratio = windowPixelRatio(); |
| 361 | return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), | 332 | return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), |
| 362 | static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; | 333 | static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; |
| 363 | } | 334 | } |
| @@ -367,6 +338,47 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { | |||
| 367 | QWidget::closeEvent(event); | 338 | QWidget::closeEvent(event); |
| 368 | } | 339 | } |
| 369 | 340 | ||
| 341 | void GRenderWindow::keyPressEvent(QKeyEvent* event) { | ||
| 342 | InputCommon::GetKeyboard()->PressKey(event->key()); | ||
| 343 | } | ||
| 344 | |||
| 345 | void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { | ||
| 346 | InputCommon::GetKeyboard()->ReleaseKey(event->key()); | ||
| 347 | } | ||
| 348 | |||
| 349 | void GRenderWindow::mousePressEvent(QMouseEvent* event) { | ||
| 350 | if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||
| 351 | return; // touch input is handled in TouchBeginEvent | ||
| 352 | |||
| 353 | auto pos = event->pos(); | ||
| 354 | if (event->button() == Qt::LeftButton) { | ||
| 355 | const auto [x, y] = ScaleTouch(pos); | ||
| 356 | this->TouchPressed(x, y); | ||
| 357 | } else if (event->button() == Qt::RightButton) { | ||
| 358 | InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { | ||
| 363 | if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||
| 364 | return; // touch input is handled in TouchUpdateEvent | ||
| 365 | |||
| 366 | auto pos = event->pos(); | ||
| 367 | const auto [x, y] = ScaleTouch(pos); | ||
| 368 | this->TouchMoved(x, y); | ||
| 369 | InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); | ||
| 370 | } | ||
| 371 | |||
| 372 | void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { | ||
| 373 | if (event->source() == Qt::MouseEventSynthesizedBySystem) | ||
| 374 | return; // touch input is handled in TouchEndEvent | ||
| 375 | |||
| 376 | if (event->button() == Qt::LeftButton) | ||
| 377 | this->TouchReleased(); | ||
| 378 | else if (event->button() == Qt::RightButton) | ||
| 379 | InputCommon::GetMotionEmu()->EndTilt(); | ||
| 380 | } | ||
| 381 | |||
| 370 | void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { | 382 | void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { |
| 371 | // TouchBegin always has exactly one touch point, so take the .first() | 383 | // TouchBegin always has exactly one touch point, so take the .first() |
| 372 | const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); | 384 | const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); |
| @@ -415,26 +427,20 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { | |||
| 415 | InputCommon::GetKeyboard()->ReleaseAllKeys(); | 427 | InputCommon::GetKeyboard()->ReleaseAllKeys(); |
| 416 | } | 428 | } |
| 417 | 429 | ||
| 418 | void GRenderWindow::OnClientAreaResized(u32 width, u32 height) { | 430 | void GRenderWindow::resizeEvent(QResizeEvent* event) { |
| 419 | NotifyClientAreaSizeChanged(std::make_pair(width, height)); | 431 | QWidget::resizeEvent(event); |
| 432 | OnFramebufferSizeChanged(); | ||
| 420 | } | 433 | } |
| 421 | 434 | ||
| 422 | std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { | 435 | std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { |
| 423 | return std::make_unique<GGLContext>(context.get()); | 436 | if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { |
| 437 | return std::make_unique<GGLContext>(QOpenGLContext::globalShareContext()); | ||
| 438 | } | ||
| 439 | return {}; | ||
| 424 | } | 440 | } |
| 425 | 441 | ||
| 426 | bool GRenderWindow::InitRenderTarget() { | 442 | bool GRenderWindow::InitRenderTarget() { |
| 427 | shared_context.reset(); | 443 | ReleaseRenderTarget(); |
| 428 | context.reset(); | ||
| 429 | if (child) { | ||
| 430 | delete child; | ||
| 431 | } | ||
| 432 | if (container) { | ||
| 433 | delete container; | ||
| 434 | } | ||
| 435 | if (layout()) { | ||
| 436 | delete layout(); | ||
| 437 | } | ||
| 438 | 444 | ||
| 439 | first_frame = false; | 445 | first_frame = false; |
| 440 | 446 | ||
| @@ -451,13 +457,6 @@ bool GRenderWindow::InitRenderTarget() { | |||
| 451 | break; | 457 | break; |
| 452 | } | 458 | } |
| 453 | 459 | ||
| 454 | container = QWidget::createWindowContainer(child, this); | ||
| 455 | QBoxLayout* layout = new QHBoxLayout(this); | ||
| 456 | |||
| 457 | layout->addWidget(container); | ||
| 458 | layout->setMargin(0); | ||
| 459 | setLayout(layout); | ||
| 460 | |||
| 461 | // Reset minimum required size to avoid resizing issues on the main window after restarting. | 460 | // Reset minimum required size to avoid resizing issues on the main window after restarting. |
| 462 | setMinimumSize(1, 1); | 461 | setMinimumSize(1, 1); |
| 463 | 462 | ||
| @@ -467,14 +466,9 @@ bool GRenderWindow::InitRenderTarget() { | |||
| 467 | hide(); | 466 | hide(); |
| 468 | 467 | ||
| 469 | resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | 468 | resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); |
| 470 | child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||
| 471 | container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||
| 472 | 469 | ||
| 473 | OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); | 470 | OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); |
| 474 | |||
| 475 | OnFramebufferSizeChanged(); | 471 | OnFramebufferSizeChanged(); |
| 476 | NotifyClientAreaSizeChanged(child->GetSize()); | ||
| 477 | |||
| 478 | BackupGeometry(); | 472 | BackupGeometry(); |
| 479 | 473 | ||
| 480 | if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { | 474 | if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { |
| @@ -486,6 +480,14 @@ bool GRenderWindow::InitRenderTarget() { | |||
| 486 | return true; | 480 | return true; |
| 487 | } | 481 | } |
| 488 | 482 | ||
| 483 | void GRenderWindow::ReleaseRenderTarget() { | ||
| 484 | if (child_widget) { | ||
| 485 | layout()->removeWidget(child_widget); | ||
| 486 | delete child_widget; | ||
| 487 | child_widget = nullptr; | ||
| 488 | } | ||
| 489 | } | ||
| 490 | |||
| 489 | void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { | 491 | void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { |
| 490 | auto& renderer = Core::System::GetInstance().Renderer(); | 492 | auto& renderer = Core::System::GetInstance().Renderer(); |
| 491 | 493 | ||
| @@ -521,16 +523,20 @@ bool GRenderWindow::InitializeOpenGL() { | |||
| 521 | fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); | 523 | fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); |
| 522 | // TODO: expose a setting for buffer value (ie default/single/double/triple) | 524 | // TODO: expose a setting for buffer value (ie default/single/double/triple) |
| 523 | fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); | 525 | fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); |
| 524 | shared_context = std::make_unique<QOpenGLContext>(); | 526 | fmt.setSwapInterval(0); |
| 525 | shared_context->setFormat(fmt); | 527 | QSurfaceFormat::setDefaultFormat(fmt); |
| 526 | shared_context->create(); | 528 | |
| 527 | context = std::make_unique<QOpenGLContext>(); | 529 | GMainWindow* parent = GetMainWindow(); |
| 528 | context->setShareContext(shared_context.get()); | 530 | QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr; |
| 529 | context->setFormat(fmt); | 531 | child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext()); |
| 530 | context->create(); | 532 | child_window->create(); |
| 531 | fmt.setSwapInterval(false); | 533 | child_widget = createWindowContainer(child_window, this); |
| 534 | child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||
| 535 | layout()->addWidget(child_widget); | ||
| 536 | |||
| 537 | core_context = CreateSharedContext(); | ||
| 538 | resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); | ||
| 532 | 539 | ||
| 533 | child = new GGLWidgetInternal(this, shared_context.get()); | ||
| 534 | return true; | 540 | return true; |
| 535 | } | 541 | } |
| 536 | 542 | ||
| @@ -621,12 +627,10 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { | |||
| 621 | 627 | ||
| 622 | void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { | 628 | void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { |
| 623 | this->emu_thread = emu_thread; | 629 | this->emu_thread = emu_thread; |
| 624 | child->DisablePainting(); | ||
| 625 | } | 630 | } |
| 626 | 631 | ||
| 627 | void GRenderWindow::OnEmulationStopping() { | 632 | void GRenderWindow::OnEmulationStopping() { |
| 628 | emu_thread = nullptr; | 633 | emu_thread = nullptr; |
| 629 | child->EnablePainting(); | ||
| 630 | } | 634 | } |
| 631 | 635 | ||
| 632 | void GRenderWindow::showEvent(QShowEvent* event) { | 636 | void GRenderWindow::showEvent(QShowEvent* event) { |
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 71a2fa321..37bc4f043 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h | |||
| @@ -11,6 +11,7 @@ | |||
| 11 | #include <QImage> | 11 | #include <QImage> |
| 12 | #include <QThread> | 12 | #include <QThread> |
| 13 | #include <QWidget> | 13 | #include <QWidget> |
| 14 | #include <QWindow> | ||
| 14 | 15 | ||
| 15 | #include "common/thread.h" | 16 | #include "common/thread.h" |
| 16 | #include "core/core.h" | 17 | #include "core/core.h" |
| @@ -42,7 +43,7 @@ class EmuThread final : public QThread { | |||
| 42 | Q_OBJECT | 43 | Q_OBJECT |
| 43 | 44 | ||
| 44 | public: | 45 | public: |
| 45 | explicit EmuThread(GRenderWindow* render_window); | 46 | explicit EmuThread(Core::Frontend::GraphicsContext& context); |
| 46 | ~EmuThread() override; | 47 | ~EmuThread() override; |
| 47 | 48 | ||
| 48 | /** | 49 | /** |
| @@ -96,7 +97,7 @@ private: | |||
| 96 | std::mutex running_mutex; | 97 | std::mutex running_mutex; |
| 97 | std::condition_variable running_cv; | 98 | std::condition_variable running_cv; |
| 98 | 99 | ||
| 99 | GRenderWindow* render_window; | 100 | Core::Frontend::GraphicsContext& core_context; |
| 100 | 101 | ||
| 101 | signals: | 102 | signals: |
| 102 | /** | 103 | /** |
| @@ -122,15 +123,32 @@ signals: | |||
| 122 | void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); | 123 | void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); |
| 123 | }; | 124 | }; |
| 124 | 125 | ||
| 126 | class OpenGLWindow : public QWindow { | ||
| 127 | Q_OBJECT | ||
| 128 | public: | ||
| 129 | explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context); | ||
| 130 | |||
| 131 | ~OpenGLWindow(); | ||
| 132 | |||
| 133 | void Present(); | ||
| 134 | |||
| 135 | protected: | ||
| 136 | bool event(QEvent* event) override; | ||
| 137 | void exposeEvent(QExposeEvent* event) override; | ||
| 138 | |||
| 139 | private: | ||
| 140 | QOpenGLContext* context; | ||
| 141 | QWidget* event_handler; | ||
| 142 | }; | ||
| 143 | |||
| 125 | class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { | 144 | class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { |
| 126 | Q_OBJECT | 145 | Q_OBJECT |
| 127 | 146 | ||
| 128 | public: | 147 | public: |
| 129 | GRenderWindow(GMainWindow* parent, EmuThread* emu_thread); | 148 | GRenderWindow(QWidget* parent, EmuThread* emu_thread); |
| 130 | ~GRenderWindow() override; | 149 | ~GRenderWindow() override; |
| 131 | 150 | ||
| 132 | // EmuWindow implementation | 151 | // EmuWindow implementation. |
| 133 | void SwapBuffers() override; | ||
| 134 | void MakeCurrent() override; | 152 | void MakeCurrent() override; |
| 135 | void DoneCurrent() override; | 153 | void DoneCurrent() override; |
| 136 | void PollEvents() override; | 154 | void PollEvents() override; |
| @@ -139,30 +157,36 @@ public: | |||
| 139 | void* surface) const override; | 157 | void* surface) const override; |
| 140 | std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; | 158 | std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; |
| 141 | 159 | ||
| 142 | void ForwardKeyPressEvent(QKeyEvent* event); | ||
| 143 | void ForwardKeyReleaseEvent(QKeyEvent* event); | ||
| 144 | |||
| 145 | void BackupGeometry(); | 160 | void BackupGeometry(); |
| 146 | void RestoreGeometry(); | 161 | void RestoreGeometry(); |
| 147 | void restoreGeometry(const QByteArray& geometry); // overridden | 162 | void restoreGeometry(const QByteArray& geometry); // overridden |
| 148 | QByteArray saveGeometry(); // overridden | 163 | QByteArray saveGeometry(); // overridden |
| 149 | 164 | ||
| 150 | qreal GetWindowPixelRatio() const; | 165 | qreal windowPixelRatio() const; |
| 151 | std::pair<u32, u32> ScaleTouch(QPointF pos) const; | ||
| 152 | 166 | ||
| 153 | void closeEvent(QCloseEvent* event) override; | 167 | void closeEvent(QCloseEvent* event) override; |
| 168 | |||
| 169 | void resizeEvent(QResizeEvent* event) override; | ||
| 170 | |||
| 171 | void keyPressEvent(QKeyEvent* event) override; | ||
| 172 | void keyReleaseEvent(QKeyEvent* event) override; | ||
| 173 | |||
| 174 | void mousePressEvent(QMouseEvent* event) override; | ||
| 175 | void mouseMoveEvent(QMouseEvent* event) override; | ||
| 176 | void mouseReleaseEvent(QMouseEvent* event) override; | ||
| 177 | |||
| 154 | bool event(QEvent* event) override; | 178 | bool event(QEvent* event) override; |
| 155 | void focusOutEvent(QFocusEvent* event) override; | ||
| 156 | 179 | ||
| 157 | void OnClientAreaResized(u32 width, u32 height); | 180 | void focusOutEvent(QFocusEvent* event) override; |
| 158 | 181 | ||
| 159 | bool InitRenderTarget(); | 182 | bool InitRenderTarget(); |
| 160 | 183 | ||
| 184 | /// Destroy the previous run's child_widget which should also destroy the child_window | ||
| 185 | void ReleaseRenderTarget(); | ||
| 186 | |||
| 161 | void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); | 187 | void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); |
| 162 | 188 | ||
| 163 | public slots: | 189 | public slots: |
| 164 | void moveContext(); // overridden | ||
| 165 | |||
| 166 | void OnEmulationStarting(EmuThread* emu_thread); | 190 | void OnEmulationStarting(EmuThread* emu_thread); |
| 167 | void OnEmulationStopping(); | 191 | void OnEmulationStopping(); |
| 168 | void OnFramebufferSizeChanged(); | 192 | void OnFramebufferSizeChanged(); |
| @@ -173,6 +197,7 @@ signals: | |||
| 173 | void FirstFrameDisplayed(); | 197 | void FirstFrameDisplayed(); |
| 174 | 198 | ||
| 175 | private: | 199 | private: |
| 200 | std::pair<u32, u32> ScaleTouch(QPointF pos) const; | ||
| 176 | void TouchBeginEvent(const QTouchEvent* event); | 201 | void TouchBeginEvent(const QTouchEvent* event); |
| 177 | void TouchUpdateEvent(const QTouchEvent* event); | 202 | void TouchUpdateEvent(const QTouchEvent* event); |
| 178 | void TouchEndEvent(); | 203 | void TouchEndEvent(); |
| @@ -184,15 +209,9 @@ private: | |||
| 184 | bool LoadOpenGL(); | 209 | bool LoadOpenGL(); |
| 185 | QStringList GetUnsupportedGLExtensions() const; | 210 | QStringList GetUnsupportedGLExtensions() const; |
| 186 | 211 | ||
| 187 | QWidget* container = nullptr; | ||
| 188 | GWidgetInternal* child = nullptr; | ||
| 189 | |||
| 190 | EmuThread* emu_thread; | 212 | EmuThread* emu_thread; |
| 191 | // Context that backs the GGLWidgetInternal (and will be used by core to render) | 213 | |
| 192 | std::unique_ptr<QOpenGLContext> context; | 214 | std::unique_ptr<GraphicsContext> core_context; |
| 193 | // Context that will be shared between all newly created contexts. This should never be made | ||
| 194 | // current | ||
| 195 | std::unique_ptr<QOpenGLContext> shared_context; | ||
| 196 | 215 | ||
| 197 | #ifdef HAS_VULKAN | 216 | #ifdef HAS_VULKAN |
| 198 | std::unique_ptr<QVulkanInstance> vk_instance; | 217 | std::unique_ptr<QVulkanInstance> vk_instance; |
| @@ -202,6 +221,15 @@ private: | |||
| 202 | QImage screenshot_image; | 221 | QImage screenshot_image; |
| 203 | 222 | ||
| 204 | QByteArray geometry; | 223 | QByteArray geometry; |
| 224 | |||
| 225 | /// Native window handle that backs this presentation widget | ||
| 226 | QWindow* child_window = nullptr; | ||
| 227 | |||
| 228 | /// In order to embed the window into GRenderWindow, you need to use createWindowContainer to | ||
| 229 | /// put the child_window into a widget then add it to the layout. This child_widget can be | ||
| 230 | /// parented to GRenderWindow and use Qt's lifetime system | ||
| 231 | QWidget* child_widget = nullptr; | ||
| 232 | |||
| 205 | bool first_frame = false; | 233 | bool first_frame = false; |
| 206 | 234 | ||
| 207 | protected: | 235 | protected: |