bootmanager.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. // Copyright 2014 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <glad/glad.h>
  5. #include <QApplication>
  6. #include <QHBoxLayout>
  7. #include <QKeyEvent>
  8. #include <QMessageBox>
  9. #include <QOffscreenSurface>
  10. #include <QOpenGLWindow>
  11. #include <QPainter>
  12. #include <QScreen>
  13. #include <QStringList>
  14. #include <QWindow>
  15. #ifdef HAS_VULKAN
  16. #include <QVulkanWindow>
  17. #endif
  18. #include <fmt/format.h>
  19. #include "common/assert.h"
  20. #include "common/microprofile.h"
  21. #include "common/scm_rev.h"
  22. #include "core/core.h"
  23. #include "core/frontend/framebuffer_layout.h"
  24. #include "core/frontend/scope_acquire_window_context.h"
  25. #include "core/settings.h"
  26. #include "input_common/keyboard.h"
  27. #include "input_common/main.h"
  28. #include "input_common/motion_emu.h"
  29. #include "video_core/renderer_base.h"
  30. #include "video_core/video_core.h"
  31. #include "yuzu/bootmanager.h"
  32. #include "yuzu/main.h"
  33. EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
  34. EmuThread::~EmuThread() = default;
  35. void EmuThread::run() {
  36. render_window->MakeCurrent();
  37. MicroProfileOnThreadCreate("EmuThread");
  38. emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
  39. Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
  40. stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
  41. emit LoadProgress(stage, value, total);
  42. });
  43. emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
  44. if (Settings::values.use_asynchronous_gpu_emulation) {
  45. // Release OpenGL context for the GPU thread
  46. render_window->DoneCurrent();
  47. }
  48. // Holds whether the cpu was running during the last iteration,
  49. // so that the DebugModeLeft signal can be emitted before the
  50. // next execution step
  51. bool was_active = false;
  52. while (!stop_run) {
  53. if (running) {
  54. if (!was_active)
  55. emit DebugModeLeft();
  56. Core::System::ResultStatus result = Core::System::GetInstance().RunLoop();
  57. if (result != Core::System::ResultStatus::Success) {
  58. this->SetRunning(false);
  59. emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails());
  60. }
  61. was_active = running || exec_step;
  62. if (!was_active && !stop_run)
  63. emit DebugModeEntered();
  64. } else if (exec_step) {
  65. if (!was_active)
  66. emit DebugModeLeft();
  67. exec_step = false;
  68. Core::System::GetInstance().SingleStep();
  69. emit DebugModeEntered();
  70. yieldCurrentThread();
  71. was_active = false;
  72. } else {
  73. std::unique_lock lock{running_mutex};
  74. running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
  75. }
  76. }
  77. // Shutdown the core emulation
  78. Core::System::GetInstance().Shutdown();
  79. #if MICROPROFILE_ENABLED
  80. MicroProfileOnThreadExit();
  81. #endif
  82. render_window->moveContext();
  83. }
  84. class GGLContext : public Core::Frontend::GraphicsContext {
  85. public:
  86. explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} {
  87. context.setFormat(shared_context->format());
  88. context.setShareContext(shared_context);
  89. context.create();
  90. }
  91. void MakeCurrent() override {
  92. context.makeCurrent(shared_context->surface());
  93. }
  94. void DoneCurrent() override {
  95. context.doneCurrent();
  96. }
  97. void SwapBuffers() override {}
  98. private:
  99. QOpenGLContext* shared_context;
  100. QOpenGLContext context;
  101. };
  102. class GWidgetInternal : public QWindow {
  103. public:
  104. GWidgetInternal(GRenderWindow* parent) : parent(parent) {}
  105. virtual ~GWidgetInternal() = default;
  106. void resizeEvent(QResizeEvent* ev) override {
  107. parent->OnClientAreaResized(ev->size().width(), ev->size().height());
  108. parent->OnFramebufferSizeChanged();
  109. }
  110. void keyPressEvent(QKeyEvent* event) override {
  111. InputCommon::GetKeyboard()->PressKey(event->key());
  112. }
  113. void keyReleaseEvent(QKeyEvent* event) override {
  114. InputCommon::GetKeyboard()->ReleaseKey(event->key());
  115. }
  116. void mousePressEvent(QMouseEvent* event) override {
  117. if (event->source() == Qt::MouseEventSynthesizedBySystem)
  118. return; // touch input is handled in TouchBeginEvent
  119. const auto pos{event->pos()};
  120. if (event->button() == Qt::LeftButton) {
  121. const auto [x, y] = parent->ScaleTouch(pos);
  122. parent->TouchPressed(x, y);
  123. } else if (event->button() == Qt::RightButton) {
  124. InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
  125. }
  126. }
  127. void mouseMoveEvent(QMouseEvent* event) override {
  128. if (event->source() == Qt::MouseEventSynthesizedBySystem)
  129. return; // touch input is handled in TouchUpdateEvent
  130. const auto pos{event->pos()};
  131. const auto [x, y] = parent->ScaleTouch(pos);
  132. parent->TouchMoved(x, y);
  133. InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
  134. }
  135. void mouseReleaseEvent(QMouseEvent* event) override {
  136. if (event->source() == Qt::MouseEventSynthesizedBySystem)
  137. return; // touch input is handled in TouchEndEvent
  138. if (event->button() == Qt::LeftButton)
  139. parent->TouchReleased();
  140. else if (event->button() == Qt::RightButton)
  141. InputCommon::GetMotionEmu()->EndTilt();
  142. }
  143. void DisablePainting() {
  144. do_painting = false;
  145. }
  146. void EnablePainting() {
  147. do_painting = true;
  148. }
  149. std::pair<unsigned, unsigned> GetSize() const {
  150. return std::make_pair(width(), height());
  151. }
  152. protected:
  153. bool IsPaintingEnabled() const {
  154. return do_painting;
  155. }
  156. private:
  157. GRenderWindow* parent;
  158. bool do_painting = false;
  159. };
  160. // This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
  161. // context.
  162. // The corresponding functionality is handled in EmuThread instead
  163. class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow {
  164. public:
  165. GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
  166. : GWidgetInternal(parent), QOpenGLWindow(shared_context) {}
  167. ~GGLWidgetInternal() override = default;
  168. void paintEvent(QPaintEvent* ev) override {
  169. if (IsPaintingEnabled()) {
  170. QPainter painter(this);
  171. }
  172. }
  173. };
  174. #ifdef HAS_VULKAN
  175. class GVKWidgetInternal final : public GWidgetInternal {
  176. public:
  177. GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) {
  178. setSurfaceType(QSurface::SurfaceType::VulkanSurface);
  179. setVulkanInstance(instance);
  180. }
  181. ~GVKWidgetInternal() override = default;
  182. };
  183. #endif
  184. GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread)
  185. : QWidget(parent), emu_thread(emu_thread) {
  186. setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
  187. .arg(QString::fromUtf8(Common::g_build_name),
  188. QString::fromUtf8(Common::g_scm_branch),
  189. QString::fromUtf8(Common::g_scm_desc)));
  190. setAttribute(Qt::WA_AcceptTouchEvents);
  191. InputCommon::Init();
  192. connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
  193. }
  194. GRenderWindow::~GRenderWindow() {
  195. InputCommon::Shutdown();
  196. // Avoid an unordered destruction that generates a segfault
  197. delete child;
  198. }
  199. void GRenderWindow::moveContext() {
  200. if (!context) {
  201. return;
  202. }
  203. DoneCurrent();
  204. // If the thread started running, move the GL Context to the new thread. Otherwise, move it
  205. // back.
  206. auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
  207. ? emu_thread
  208. : qApp->thread();
  209. context->moveToThread(thread);
  210. }
  211. void GRenderWindow::SwapBuffers() {
  212. if (context) {
  213. context->swapBuffers(child);
  214. }
  215. if (!first_frame) {
  216. first_frame = true;
  217. emit FirstFrameDisplayed();
  218. }
  219. }
  220. void GRenderWindow::MakeCurrent() {
  221. if (context) {
  222. context->makeCurrent(child);
  223. }
  224. }
  225. void GRenderWindow::DoneCurrent() {
  226. if (context) {
  227. context->doneCurrent();
  228. }
  229. }
  230. void GRenderWindow::PollEvents() {}
  231. bool GRenderWindow::IsShown() const {
  232. return !isMinimized();
  233. }
  234. void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance,
  235. void* surface) const {
  236. #ifdef HAS_VULKAN
  237. const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr");
  238. const VkInstance instance_copy = vk_instance->vkInstance();
  239. const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child);
  240. std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr));
  241. std::memcpy(instance, &instance_copy, sizeof(instance_copy));
  242. std::memcpy(surface, &surface_copy, sizeof(surface_copy));
  243. #else
  244. UNREACHABLE_MSG("Executing Vulkan code without compiling Vulkan");
  245. #endif
  246. }
  247. // On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
  248. //
  249. // Older versions get the window size (density independent pixels),
  250. // and hence, do not support DPI scaling ("retina" displays).
  251. // The result will be a viewport that is smaller than the extent of the window.
  252. void GRenderWindow::OnFramebufferSizeChanged() {
  253. // Screen changes potentially incur a change in screen DPI, hence we should update the
  254. // framebuffer size
  255. const qreal pixelRatio{GetWindowPixelRatio()};
  256. const auto size{child->GetSize()};
  257. UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio);
  258. }
  259. void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
  260. if (child) {
  261. child->keyPressEvent(event);
  262. }
  263. }
  264. void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
  265. if (child) {
  266. child->keyReleaseEvent(event);
  267. }
  268. }
  269. void GRenderWindow::BackupGeometry() {
  270. geometry = QWidget::saveGeometry();
  271. }
  272. void GRenderWindow::RestoreGeometry() {
  273. // We don't want to back up the geometry here (obviously)
  274. QWidget::restoreGeometry(geometry);
  275. }
  276. void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
  277. // Make sure users of this class don't need to deal with backing up the geometry themselves
  278. QWidget::restoreGeometry(geometry);
  279. BackupGeometry();
  280. }
  281. QByteArray GRenderWindow::saveGeometry() {
  282. // If we are a top-level widget, store the current geometry
  283. // otherwise, store the last backup
  284. if (parent() == nullptr) {
  285. return QWidget::saveGeometry();
  286. }
  287. return geometry;
  288. }
  289. qreal GRenderWindow::GetWindowPixelRatio() const {
  290. // windowHandle() might not be accessible until the window is displayed to screen.
  291. return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
  292. }
  293. std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const {
  294. const qreal pixel_ratio{GetWindowPixelRatio()};
  295. return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
  296. static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
  297. }
  298. void GRenderWindow::closeEvent(QCloseEvent* event) {
  299. emit Closed();
  300. QWidget::closeEvent(event);
  301. }
  302. void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
  303. // TouchBegin always has exactly one touch point, so take the .first()
  304. const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
  305. this->TouchPressed(x, y);
  306. }
  307. void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
  308. QPointF pos;
  309. int active_points = 0;
  310. // average all active touch points
  311. for (const auto tp : event->touchPoints()) {
  312. if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
  313. active_points++;
  314. pos += tp.pos();
  315. }
  316. }
  317. pos /= active_points;
  318. const auto [x, y] = ScaleTouch(pos);
  319. this->TouchMoved(x, y);
  320. }
  321. void GRenderWindow::TouchEndEvent() {
  322. this->TouchReleased();
  323. }
  324. bool GRenderWindow::event(QEvent* event) {
  325. if (event->type() == QEvent::TouchBegin) {
  326. TouchBeginEvent(static_cast<QTouchEvent*>(event));
  327. return true;
  328. } else if (event->type() == QEvent::TouchUpdate) {
  329. TouchUpdateEvent(static_cast<QTouchEvent*>(event));
  330. return true;
  331. } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) {
  332. TouchEndEvent();
  333. return true;
  334. }
  335. return QWidget::event(event);
  336. }
  337. void GRenderWindow::focusOutEvent(QFocusEvent* event) {
  338. QWidget::focusOutEvent(event);
  339. InputCommon::GetKeyboard()->ReleaseAllKeys();
  340. }
  341. void GRenderWindow::OnClientAreaResized(u32 width, u32 height) {
  342. NotifyClientAreaSizeChanged(std::make_pair(width, height));
  343. }
  344. std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
  345. return std::make_unique<GGLContext>(context.get());
  346. }
  347. bool GRenderWindow::InitRenderTarget() {
  348. shared_context.reset();
  349. context.reset();
  350. if (child) {
  351. delete child;
  352. }
  353. if (container) {
  354. delete container;
  355. }
  356. if (layout()) {
  357. delete layout();
  358. }
  359. first_frame = false;
  360. switch (Settings::values.renderer_backend) {
  361. case Settings::RendererBackend::OpenGL:
  362. if (!InitializeOpenGL()) {
  363. return false;
  364. }
  365. break;
  366. case Settings::RendererBackend::Vulkan:
  367. if (!InitializeVulkan()) {
  368. return false;
  369. }
  370. break;
  371. }
  372. container = QWidget::createWindowContainer(child, this);
  373. QBoxLayout* layout = new QHBoxLayout(this);
  374. layout->addWidget(container);
  375. layout->setMargin(0);
  376. setLayout(layout);
  377. // Reset minimum required size to avoid resizing issues on the main window after restarting.
  378. setMinimumSize(1, 1);
  379. // Show causes the window to actually be created and the gl context as well, but we don't want
  380. // the widget to be shown yet, so immediately hide it.
  381. show();
  382. hide();
  383. resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
  384. child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
  385. container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
  386. OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
  387. OnFramebufferSizeChanged();
  388. NotifyClientAreaSizeChanged(child->GetSize());
  389. BackupGeometry();
  390. if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) {
  391. if (!LoadOpenGL()) {
  392. return false;
  393. }
  394. }
  395. return true;
  396. }
  397. void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
  398. auto& renderer = Core::System::GetInstance().Renderer();
  399. if (res_scale == 0) {
  400. res_scale = VideoCore::GetResolutionScaleFactor(renderer);
  401. }
  402. const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
  403. screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
  404. renderer.RequestScreenshot(
  405. screenshot_image.bits(),
  406. [=] {
  407. const std::string std_screenshot_path = screenshot_path.toStdString();
  408. if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
  409. LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
  410. } else {
  411. LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);
  412. }
  413. },
  414. layout);
  415. }
  416. void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
  417. setMinimumSize(minimal_size.first, minimal_size.second);
  418. }
  419. bool GRenderWindow::InitializeOpenGL() {
  420. // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
  421. // WA_DontShowOnScreen, WA_DeleteOnClose
  422. QSurfaceFormat fmt;
  423. fmt.setVersion(4, 3);
  424. fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
  425. fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
  426. // TODO: expose a setting for buffer value (ie default/single/double/triple)
  427. fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
  428. shared_context = std::make_unique<QOpenGLContext>();
  429. shared_context->setFormat(fmt);
  430. shared_context->create();
  431. context = std::make_unique<QOpenGLContext>();
  432. context->setShareContext(shared_context.get());
  433. context->setFormat(fmt);
  434. context->create();
  435. fmt.setSwapInterval(false);
  436. child = new GGLWidgetInternal(this, shared_context.get());
  437. return true;
  438. }
  439. bool GRenderWindow::InitializeVulkan() {
  440. #ifdef HAS_VULKAN
  441. vk_instance = std::make_unique<QVulkanInstance>();
  442. vk_instance->setApiVersion(QVersionNumber(1, 1, 0));
  443. vk_instance->setFlags(QVulkanInstance::Flag::NoDebugOutputRedirect);
  444. if (Settings::values.renderer_debug) {
  445. const auto supported_layers{vk_instance->supportedLayers()};
  446. const bool found =
  447. std::find_if(supported_layers.begin(), supported_layers.end(), [](const auto& layer) {
  448. constexpr const char searched_layer[] = "VK_LAYER_LUNARG_standard_validation";
  449. return layer.name == searched_layer;
  450. });
  451. if (found) {
  452. vk_instance->setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
  453. vk_instance->setExtensions(QByteArrayList() << VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
  454. }
  455. }
  456. if (!vk_instance->create()) {
  457. QMessageBox::critical(
  458. this, tr("Error while initializing Vulkan 1.1!"),
  459. tr("Your OS doesn't seem to support Vulkan 1.1 instances, or you do not have the "
  460. "latest graphics drivers."));
  461. return false;
  462. }
  463. child = new GVKWidgetInternal(this, vk_instance.get());
  464. return true;
  465. #else
  466. QMessageBox::critical(this, tr("Vulkan not available!"),
  467. tr("yuzu has not been compiled with Vulkan support."));
  468. return false;
  469. #endif
  470. }
  471. bool GRenderWindow::LoadOpenGL() {
  472. Core::Frontend::ScopeAcquireWindowContext acquire_context{*this};
  473. if (!gladLoadGL()) {
  474. QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"),
  475. tr("Your GPU may not support OpenGL 4.3, or you do not have the "
  476. "latest graphics driver."));
  477. return false;
  478. }
  479. QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
  480. if (!unsupported_gl_extensions.empty()) {
  481. QMessageBox::critical(
  482. this, tr("Error while initializing OpenGL!"),
  483. tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
  484. "have the latest graphics driver.<br><br>Unsupported extensions:<br>") +
  485. unsupported_gl_extensions.join(QStringLiteral("<br>")));
  486. return false;
  487. }
  488. return true;
  489. }
  490. QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
  491. QStringList unsupported_ext;
  492. if (!GLAD_GL_ARB_buffer_storage)
  493. unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
  494. if (!GLAD_GL_ARB_direct_state_access)
  495. unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
  496. if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
  497. unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
  498. if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
  499. unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
  500. if (!GLAD_GL_ARB_multi_bind)
  501. unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
  502. if (!GLAD_GL_ARB_clip_control)
  503. unsupported_ext.append(QStringLiteral("ARB_clip_control"));
  504. // Extensions required to support some texture formats.
  505. if (!GLAD_GL_EXT_texture_compression_s3tc)
  506. unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
  507. if (!GLAD_GL_ARB_texture_compression_rgtc)
  508. unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
  509. if (!GLAD_GL_ARB_depth_buffer_float)
  510. unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
  511. for (const QString& ext : unsupported_ext)
  512. LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
  513. return unsupported_ext;
  514. }
  515. void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
  516. this->emu_thread = emu_thread;
  517. child->DisablePainting();
  518. }
  519. void GRenderWindow::OnEmulationStopping() {
  520. emu_thread = nullptr;
  521. child->EnablePainting();
  522. }
  523. void GRenderWindow::showEvent(QShowEvent* event) {
  524. QWidget::showEvent(event);
  525. // windowHandle() is not initialized until the Window is shown, so we connect it here.
  526. connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged,
  527. Qt::UniqueConnection);
  528. }