1 // Copyright 2014 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <glad/glad.h>
6 
7 #include <QApplication>
8 #include <QHBoxLayout>
9 #include <QKeyEvent>
10 #include <QMessageBox>
11 #include <QPainter>
12 #include <QScreen>
13 #include <QString>
14 #include <QStringList>
15 #include <QWindow>
16 
17 #ifdef HAS_OPENGL
18 #include <QOffscreenSurface>
19 #include <QOpenGLContext>
20 #endif
21 
22 #if !defined(WIN32) && HAS_VULKAN
23 #include <qpa/qplatformnativeinterface.h>
24 #endif
25 
26 #include <fmt/format.h>
27 
28 #include "common/assert.h"
29 #include "common/microprofile.h"
30 #include "common/scm_rev.h"
31 #include "common/scope_exit.h"
32 #include "core/core.h"
33 #include "core/frontend/framebuffer_layout.h"
34 #include "core/hle/kernel/process.h"
35 #include "core/settings.h"
36 #include "input_common/keyboard.h"
37 #include "input_common/main.h"
38 #include "input_common/mouse/mouse_input.h"
39 #include "video_core/renderer_base.h"
40 #include "video_core/video_core.h"
41 #include "yuzu/bootmanager.h"
42 #include "yuzu/main.h"
43 
44 EmuThread::EmuThread() = default;
45 
46 EmuThread::~EmuThread() = default;
47 
run()48 void EmuThread::run() {
49     std::string name = "yuzu:EmuControlThread";
50     MicroProfileOnThreadCreate(name.c_str());
51     Common::SetCurrentThreadName(name.c_str());
52 
53     auto& system = Core::System::GetInstance();
54 
55     system.RegisterHostThread();
56 
57     auto& gpu = system.GPU();
58 
59     // Main process has been loaded. Make the context current to this thread and begin GPU and CPU
60     // execution.
61     gpu.Start();
62 
63     gpu.ObtainContext();
64 
65     emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
66 
67     system.Renderer().Rasterizer().LoadDiskResources(
68         system.CurrentProcess()->GetTitleID(), stop_run,
69         [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
70             emit LoadProgress(stage, value, total);
71         });
72 
73     emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
74 
75     gpu.ReleaseContext();
76 
77     // Holds whether the cpu was running during the last iteration,
78     // so that the DebugModeLeft signal can be emitted before the
79     // next execution step
80     bool was_active = false;
81     while (!stop_run) {
82         if (running) {
83             if (was_active) {
84                 emit DebugModeLeft();
85             }
86 
87             running_guard = true;
88             Core::System::ResultStatus result = system.Run();
89             if (result != Core::System::ResultStatus::Success) {
90                 running_guard = false;
91                 this->SetRunning(false);
92                 emit ErrorThrown(result, system.GetStatusDetails());
93             }
94             running_wait.Wait();
95             result = system.Pause();
96             if (result != Core::System::ResultStatus::Success) {
97                 running_guard = false;
98                 this->SetRunning(false);
99                 emit ErrorThrown(result, system.GetStatusDetails());
100             }
101             running_guard = false;
102 
103             if (!stop_run) {
104                 was_active = true;
105                 emit DebugModeEntered();
106             }
107         } else if (exec_step) {
108             UNIMPLEMENTED();
109         } else {
110             std::unique_lock lock{running_mutex};
111             running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
112         }
113     }
114 
115     // Shutdown the core emulation
116     system.Shutdown();
117 
118 #if MICROPROFILE_ENABLED
119     MicroProfileOnThreadExit();
120 #endif
121 }
122 
123 #ifdef HAS_OPENGL
124 class OpenGLSharedContext : public Core::Frontend::GraphicsContext {
125 public:
126     /// Create the original context that should be shared from
OpenGLSharedContext(QSurface * surface)127     explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
128         QSurfaceFormat format;
129         format.setVersion(4, 3);
130         format.setProfile(QSurfaceFormat::CompatibilityProfile);
131         format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
132         if (Settings::values.renderer_debug) {
133             format.setOption(QSurfaceFormat::FormatOption::DebugContext);
134         }
135         // TODO: expose a setting for buffer value (ie default/single/double/triple)
136         format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
137         format.setSwapInterval(0);
138 
139         context = std::make_unique<QOpenGLContext>();
140         context->setFormat(format);
141         if (!context->create()) {
142             LOG_ERROR(Frontend, "Unable to create main openGL context");
143         }
144     }
145 
146     /// Create the shared contexts for rendering and presentation
OpenGLSharedContext(QOpenGLContext * share_context,QSurface * main_surface=nullptr)147     explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
148 
149         // disable vsync for any shared contexts
150         auto format = share_context->format();
151         format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0);
152 
153         context = std::make_unique<QOpenGLContext>();
154         context->setShareContext(share_context);
155         context->setFormat(format);
156         if (!context->create()) {
157             LOG_ERROR(Frontend, "Unable to create shared openGL context");
158         }
159 
160         if (!main_surface) {
161             offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
162             offscreen_surface->setFormat(format);
163             offscreen_surface->create();
164             surface = offscreen_surface.get();
165         } else {
166             surface = main_surface;
167         }
168     }
169 
~OpenGLSharedContext()170     ~OpenGLSharedContext() {
171         DoneCurrent();
172     }
173 
SwapBuffers()174     void SwapBuffers() override {
175         context->swapBuffers(surface);
176     }
177 
MakeCurrent()178     void MakeCurrent() override {
179         // We can't track the current state of the underlying context in this wrapper class because
180         // Qt may make the underlying context not current for one reason or another. In particular,
181         // the WebBrowser uses GL, so it seems to conflict if we aren't careful.
182         // Instead of always just making the context current (which does not have any caching to
183         // check if the underlying context is already current) we can check for the current context
184         // in the thread local data by calling `currentContext()` and checking if its ours.
185         if (QOpenGLContext::currentContext() != context.get()) {
186             context->makeCurrent(surface);
187         }
188     }
189 
DoneCurrent()190     void DoneCurrent() override {
191         context->doneCurrent();
192     }
193 
GetShareContext()194     QOpenGLContext* GetShareContext() {
195         return context.get();
196     }
197 
GetShareContext() const198     const QOpenGLContext* GetShareContext() const {
199         return context.get();
200     }
201 
202 private:
203     // Avoid using Qt parent system here since we might move the QObjects to new threads
204     // As a note, this means we should avoid using slots/signals with the objects too
205     std::unique_ptr<QOpenGLContext> context;
206     std::unique_ptr<QOffscreenSurface> offscreen_surface{};
207     QSurface* surface;
208 };
209 #endif
210 
211 class DummyContext : public Core::Frontend::GraphicsContext {};
212 
213 class RenderWidget : public QWidget {
214 public:
RenderWidget(GRenderWindow * parent)215     explicit RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
216         setAttribute(Qt::WA_NativeWindow);
217         setAttribute(Qt::WA_PaintOnScreen);
218     }
219 
220     virtual ~RenderWidget() = default;
221 
paintEngine() const222     QPaintEngine* paintEngine() const override {
223         return nullptr;
224     }
225 
226 private:
227     GRenderWindow* render_window;
228 };
229 
230 class OpenGLRenderWidget : public RenderWidget {
231 public:
OpenGLRenderWidget(GRenderWindow * parent)232     explicit OpenGLRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
233         windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
234     }
235 
SetContext(std::unique_ptr<Core::Frontend::GraphicsContext> && context_)236     void SetContext(std::unique_ptr<Core::Frontend::GraphicsContext>&& context_) {
237         context = std::move(context_);
238     }
239 
240 private:
241     std::unique_ptr<Core::Frontend::GraphicsContext> context;
242 };
243 
244 #ifdef HAS_VULKAN
245 class VulkanRenderWidget : public RenderWidget {
246 public:
VulkanRenderWidget(GRenderWindow * parent)247     explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
248         windowHandle()->setSurfaceType(QWindow::VulkanSurface);
249     }
250 };
251 #endif
252 
GetWindowSystemType()253 static Core::Frontend::WindowSystemType GetWindowSystemType() {
254     // Determine WSI type based on Qt platform.
255     QString platform_name = QGuiApplication::platformName();
256     if (platform_name == QStringLiteral("windows"))
257         return Core::Frontend::WindowSystemType::Windows;
258     else if (platform_name == QStringLiteral("xcb"))
259         return Core::Frontend::WindowSystemType::X11;
260     else if (platform_name == QStringLiteral("wayland"))
261         return Core::Frontend::WindowSystemType::Wayland;
262 
263     LOG_CRITICAL(Frontend, "Unknown Qt platform!");
264     return Core::Frontend::WindowSystemType::Windows;
265 }
266 
GetWindowSystemInfo(QWindow * window)267 static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
268     Core::Frontend::EmuWindow::WindowSystemInfo wsi;
269     wsi.type = GetWindowSystemType();
270 
271 #ifdef HAS_VULKAN
272     // Our Win32 Qt external doesn't have the private API.
273 #if defined(WIN32) || defined(__APPLE__)
274     wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
275 #else
276     QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
277     wsi.display_connection = pni->nativeResourceForWindow("display", window);
278     if (wsi.type == Core::Frontend::WindowSystemType::Wayland)
279         wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
280     else
281         wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
282 #endif
283     wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
284 #endif
285 
286     return wsi;
287 }
288 
GRenderWindow(GMainWindow * parent,EmuThread * emu_thread_,std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_)289 GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
290                              std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_)
291     : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)} {
292     setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
293                        .arg(QString::fromUtf8(Common::g_build_name),
294                             QString::fromUtf8(Common::g_scm_branch),
295                             QString::fromUtf8(Common::g_scm_desc)));
296     setAttribute(Qt::WA_AcceptTouchEvents);
297     auto layout = new QHBoxLayout(this);
298     layout->setMargin(0);
299     setLayout(layout);
300     input_subsystem->Initialize();
301 
302     this->setMouseTracking(true);
303 
304     connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
305     connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram,
306             Qt::QueuedConnection);
307 }
308 
ExecuteProgram(std::size_t program_index)309 void GRenderWindow::ExecuteProgram(std::size_t program_index) {
310     emit ExecuteProgramSignal(program_index);
311 }
312 
~GRenderWindow()313 GRenderWindow::~GRenderWindow() {
314     input_subsystem->Shutdown();
315 }
316 
OnFrameDisplayed()317 void GRenderWindow::OnFrameDisplayed() {
318     if (!first_frame) {
319         first_frame = true;
320         emit FirstFrameDisplayed();
321     }
322 }
323 
IsShown() const324 bool GRenderWindow::IsShown() const {
325     return !isMinimized();
326 }
327 
328 // On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
329 //
330 // Older versions get the window size (density independent pixels),
331 // and hence, do not support DPI scaling ("retina" displays).
332 // The result will be a viewport that is smaller than the extent of the window.
OnFramebufferSizeChanged()333 void GRenderWindow::OnFramebufferSizeChanged() {
334     // Screen changes potentially incur a change in screen DPI, hence we should update the
335     // framebuffer size
336     const qreal pixel_ratio = windowPixelRatio();
337     const u32 width = this->width() * pixel_ratio;
338     const u32 height = this->height() * pixel_ratio;
339     UpdateCurrentFramebufferLayout(width, height);
340 }
341 
BackupGeometry()342 void GRenderWindow::BackupGeometry() {
343     geometry = QWidget::saveGeometry();
344 }
345 
RestoreGeometry()346 void GRenderWindow::RestoreGeometry() {
347     // We don't want to back up the geometry here (obviously)
348     QWidget::restoreGeometry(geometry);
349 }
350 
restoreGeometry(const QByteArray & geometry)351 void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
352     // Make sure users of this class don't need to deal with backing up the geometry themselves
353     QWidget::restoreGeometry(geometry);
354     BackupGeometry();
355 }
356 
saveGeometry()357 QByteArray GRenderWindow::saveGeometry() {
358     // If we are a top-level widget, store the current geometry
359     // otherwise, store the last backup
360     if (parent() == nullptr) {
361         return QWidget::saveGeometry();
362     }
363 
364     return geometry;
365 }
366 
windowPixelRatio() const367 qreal GRenderWindow::windowPixelRatio() const {
368     return devicePixelRatioF();
369 }
370 
ScaleTouch(const QPointF & pos) const371 std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF& pos) const {
372     const qreal pixel_ratio = windowPixelRatio();
373     return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
374             static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
375 }
376 
closeEvent(QCloseEvent * event)377 void GRenderWindow::closeEvent(QCloseEvent* event) {
378     emit Closed();
379     QWidget::closeEvent(event);
380 }
381 
keyPressEvent(QKeyEvent * event)382 void GRenderWindow::keyPressEvent(QKeyEvent* event) {
383     input_subsystem->GetKeyboard()->PressKey(event->key());
384 }
385 
keyReleaseEvent(QKeyEvent * event)386 void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
387     input_subsystem->GetKeyboard()->ReleaseKey(event->key());
388 }
389 
mousePressEvent(QMouseEvent * event)390 void GRenderWindow::mousePressEvent(QMouseEvent* event) {
391     // Touch input is handled in TouchBeginEvent
392     if (event->source() == Qt::MouseEventSynthesizedBySystem) {
393         return;
394     }
395 
396     auto pos = event->pos();
397     const auto [x, y] = ScaleTouch(pos);
398     input_subsystem->GetMouse()->PressButton(x, y, event->button());
399 
400     if (event->button() == Qt::LeftButton) {
401         this->TouchPressed(x, y);
402     }
403 
404     QWidget::mousePressEvent(event);
405 }
406 
mouseMoveEvent(QMouseEvent * event)407 void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
408     // Touch input is handled in TouchUpdateEvent
409     if (event->source() == Qt::MouseEventSynthesizedBySystem) {
410         return;
411     }
412 
413     auto pos = event->pos();
414     const auto [x, y] = ScaleTouch(pos);
415     input_subsystem->GetMouse()->MouseMove(x, y);
416     this->TouchMoved(x, y);
417 
418     QWidget::mouseMoveEvent(event);
419 }
420 
mouseReleaseEvent(QMouseEvent * event)421 void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
422     // Touch input is handled in TouchEndEvent
423     if (event->source() == Qt::MouseEventSynthesizedBySystem) {
424         return;
425     }
426 
427     input_subsystem->GetMouse()->ReleaseButton(event->button());
428 
429     if (event->button() == Qt::LeftButton) {
430         this->TouchReleased();
431     }
432 }
433 
TouchBeginEvent(const QTouchEvent * event)434 void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
435     // TouchBegin always has exactly one touch point, so take the .first()
436     const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
437     this->TouchPressed(x, y);
438 }
439 
TouchUpdateEvent(const QTouchEvent * event)440 void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
441     QPointF pos;
442     int active_points = 0;
443 
444     // average all active touch points
445     for (const auto& tp : event->touchPoints()) {
446         if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
447             active_points++;
448             pos += tp.pos();
449         }
450     }
451 
452     pos /= active_points;
453 
454     const auto [x, y] = ScaleTouch(pos);
455     this->TouchMoved(x, y);
456 }
457 
TouchEndEvent()458 void GRenderWindow::TouchEndEvent() {
459     this->TouchReleased();
460 }
461 
event(QEvent * event)462 bool GRenderWindow::event(QEvent* event) {
463     if (event->type() == QEvent::TouchBegin) {
464         TouchBeginEvent(static_cast<QTouchEvent*>(event));
465         return true;
466     } else if (event->type() == QEvent::TouchUpdate) {
467         TouchUpdateEvent(static_cast<QTouchEvent*>(event));
468         return true;
469     } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) {
470         TouchEndEvent();
471         return true;
472     }
473 
474     return QWidget::event(event);
475 }
476 
focusOutEvent(QFocusEvent * event)477 void GRenderWindow::focusOutEvent(QFocusEvent* event) {
478     QWidget::focusOutEvent(event);
479     input_subsystem->GetKeyboard()->ReleaseAllKeys();
480 }
481 
resizeEvent(QResizeEvent * event)482 void GRenderWindow::resizeEvent(QResizeEvent* event) {
483     QWidget::resizeEvent(event);
484     OnFramebufferSizeChanged();
485 }
486 
CreateSharedContext() const487 std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
488 #ifdef HAS_OPENGL
489     if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) {
490         auto c = static_cast<OpenGLSharedContext*>(main_context.get());
491         // Bind the shared contexts to the main surface in case the backend wants to take over
492         // presentation
493         return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
494                                                      child_widget->windowHandle());
495     }
496 #endif
497     return std::make_unique<DummyContext>();
498 }
499 
InitRenderTarget()500 bool GRenderWindow::InitRenderTarget() {
501     ReleaseRenderTarget();
502 
503     first_frame = false;
504 
505     switch (Settings::values.renderer_backend.GetValue()) {
506     case Settings::RendererBackend::OpenGL:
507         if (!InitializeOpenGL()) {
508             return false;
509         }
510         break;
511     case Settings::RendererBackend::Vulkan:
512         if (!InitializeVulkan()) {
513             return false;
514         }
515         break;
516     }
517 
518     // Update the Window System information with the new render target
519     window_info = GetWindowSystemInfo(child_widget->windowHandle());
520 
521     child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
522     layout()->addWidget(child_widget);
523     // Reset minimum required size to avoid resizing issues on the main window after restarting.
524     setMinimumSize(1, 1);
525 
526     resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
527 
528     OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
529     OnFramebufferSizeChanged();
530     BackupGeometry();
531 
532     if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) {
533         if (!LoadOpenGL()) {
534             return false;
535         }
536     }
537 
538     return true;
539 }
540 
ReleaseRenderTarget()541 void GRenderWindow::ReleaseRenderTarget() {
542     if (child_widget) {
543         layout()->removeWidget(child_widget);
544         child_widget->deleteLater();
545         child_widget = nullptr;
546     }
547     main_context.reset();
548 }
549 
CaptureScreenshot(u32 res_scale,const QString & screenshot_path)550 void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
551     auto& renderer = Core::System::GetInstance().Renderer();
552 
553     if (res_scale == 0) {
554         res_scale = VideoCore::GetResolutionScaleFactor(renderer);
555     }
556 
557     const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
558     screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
559     renderer.RequestScreenshot(
560         screenshot_image.bits(),
561         [=, this] {
562             const std::string std_screenshot_path = screenshot_path.toStdString();
563             if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
564                 LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
565             } else {
566                 LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);
567             }
568         },
569         layout);
570 }
571 
OnMinimalClientAreaChangeRequest(std::pair<u32,u32> minimal_size)572 void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
573     setMinimumSize(minimal_size.first, minimal_size.second);
574 }
575 
InitializeOpenGL()576 bool GRenderWindow::InitializeOpenGL() {
577 #ifdef HAS_OPENGL
578     // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
579     // WA_DontShowOnScreen, WA_DeleteOnClose
580     auto child = new OpenGLRenderWidget(this);
581     child_widget = child;
582     child_widget->windowHandle()->create();
583     auto context = std::make_shared<OpenGLSharedContext>(child->windowHandle());
584     main_context = context;
585     child->SetContext(
586         std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle()));
587 
588     return true;
589 #else
590     QMessageBox::warning(this, tr("OpenGL not available!"),
591                          tr("yuzu has not been compiled with OpenGL support."));
592     return false;
593 #endif
594 }
595 
InitializeVulkan()596 bool GRenderWindow::InitializeVulkan() {
597 #ifdef HAS_VULKAN
598     auto child = new VulkanRenderWidget(this);
599     child_widget = child;
600     child_widget->windowHandle()->create();
601     main_context = std::make_unique<DummyContext>();
602 
603     return true;
604 #else
605     QMessageBox::critical(this, tr("Vulkan not available!"),
606                           tr("yuzu has not been compiled with Vulkan support."));
607     return false;
608 #endif
609 }
610 
LoadOpenGL()611 bool GRenderWindow::LoadOpenGL() {
612     auto context = CreateSharedContext();
613     auto scope = context->Acquire();
614     if (!gladLoadGL()) {
615         QMessageBox::warning(
616             this, tr("Error while initializing OpenGL!"),
617             tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver."));
618         return false;
619     }
620 
621     const QString renderer =
622         QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
623 
624     if (!GLAD_GL_VERSION_4_3) {
625         LOG_ERROR(Frontend, "GPU does not support OpenGL 4.3: {}", renderer.toStdString());
626         QMessageBox::warning(this, tr("Error while initializing OpenGL 4.3!"),
627                              tr("Your GPU may not support OpenGL 4.3, or you do not have the "
628                                 "latest graphics driver.<br><br>GL Renderer:<br>%1")
629                                  .arg(renderer));
630         return false;
631     }
632 
633     QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
634     if (!unsupported_gl_extensions.empty()) {
635         QMessageBox::warning(
636             this, tr("Error while initializing OpenGL!"),
637             tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
638                "have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported "
639                "extensions:<br>%2")
640                 .arg(renderer)
641                 .arg(unsupported_gl_extensions.join(QStringLiteral("<br>"))));
642         return false;
643     }
644     return true;
645 }
646 
GetUnsupportedGLExtensions() const647 QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
648     QStringList unsupported_ext;
649 
650     if (!GLAD_GL_ARB_buffer_storage)
651         unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
652     if (!GLAD_GL_ARB_direct_state_access)
653         unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
654     if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
655         unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
656     if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
657         unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
658     if (!GLAD_GL_ARB_multi_bind)
659         unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
660     if (!GLAD_GL_ARB_clip_control)
661         unsupported_ext.append(QStringLiteral("ARB_clip_control"));
662 
663     // Extensions required to support some texture formats.
664     if (!GLAD_GL_EXT_texture_compression_s3tc)
665         unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
666     if (!GLAD_GL_ARB_texture_compression_rgtc)
667         unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
668     if (!GLAD_GL_ARB_depth_buffer_float)
669         unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
670 
671     if (!unsupported_ext.empty()) {
672         LOG_ERROR(Frontend, "GPU does not support all required extensions: {}",
673                   glGetString(GL_RENDERER));
674     }
675     for (const QString& ext : unsupported_ext) {
676         LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString());
677     }
678 
679     return unsupported_ext;
680 }
681 
OnEmulationStarting(EmuThread * emu_thread)682 void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
683     this->emu_thread = emu_thread;
684 }
685 
OnEmulationStopping()686 void GRenderWindow::OnEmulationStopping() {
687     emu_thread = nullptr;
688 }
689 
showEvent(QShowEvent * event)690 void GRenderWindow::showEvent(QShowEvent* event) {
691     QWidget::showEvent(event);
692 
693     // windowHandle() is not initialized until the Window is shown, so we connect it here.
694     connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged,
695             Qt::UniqueConnection);
696 }
697