From 08fcf41b0a3d4e6066cb72f47c3e1d94bb7fc408 Mon Sep 17 00:00:00 2001 From: James Rowe Date: Thu, 17 Jan 2019 00:01:00 -0700 Subject: [PATCH] QT Frontend: Add a Loading screen with progressbar With shader caches on the horizon, one requirement is to provide visible feedback for the progress. The shader cache reportedly takes several minutes to load for large caches that were invalidated, and as such we should provide a loading screen with progress. Adds a loading screen widget that will be shown until the first frame of the game is swapped. This was chosen in case shader caches are not being used, several games still take more than a few seconds to launch and could benefit from a loading screen. --- CMakeModules/CopyYuzuQt5Deps.cmake | 5 +- src/yuzu/CMakeLists.txt | 7 ++- src/yuzu/bootmanager.cpp | 11 ++++- src/yuzu/bootmanager.h | 3 ++ src/yuzu/loading_screen.cpp | 71 +++++++++++++++++++++++++++ src/yuzu/loading_screen.h | 50 +++++++++++++++++++ src/yuzu/loading_screen.ui | 79 ++++++++++++++++++++++++++++++ src/yuzu/main.cpp | 26 +++++++--- src/yuzu/main.h | 4 +- 9 files changed, 244 insertions(+), 12 deletions(-) create mode 100644 src/yuzu/loading_screen.cpp create mode 100644 src/yuzu/loading_screen.h create mode 100644 src/yuzu/loading_screen.ui diff --git a/CMakeModules/CopyYuzuQt5Deps.cmake b/CMakeModules/CopyYuzuQt5Deps.cmake index 4fef666594..1e9810bba2 100644 --- a/CMakeModules/CopyYuzuQt5Deps.cmake +++ b/CMakeModules/CopyYuzuQt5Deps.cmake @@ -45,5 +45,8 @@ function(copy_yuzu_Qt5_deps target_dir) windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$:d>.*) windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$:d>.*) - windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$:d>.*) + windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} + qjpeg$<$:d>.* + qgif$<$:d>.* + ) endfunction(copy_yuzu_Qt5_deps) diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 1f852df4bc..4cab599b4d 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -68,6 +68,8 @@ add_executable(yuzu game_list_p.h game_list_worker.cpp game_list_worker.h + loading_screen.cpp + loading_screen.h hotkeys.cpp hotkeys.h main.cpp @@ -102,9 +104,10 @@ set(UIS configuration/configure_system.ui configuration/configure_touchscreen_advanced.ui configuration/configure_web.ui - hotkeys.ui - main.ui compatdb.ui + hotkeys.ui + loading_screen.ui + main.ui ) file(GLOB COMPAT_LIST diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 40db7a5e92..f74cb693a4 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -3,9 +3,7 @@ #include #include #include - #include - #include "common/microprofile.h" #include "common/scm_rev.h" #include "core/core.h" @@ -17,6 +15,7 @@ #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "yuzu/bootmanager.h" +#include "yuzu/main.h" EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} @@ -114,6 +113,8 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) InputCommon::Init(); InputCommon::StartJoystickEventHandler(); + connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast(parent), + &GMainWindow::OnLoadComplete); } GRenderWindow::~GRenderWindow() { @@ -141,6 +142,10 @@ void GRenderWindow::SwapBuffers() { child->makeCurrent(); child->swapBuffers(); + if (!first_frame) { + emit FirstFrameDisplayed(); + first_frame = true; + } } void GRenderWindow::MakeCurrent() { @@ -309,6 +314,8 @@ void GRenderWindow::InitRenderTarget() { delete layout(); } + first_frame = false; + // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, // WA_DontShowOnScreen, WA_DeleteOnClose QGLFormat fmt; diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 4e30282155..d1f37e5032 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -152,6 +152,7 @@ public slots: signals: /// Emitted when the window is closed void Closed(); + void FirstFrameDisplayed(); private: std::pair ScaleTouch(const QPointF pos) const; @@ -171,6 +172,8 @@ private: /// Temporary storage of the screenshot taken QImage screenshot_image; + bool first_frame = false; + protected: void showEvent(QShowEvent* event) override; }; diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp new file mode 100644 index 0000000000..f2d3214f6f --- /dev/null +++ b/src/yuzu/loading_screen.cpp @@ -0,0 +1,71 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/logging/log.h" +#include "core/loader/loader.h" +#include "ui_loading_screen.h" +#include "yuzu/loading_screen.h" + +LoadingScreen::LoadingScreen(QWidget* parent) + : QWidget(parent), ui(std::make_unique()) { + ui->setupUi(this); + // Progress bar is hidden until we have a use for it. + ui->progress_bar->hide(); +} + +LoadingScreen::~LoadingScreen() = default; + +void LoadingScreen::Prepare(Loader::AppLoader& loader) { + std::vector buffer; + if (loader.ReadBanner(buffer) == Loader::ResultStatus::Success) { + backing_mem = + std::make_unique(reinterpret_cast(buffer.data()), buffer.size()); + backing_buf = std::make_unique(backing_mem.get()); + backing_buf->open(QIODevice::ReadOnly); + animation = std::make_unique(backing_buf.get(), QByteArray("GIF")); + animation->start(); + ui->banner->setMovie(animation.get()); + buffer.clear(); + } + if (loader.ReadLogo(buffer) == Loader::ResultStatus::Success) { + QPixmap map; + map.loadFromData(buffer.data(), buffer.size()); + ui->logo->setPixmap(map); + } +} + +void LoadingScreen::OnLoadProgress(std::size_t value, std::size_t total) { + if (total != previous_total) { + ui->progress_bar->setMaximum(total); + previous_total = total; + } + ui->progress_bar->setValue(value); +} + +void LoadingScreen::paintEvent(QPaintEvent* event) { + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QWidget::paintEvent(event); +} + +void LoadingScreen::Clear() { + animation.reset(); + backing_buf.reset(); + backing_mem.reset(); +} diff --git a/src/yuzu/loading_screen.h b/src/yuzu/loading_screen.h new file mode 100644 index 0000000000..ffcaa260df --- /dev/null +++ b/src/yuzu/loading_screen.h @@ -0,0 +1,50 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Loader { +class AppLoader; +} + +namespace Ui { +class LoadingScreen; +} + +class QBuffer; +class QByteArray; +class QMovie; + +class LoadingScreen : public QWidget { + Q_OBJECT + +public: + explicit LoadingScreen(QWidget* parent = nullptr); + + ~LoadingScreen(); + + /// Call before showing the loading screen to load the widgets with the logo and banner for the + /// currently loaded application. + void Prepare(Loader::AppLoader& loader); + + /// After the loading screen is hidden, the owner of this class can call this to clean up any + /// used resources such as the logo and banner. + void Clear(); + + // In order to use a custom widget with a stylesheet, you need to override the paintEvent + // See https://wiki.qt.io/How_to_Change_the_Background_Color_of_QWidget + void paintEvent(QPaintEvent* event) override; + + void OnLoadProgress(std::size_t value, std::size_t total); + +private: + std::unique_ptr animation; + std::unique_ptr backing_buf; + std::unique_ptr backing_mem; + std::unique_ptr ui; + std::size_t previous_total = 0; +}; diff --git a/src/yuzu/loading_screen.ui b/src/yuzu/loading_screen.ui new file mode 100644 index 0000000000..00579b6709 --- /dev/null +++ b/src/yuzu/loading_screen.ui @@ -0,0 +1,79 @@ + + + LoadingScreen + + + + 0 + 0 + 746 + 495 + + + + background-color: rgb(0, 0, 0); + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + 30 + + + + + + + + + font-size: 26px; + + + 0 + + + Loading Shaders %v out of %m + + + + + + + + + background-color: black; + + + + + + 30 + + + + + + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index f564de9948..68bfa23ab1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -92,6 +92,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/hotkeys.h" +#include "yuzu/loading_screen.h" #include "yuzu/main.h" #include "yuzu/ui_settings.h" @@ -411,6 +412,10 @@ void GMainWindow::InitializeWidgets() { game_list = new GameList(vfs, this); ui.horizontalLayout->addWidget(game_list); + loading_screen = new LoadingScreen(this); + loading_screen->hide(); + ui.horizontalLayout->addWidget(loading_screen); + // Create status bar message_label = new QLabel(); // Configured separately for left alignment @@ -897,8 +902,9 @@ void GMainWindow::BootGame(const QString& filename) { .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, QString::fromStdString(title_name))); - render_window->show(); - render_window->setFocus(); + loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); + loading_screen->show(); + loading_screen->setFocus(); emulation_running = true; if (ui.action_Fullscreen->isChecked()) { @@ -932,6 +938,8 @@ void GMainWindow::ShutdownGame() { ui.action_Load_Amiibo->setEnabled(false); ui.action_Capture_Screenshot->setEnabled(false); render_window->hide(); + loading_screen->hide(); + loading_screen->Clear(); game_list->show(); game_list->setFilterFocus(); setWindowTitle(QString("yuzu %1| %2-%3") @@ -1505,6 +1513,13 @@ void GMainWindow::OnStopGame() { ShutdownGame(); } +void GMainWindow::OnLoadComplete() { + loading_screen->hide(); + loading_screen->Clear(); + render_window->show(); + render_window->setFocus(); +} + void GMainWindow::OnMenuReportCompatibility() { if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) { CompatDB compatdb{this}; @@ -1771,9 +1786,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { this, tr("Confirm Key Rederivation"), tr("You are about to force rederive all of your keys. \nIf you do not know what this " "means or what you are doing, \nthis is a potentially destructive action. \nPlease " - "make " - "sure this is what you want \nand optionally make backups.\n\nThis will delete your " - "autogenerated key files and re-run the key derivation module."), + "make sure this is what you want \nand optionally make backups.\n\nThis will delete " + "your autogenerated key files and re-run the key derivation module."), QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel}); if (res == QMessageBox::Cancel) @@ -1818,7 +1832,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { errors + tr("

You can get all of these and dump all of your games easily by " "following the " - "quickstart guide. Alternatively, you can use another method of dumping " + "quickstart guide. Alternatively, you can use another method of dumping" "to obtain all of your keys.")); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 2d705ad54d..e07c892cf2 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -25,6 +25,7 @@ class GImageInfo; class GraphicsBreakPointsWidget; class GraphicsSurfaceWidget; class GRenderWindow; +class LoadingScreen; class MicroProfileDialog; class ProfilerWidget; class QLabel; @@ -109,10 +110,10 @@ signals: void WebBrowserFinishedBrowsing(); public slots: + void OnLoadComplete(); void ProfileSelectorSelectProfile(); void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); - void WebBrowserOpenPage(std::string_view filename, std::string_view arguments); private: @@ -212,6 +213,7 @@ private: GRenderWindow* render_window; GameList* game_list; + LoadingScreen* loading_screen; // Status bar elements QLabel* message_label = nullptr;