| |
| |
| |
|
|
| #include <QApplication> |
| #include <QHBoxLayout> |
| #include <QKeyEvent> |
| #include <QMessageBox> |
| #include <QPainter> |
| #include <QWindow> |
| #include "citra_qt/bootmanager.h" |
| #include "citra_qt/main.h" |
| #include "common/color.h" |
| #include "common/microprofile.h" |
| #include "common/scm_rev.h" |
| #include "common/settings.h" |
| #include "core/3ds.h" |
| #include "core/core.h" |
| #include "core/frontend/framebuffer_layout.h" |
| #include "core/perf_stats.h" |
| #include "input_common/keyboard.h" |
| #include "input_common/main.h" |
| #include "input_common/motion_emu.h" |
| #include "video_core/custom_textures/custom_tex_manager.h" |
| #include "video_core/gpu.h" |
| #include "video_core/renderer_base.h" |
| #include "video_core/renderer_software/renderer_software.h" |
|
|
| #ifdef ENABLE_OPENGL |
| #include <glad/glad.h> |
|
|
| #include <QOffscreenSurface> |
| #include <QOpenGLContext> |
| #endif |
|
|
| #if defined(__APPLE__) |
| #include <objc/message.h> |
| #include <objc/objc.h> |
| #endif |
|
|
| #if !defined(WIN32) |
| #include <qpa/qplatformnativeinterface.h> |
| #endif |
|
|
| static Frontend::WindowSystemType GetWindowSystemType(); |
|
|
| EmuThread::EmuThread(Core::System& system_, Frontend::GraphicsContext& core_context) |
| : system{system_}, core_context(core_context) {} |
|
|
| EmuThread::~EmuThread() = default; |
|
|
| static GMainWindow* GetMainWindow() { |
| const auto widgets = qApp->topLevelWidgets(); |
| for (QWidget* w : widgets) { |
| if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) { |
| return main; |
| } |
| } |
|
|
| return nullptr; |
| } |
|
|
| void EmuThread::run() { |
| MicroProfileOnThreadCreate("EmuThread"); |
| const auto scope = core_context.Acquire(); |
|
|
| if (Settings::values.preload_textures) { |
| emit LoadProgress(VideoCore::LoadCallbackStage::Preload, 0, 0); |
| system.CustomTexManager().PreloadTextures( |
| stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, |
| std::size_t total) { emit LoadProgress(stage, value, total); }); |
| } |
|
|
| emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); |
|
|
| system.GPU().Renderer().Rasterizer()->LoadDiskResources( |
| stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { |
| emit LoadProgress(stage, value, total); |
| }); |
|
|
| emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); |
|
|
| core_context.MakeCurrent(); |
|
|
| if (system.frame_limiter.IsFrameAdvancing()) { |
| |
| |
| emit HideLoadingScreen(); |
| system.frame_limiter.WaitOnce(); |
| } |
|
|
| |
| |
| |
| bool was_active = false; |
| while (!stop_run) { |
| if (running) { |
| if (!was_active) |
| emit DebugModeLeft(); |
|
|
| const Core::System::ResultStatus result = system.RunLoop(); |
| if (result == Core::System::ResultStatus::ShutdownRequested) { |
| |
| emit ErrorThrown(result, ""); |
| |
| break; |
| } |
| if (result != Core::System::ResultStatus::Success) { |
| this->SetRunning(false); |
| emit ErrorThrown(result, system.GetStatusDetails()); |
| } |
|
|
| was_active = running || exec_step; |
| if (!was_active && !stop_run) |
| emit DebugModeEntered(); |
| } else if (exec_step) { |
| if (!was_active) |
| emit DebugModeLeft(); |
|
|
| exec_step = false; |
| [[maybe_unused]] const Core::System::ResultStatus result = system.SingleStep(); |
| emit DebugModeEntered(); |
| yieldCurrentThread(); |
|
|
| was_active = false; |
| } else { |
| std::unique_lock lock{running_mutex}; |
| running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; }); |
| } |
| } |
|
|
| |
| system.Shutdown(); |
|
|
| #if MICROPROFILE_ENABLED |
| MicroProfileOnThreadExit(); |
| #endif |
| } |
|
|
| #ifdef ENABLE_OPENGL |
| static std::unique_ptr<QOpenGLContext> CreateQOpenGLContext(bool gles) { |
| QSurfaceFormat format; |
| if (gles) { |
| format.setRenderableType(QSurfaceFormat::RenderableType::OpenGLES); |
| format.setVersion(3, 2); |
| } else { |
| format.setRenderableType(QSurfaceFormat::RenderableType::OpenGL); |
| format.setVersion(4, 3); |
| } |
| format.setProfile(QSurfaceFormat::CoreProfile); |
|
|
| if (Settings::values.renderer_debug) { |
| format.setOption(QSurfaceFormat::FormatOption::DebugContext); |
| } |
|
|
| |
| format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); |
| format.setSwapInterval(0); |
|
|
| auto context = std::make_unique<QOpenGLContext>(); |
| context->setFormat(format); |
| if (!context->create()) { |
| LOG_ERROR(Frontend, "Unable to create OpenGL context with GLES = {}", gles); |
| return nullptr; |
| } |
| return context; |
| } |
|
|
| class OpenGLSharedContext : public Frontend::GraphicsContext { |
| public: |
| |
| explicit OpenGLSharedContext() { |
| |
| context = CreateQOpenGLContext(Settings::values.use_gles.GetValue()); |
| if (context == nullptr) { |
| |
| context = CreateQOpenGLContext(!Settings::values.use_gles.GetValue()); |
| if (context == nullptr) { |
| LOG_ERROR(Frontend, "Unable to create any OpenGL context."); |
| } |
| } |
|
|
| offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr); |
| offscreen_surface->setFormat(context->format()); |
| offscreen_surface->create(); |
| surface = offscreen_surface.get(); |
| } |
|
|
| |
| explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface) { |
|
|
| |
| auto format = share_context->format(); |
| format.setSwapInterval(0); |
|
|
| context = std::make_unique<QOpenGLContext>(); |
| context->setShareContext(share_context); |
| context->setFormat(format); |
| if (!context->create()) { |
| LOG_ERROR(Frontend, "Unable to create shared OpenGL context"); |
| } |
|
|
| surface = main_surface; |
| } |
|
|
| ~OpenGLSharedContext() { |
| OpenGLSharedContext::DoneCurrent(); |
| } |
|
|
| bool IsGLES() override { |
| return context->format().renderableType() == QSurfaceFormat::RenderableType::OpenGLES; |
| } |
|
|
| void SwapBuffers() override { |
| context->swapBuffers(surface); |
| } |
|
|
| void MakeCurrent() override { |
| |
| |
| |
| |
| |
| |
| if (QOpenGLContext::currentContext() != context.get()) { |
| context->makeCurrent(surface); |
| } |
| } |
|
|
| void DoneCurrent() override { |
| if (QOpenGLContext::currentContext() == context.get()) { |
| context->doneCurrent(); |
| } |
| } |
|
|
| QOpenGLContext* GetShareContext() const { |
| return context.get(); |
| } |
|
|
| private: |
| |
| |
| std::unique_ptr<QOpenGLContext> context; |
| std::unique_ptr<QOffscreenSurface> offscreen_surface{}; |
| QSurface* surface; |
| }; |
| #endif |
|
|
| class DummyContext : public Frontend::GraphicsContext {}; |
|
|
| class RenderWidget : public QWidget { |
| public: |
| RenderWidget(GRenderWindow* parent) : QWidget(parent) { |
| setMouseTracking(true); |
| update(); |
| } |
|
|
| virtual ~RenderWidget() = default; |
| }; |
|
|
| #ifdef ENABLE_OPENGL |
| class OpenGLRenderWidget : public RenderWidget { |
| public: |
| explicit OpenGLRenderWidget(GRenderWindow* parent, Core::System& system_, bool is_secondary) |
| : RenderWidget(parent), system(system_), is_secondary(is_secondary) { |
| setAttribute(Qt::WA_NativeWindow); |
| setAttribute(Qt::WA_PaintOnScreen); |
| if (GetWindowSystemType() == Frontend::WindowSystemType::Wayland) { |
| setAttribute(Qt::WA_DontCreateNativeAncestors); |
| } |
| windowHandle()->setSurfaceType(QWindow::OpenGLSurface); |
| } |
|
|
| void SetContext(std::unique_ptr<Frontend::GraphicsContext>&& context_) { |
| context = std::move(context_); |
| } |
|
|
| void Present() { |
| if (!isVisible()) { |
| return; |
| } |
| if (!system.IsPoweredOn()) { |
| return; |
| } |
| context->MakeCurrent(); |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); |
| system.GPU().Renderer().TryPresent(100, is_secondary); |
| context->SwapBuffers(); |
| glFinish(); |
| } |
|
|
| void paintEvent(QPaintEvent* event) override { |
| Present(); |
| update(); |
| } |
|
|
| QPaintEngine* paintEngine() const override { |
| return nullptr; |
| } |
|
|
| private: |
| std::unique_ptr<Frontend::GraphicsContext> context{}; |
| Core::System& system; |
| bool is_secondary; |
| }; |
| #endif |
|
|
| #ifdef ENABLE_VULKAN |
| class VulkanRenderWidget : public RenderWidget { |
| public: |
| explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) { |
| setAttribute(Qt::WA_NativeWindow); |
| setAttribute(Qt::WA_PaintOnScreen); |
| if (GetWindowSystemType() == Frontend::WindowSystemType::Wayland) { |
| setAttribute(Qt::WA_DontCreateNativeAncestors); |
| } |
| #ifdef __APPLE__ |
| windowHandle()->setSurfaceType(QWindow::MetalSurface); |
| #else |
| windowHandle()->setSurfaceType(QWindow::VulkanSurface); |
| #endif |
| } |
|
|
| QPaintEngine* paintEngine() const override { |
| return nullptr; |
| } |
| }; |
| #endif |
|
|
| #ifdef ENABLE_SOFTWARE_RENDERER |
| struct SoftwareRenderWidget : public RenderWidget { |
| explicit SoftwareRenderWidget(GRenderWindow* parent, Core::System& system_) |
| : RenderWidget(parent), system(system_) {} |
|
|
| void Present() { |
| if (!isVisible()) { |
| return; |
| } |
| if (!system.IsPoweredOn()) { |
| return; |
| } |
|
|
| using VideoCore::ScreenId; |
|
|
| const auto layout{Layout::DefaultFrameLayout(width(), height(), false, false)}; |
| QPainter painter(this); |
|
|
| const auto draw_screen = [&](ScreenId screen_id) { |
| const auto rect = |
| screen_id == ScreenId::TopLeft ? layout.top_screen : layout.bottom_screen; |
| const QImage screen = |
| LoadFramebuffer(screen_id).scaled(rect.GetWidth(), rect.GetHeight()); |
| painter.drawImage(rect.left, rect.top, screen); |
| }; |
|
|
| painter.fillRect(rect(), qRgb(Settings::values.bg_red.GetValue() * 255, |
| Settings::values.bg_green.GetValue() * 255, |
| Settings::values.bg_blue.GetValue() * 255)); |
| draw_screen(ScreenId::TopLeft); |
| draw_screen(ScreenId::Bottom); |
|
|
| painter.end(); |
| } |
|
|
| void paintEvent(QPaintEvent* event) override { |
| Present(); |
| update(); |
| } |
|
|
| QImage LoadFramebuffer(VideoCore::ScreenId screen_id) { |
| const auto& renderer = static_cast<SwRenderer::RendererSoftware&>(system.GPU().Renderer()); |
| const auto& info = renderer.Screen(screen_id); |
| const int width = static_cast<int>(info.width); |
| const int height = static_cast<int>(info.height); |
| QImage image{height, width, QImage::Format_RGBA8888}; |
| std::memcpy(image.bits(), info.pixels.data(), info.pixels.size()); |
| return image; |
| } |
|
|
| private: |
| Core::System& system; |
| }; |
| #endif |
|
|
| static Frontend::WindowSystemType GetWindowSystemType() { |
| |
| const QString platform_name = QGuiApplication::platformName(); |
| if (platform_name == QStringLiteral("windows")) |
| return Frontend::WindowSystemType::Windows; |
| else if (platform_name == QStringLiteral("xcb")) |
| return Frontend::WindowSystemType::X11; |
| else if (platform_name == QStringLiteral("wayland") || |
| platform_name == QStringLiteral("wayland-egl")) |
| return Frontend::WindowSystemType::Wayland; |
| else if (platform_name == QStringLiteral("cocoa") || platform_name == QStringLiteral("ios")) |
| return Frontend::WindowSystemType::MacOS; |
|
|
| LOG_CRITICAL(Frontend, "Unknown Qt platform!"); |
| return Frontend::WindowSystemType::Windows; |
| } |
|
|
| static Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) { |
| Frontend::EmuWindow::WindowSystemInfo wsi; |
| wsi.type = GetWindowSystemType(); |
|
|
| if (window) { |
| #if defined(WIN32) |
| |
| wsi.render_surface = reinterpret_cast<void*>(window->winId()); |
| #elif defined(__APPLE__) |
| wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)( |
| reinterpret_cast<id>(window->winId()), sel_registerName("layer")); |
| #else |
| QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); |
| wsi.display_connection = pni->nativeResourceForWindow("display", window); |
| if (wsi.type == Frontend::WindowSystemType::Wayland) |
| wsi.render_surface = pni->nativeResourceForWindow("surface", window); |
| else |
| wsi.render_surface = reinterpret_cast<void*>(window->winId()); |
| #endif |
| wsi.render_surface_scale = static_cast<float>(window->devicePixelRatio()); |
| } else { |
| wsi.render_surface = nullptr; |
| wsi.render_surface_scale = 1.0f; |
| } |
|
|
| return wsi; |
| } |
|
|
| std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::main_context; |
|
|
| GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread_, Core::System& system_, |
| bool is_secondary_) |
| : QWidget(parent_), EmuWindow(is_secondary_), emu_thread(emu_thread_), system{system_} { |
|
|
| setAttribute(Qt::WA_AcceptTouchEvents); |
| auto layout = new QHBoxLayout(this); |
| layout->setContentsMargins(0, 0, 0, 0); |
| setLayout(layout); |
|
|
| this->setMouseTracking(true); |
| strict_context_required = QGuiApplication::platformName() == QStringLiteral("wayland") || |
| QGuiApplication::platformName() == QStringLiteral("wayland-egl"); |
|
|
| GMainWindow* parent = GetMainWindow(); |
| connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); |
| } |
|
|
| GRenderWindow::~GRenderWindow() = default; |
|
|
| void GRenderWindow::MakeCurrent() { |
| main_context->MakeCurrent(); |
| } |
|
|
| void GRenderWindow::DoneCurrent() { |
| main_context->DoneCurrent(); |
| } |
|
|
| void GRenderWindow::PollEvents() { |
| if (!first_frame) { |
| first_frame = true; |
| emit FirstFrameDisplayed(); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| void GRenderWindow::OnFramebufferSizeChanged() { |
| |
| |
| const qreal pixel_ratio = windowPixelRatio(); |
| const u32 width = static_cast<u32>(this->width() * pixel_ratio); |
| const u32 height = static_cast<u32>(this->height() * pixel_ratio); |
| UpdateCurrentFramebufferLayout(width, height); |
| } |
|
|
| void GRenderWindow::BackupGeometry() { |
| geometry = QWidget::saveGeometry(); |
| } |
|
|
| void GRenderWindow::RestoreGeometry() { |
| |
| QWidget::restoreGeometry(geometry); |
| } |
|
|
| void GRenderWindow::restoreGeometry(const QByteArray& geometry) { |
| |
| QWidget::restoreGeometry(geometry); |
| BackupGeometry(); |
| } |
|
|
| QByteArray GRenderWindow::saveGeometry() { |
| |
| |
| if (parent() == nullptr) { |
| return QWidget::saveGeometry(); |
| } |
|
|
| return geometry; |
| } |
|
|
| qreal GRenderWindow::windowPixelRatio() const { |
| return devicePixelRatioF(); |
| } |
|
|
| std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { |
| 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}))}; |
| } |
|
|
| void GRenderWindow::closeEvent(QCloseEvent* event) { |
| emit Closed(); |
| 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; |
| } |
|
|
| 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()); |
| } |
| emit MouseActivity(); |
| } |
|
|
| void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { |
| if (event->source() == Qt::MouseEventSynthesizedBySystem) { |
| return; |
| } |
|
|
| auto pos = event->pos(); |
| const auto [x, y] = ScaleTouch(pos); |
| this->TouchMoved(x, y); |
| InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); |
| emit MouseActivity(); |
| } |
|
|
| void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { |
| if (event->source() == Qt::MouseEventSynthesizedBySystem) { |
| return; |
| } |
|
|
| if (event->button() == Qt::LeftButton) |
| this->TouchReleased(); |
| else if (event->button() == Qt::RightButton) |
| InputCommon::GetMotionEmu()->EndTilt(); |
| emit MouseActivity(); |
| } |
|
|
| void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { |
| |
| const auto [x, y] = ScaleTouch(event->points().first().position()); |
| this->TouchPressed(x, y); |
| } |
|
|
| void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) { |
| QPointF pos; |
| int active_points = 0; |
|
|
| |
| for (const auto& tp : event->points()) { |
| if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) { |
| active_points++; |
| pos += tp.position(); |
| } |
| } |
|
|
| pos /= active_points; |
|
|
| const auto [x, y] = ScaleTouch(pos); |
| this->TouchMoved(x, y); |
| } |
|
|
| void GRenderWindow::TouchEndEvent() { |
| this->TouchReleased(); |
| } |
|
|
| bool GRenderWindow::event(QEvent* event) { |
| switch (event->type()) { |
| case QEvent::TouchBegin: |
| TouchBeginEvent(static_cast<QTouchEvent*>(event)); |
| return true; |
| case QEvent::TouchUpdate: |
| TouchUpdateEvent(static_cast<QTouchEvent*>(event)); |
| return true; |
| case QEvent::TouchEnd: |
| case QEvent::TouchCancel: |
| TouchEndEvent(); |
| return true; |
| default: |
| break; |
| } |
|
|
| return QWidget::event(event); |
| } |
|
|
| void GRenderWindow::focusOutEvent(QFocusEvent* event) { |
| QWidget::focusOutEvent(event); |
| if (auto* keyboard = InputCommon::GetKeyboard(); keyboard) { |
| keyboard->ReleaseAllKeys(); |
| } |
| has_focus = false; |
| } |
|
|
| void GRenderWindow::focusInEvent(QFocusEvent* event) { |
| QWidget::focusInEvent(event); |
| has_focus = true; |
| } |
|
|
| void GRenderWindow::resizeEvent(QResizeEvent* event) { |
| QWidget::resizeEvent(event); |
| OnFramebufferSizeChanged(); |
| } |
|
|
| bool GRenderWindow::InitRenderTarget() { |
| { |
| |
| |
| const RenderWidget dummy_widget{this}; |
| } |
|
|
| first_frame = false; |
|
|
| const auto graphics_api = Settings::values.graphics_api.GetValue(); |
| switch (graphics_api) { |
| #ifdef ENABLE_SOFTWARE_RENDERER |
| case Settings::GraphicsAPI::Software: |
| InitializeSoftware(); |
| break; |
| #endif |
| #ifdef ENABLE_OPENGL |
| case Settings::GraphicsAPI::OpenGL: |
| if (!InitializeOpenGL() || !LoadOpenGL()) { |
| return false; |
| } |
| break; |
| #endif |
| #ifdef ENABLE_VULKAN |
| case Settings::GraphicsAPI::Vulkan: |
| InitializeVulkan(); |
| break; |
| #endif |
| default: |
| LOG_CRITICAL(Frontend, |
| "Unknown or unsupported graphics API {}, falling back to available default", |
| graphics_api); |
| #ifdef ENABLE_OPENGL |
| if (!InitializeOpenGL() || !LoadOpenGL()) { |
| return false; |
| } |
| #elif ENABLE_VULKAN |
| InitializeVulkan(); |
| #elif ENABLE_SOFTWARE_RENDERER |
| InitializeSoftware(); |
| #else |
| |
| #error "At least one renderer must be enabled." |
| #endif |
| break; |
| } |
|
|
| |
| window_info = GetWindowSystemInfo(child_widget->windowHandle()); |
|
|
| child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); |
|
|
| layout()->addWidget(child_widget); |
| |
| setMinimumSize(1, 1); |
|
|
| resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight); |
| OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); |
| OnFramebufferSizeChanged(); |
| BackupGeometry(); |
|
|
| return true; |
| } |
|
|
| void GRenderWindow::ReleaseRenderTarget() { |
| if (child_widget) { |
| layout()->removeWidget(child_widget); |
| child_widget->deleteLater(); |
| child_widget = nullptr; |
| } |
| main_context.reset(); |
| } |
|
|
| void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { |
| auto& renderer = system.GPU().Renderer(); |
| if (res_scale == 0) { |
| res_scale = renderer.GetResolutionScaleFactor(); |
| } |
|
|
| const auto layout{Layout::FrameLayoutFromResolutionScale(res_scale, is_secondary)}; |
| screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); |
| renderer.RequestScreenshot( |
| screenshot_image.bits(), |
| [this, screenshot_path](bool invert_y) { |
| const std::string std_screenshot_path = screenshot_path.toStdString(); |
| if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) { |
| LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); |
| } else { |
| LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path); |
| } |
| }, |
| layout); |
| } |
|
|
| void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) { |
| setMinimumSize(minimal_size.first, minimal_size.second); |
| } |
|
|
| #ifdef ENABLE_OPENGL |
| bool GRenderWindow::InitializeOpenGL() { |
| if (!QOpenGLContext::supportsThreadedOpenGL()) { |
| QMessageBox::warning(this, tr("OpenGL not available!"), |
| tr("OpenGL shared contexts are not supported.")); |
| return false; |
| } |
|
|
| |
| |
| auto child = new OpenGLRenderWidget(this, system, is_secondary); |
| child_widget = child; |
| child_widget->windowHandle()->create(); |
|
|
| if (!main_context) { |
| main_context = std::make_unique<OpenGLSharedContext>(); |
| } |
|
|
| auto child_context = CreateSharedContext(); |
| child->SetContext(std::move(child_context)); |
|
|
| auto format = child_widget->windowHandle()->format(); |
| format.setSwapInterval(Settings::values.use_vsync_new.GetValue()); |
| child_widget->windowHandle()->setFormat(format); |
|
|
| return true; |
| } |
|
|
| static void* GetProcAddressGL(const char* name) { |
| return reinterpret_cast<void*>(QOpenGLContext::currentContext()->getProcAddress(name)); |
| } |
|
|
| bool GRenderWindow::LoadOpenGL() { |
| auto context = CreateSharedContext(); |
| auto scope = context->Acquire(); |
| const auto gles = context->IsGLES(); |
|
|
| auto gl_load_func = gles ? gladLoadGLES2Loader : gladLoadGLLoader; |
| if (!gl_load_func(GetProcAddressGL)) { |
| QMessageBox::warning( |
| this, tr("Error while initializing OpenGL!"), |
| tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver.")); |
| return false; |
| } |
|
|
| const QString renderer = |
| QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER))); |
|
|
| if (!gles && !GLAD_GL_VERSION_4_3) { |
| LOG_ERROR(Frontend, "GPU does not support OpenGL 4.3: {}", renderer.toStdString()); |
| QMessageBox::warning(this, tr("Error while initializing OpenGL 4.3!"), |
| tr("Your GPU may not support OpenGL 4.3, or you do not have the " |
| "latest graphics driver.<br><br>GL Renderer:<br>%1") |
| .arg(renderer)); |
| return false; |
| } else if (gles && !GLAD_GL_ES_VERSION_3_2) { |
| LOG_ERROR(Frontend, "GPU does not support OpenGL ES 3.2: {}", renderer.toStdString()); |
| QMessageBox::warning(this, tr("Error while initializing OpenGL ES 3.2!"), |
| tr("Your GPU may not support OpenGL ES 3.2, or you do not have the " |
| "latest graphics driver.<br><br>GL Renderer:<br>%1") |
| .arg(renderer)); |
| return false; |
| } |
|
|
| return true; |
| } |
| #endif |
|
|
| #ifdef ENABLE_VULKAN |
| void GRenderWindow::InitializeVulkan() { |
| auto child = new VulkanRenderWidget(this); |
| child_widget = child; |
| child_widget->windowHandle()->create(); |
| main_context = std::make_unique<DummyContext>(); |
| } |
| #endif |
|
|
| #ifdef ENABLE_SOFTWARE_RENDERER |
| void GRenderWindow::InitializeSoftware() { |
| child_widget = new SoftwareRenderWidget(this, system); |
| main_context = std::make_unique<DummyContext>(); |
| } |
| #endif |
|
|
| void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { |
| this->emu_thread = emu_thread; |
| } |
|
|
| void GRenderWindow::OnEmulationStopping() { |
| emu_thread = nullptr; |
| } |
|
|
| void GRenderWindow::showEvent(QShowEvent* event) { |
| QWidget::showEvent(event); |
| } |
|
|
| std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { |
| #ifdef ENABLE_OPENGL |
| const auto graphics_api = Settings::values.graphics_api.GetValue(); |
| if (graphics_api == Settings::GraphicsAPI::OpenGL) { |
| auto gl_context = static_cast<OpenGLSharedContext*>(main_context.get()); |
| |
| |
| return std::make_unique<OpenGLSharedContext>(gl_context->GetShareContext(), |
| child_widget->windowHandle()); |
| } |
| #endif |
| return std::make_unique<DummyContext>(); |
| } |
|
|