QT Frontend: Add disk shader loading progress bar
Until we get a on screen display or async shader loading, we should at least have some measure of progress in the meantime. This is 90% a port from the loading screen I made for yuzu, but with a slightly different changed detection for when to display the ETA. Now we keep track of a rolling estimate for shader load ETA and only display a ETA if its going to take longer than 10 seconds.
This commit is contained in:
parent
c0df8271bf
commit
961a7b59c9
|
@ -115,6 +115,9 @@ add_executable(citra-qt
|
||||||
game_list_worker.h
|
game_list_worker.h
|
||||||
hotkeys.cpp
|
hotkeys.cpp
|
||||||
hotkeys.h
|
hotkeys.h
|
||||||
|
loading_screen.cpp
|
||||||
|
loading_screen.h
|
||||||
|
loading_screen.ui
|
||||||
main.cpp
|
main.cpp
|
||||||
main.h
|
main.h
|
||||||
main.ui
|
main.ui
|
||||||
|
|
|
@ -46,12 +46,15 @@ void EmuThread::run() {
|
||||||
MicroProfileOnThreadCreate("EmuThread");
|
MicroProfileOnThreadCreate("EmuThread");
|
||||||
Frontend::ScopeAcquireContext scope(core_context);
|
Frontend::ScopeAcquireContext scope(core_context);
|
||||||
|
|
||||||
|
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||||
|
|
||||||
Core::System::GetInstance().Renderer().Rasterizer()->LoadDiskResources(
|
Core::System::GetInstance().Renderer().Rasterizer()->LoadDiskResources(
|
||||||
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
|
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
|
||||||
LOG_DEBUG(Frontend, "Loading stage {} progress {} {}", static_cast<u32>(stage), value,
|
emit LoadProgress(stage, value, total);
|
||||||
total);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||||
|
|
||||||
// Holds whether the cpu was running during the last iteration,
|
// Holds whether the cpu was running during the last iteration,
|
||||||
// so that the DebugModeLeft signal can be emitted before the
|
// so that the DebugModeLeft signal can be emitted before the
|
||||||
// next execution step.
|
// next execution step.
|
||||||
|
@ -127,6 +130,7 @@ OpenGLWindow::~OpenGLWindow() {
|
||||||
void OpenGLWindow::Present() {
|
void OpenGLWindow::Present() {
|
||||||
if (!isExposed())
|
if (!isExposed())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
context->makeCurrent(this);
|
context->makeCurrent(this);
|
||||||
VideoCore::g_renderer->TryPresent(100);
|
VideoCore::g_renderer->TryPresent(100);
|
||||||
context->swapBuffers(this);
|
context->swapBuffers(this);
|
||||||
|
@ -182,8 +186,8 @@ void OpenGLWindow::exposeEvent(QExposeEvent* event) {
|
||||||
QWindow::exposeEvent(event);
|
QWindow::exposeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
|
||||||
: QWidget(parent), emu_thread(emu_thread) {
|
: QWidget(parent_), emu_thread(emu_thread) {
|
||||||
|
|
||||||
setWindowTitle(QStringLiteral("Citra %1 | %2-%3")
|
setWindowTitle(QStringLiteral("Citra %1 | %2-%3")
|
||||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||||
|
@ -192,6 +196,9 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||||
layout->setMargin(0);
|
layout->setMargin(0);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
InputCommon::Init();
|
InputCommon::Init();
|
||||||
|
|
||||||
|
GMainWindow* parent = GetMainWindow();
|
||||||
|
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
GRenderWindow::~GRenderWindow() {
|
GRenderWindow::~GRenderWindow() {
|
||||||
|
@ -206,7 +213,12 @@ void GRenderWindow::DoneCurrent() {
|
||||||
core_context->DoneCurrent();
|
core_context->DoneCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::PollEvents() {}
|
void GRenderWindow::PollEvents() {
|
||||||
|
if (!first_frame) {
|
||||||
|
first_frame = true;
|
||||||
|
emit FirstFrameDisplayed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
|
// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
|
||||||
//
|
//
|
||||||
|
@ -363,12 +375,15 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
||||||
void GRenderWindow::InitRenderTarget() {
|
void GRenderWindow::InitRenderTarget() {
|
||||||
ReleaseRenderTarget();
|
ReleaseRenderTarget();
|
||||||
|
|
||||||
|
first_frame = false;
|
||||||
|
|
||||||
GMainWindow* parent = GetMainWindow();
|
GMainWindow* parent = GetMainWindow();
|
||||||
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
|
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
|
||||||
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
|
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
|
||||||
child_window->create();
|
child_window->create();
|
||||||
child_widget = createWindowContainer(child_window, this);
|
child_widget = createWindowContainer(child_window, this);
|
||||||
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||||
|
|
||||||
layout()->addWidget(child_widget);
|
layout()->addWidget(child_widget);
|
||||||
|
|
||||||
core_context = CreateSharedContext();
|
core_context = CreateSharedContext();
|
||||||
|
|
|
@ -23,6 +23,10 @@ class QOpenGLContext;
|
||||||
class GMainWindow;
|
class GMainWindow;
|
||||||
class GRenderWindow;
|
class GRenderWindow;
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
enum class LoadCallbackStage;
|
||||||
|
}
|
||||||
|
|
||||||
class GLContext : public Frontend::GraphicsContext {
|
class GLContext : public Frontend::GraphicsContext {
|
||||||
public:
|
public:
|
||||||
explicit GLContext(QOpenGLContext* shared_context);
|
explicit GLContext(QOpenGLContext* shared_context);
|
||||||
|
@ -116,6 +120,8 @@ signals:
|
||||||
void DebugModeLeft();
|
void DebugModeLeft();
|
||||||
|
|
||||||
void ErrorThrown(Core::System::ResultStatus, std::string);
|
void ErrorThrown(Core::System::ResultStatus, std::string);
|
||||||
|
|
||||||
|
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
|
||||||
};
|
};
|
||||||
|
|
||||||
class OpenGLWindow : public QWindow {
|
class OpenGLWindow : public QWindow {
|
||||||
|
@ -188,6 +194,11 @@ signals:
|
||||||
/// Emitted when the window is closed
|
/// Emitted when the window is closed
|
||||||
void Closed();
|
void Closed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the guest first calls SwapBuffers. This is used to hide the loading screen
|
||||||
|
*/
|
||||||
|
void FirstFrameDisplayed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
|
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
|
||||||
void TouchBeginEvent(const QTouchEvent* event);
|
void TouchBeginEvent(const QTouchEvent* event);
|
||||||
|
@ -212,6 +223,7 @@ private:
|
||||||
|
|
||||||
/// Temporary storage of the screenshot taken
|
/// Temporary storage of the screenshot taken
|
||||||
QImage screenshot_image;
|
QImage screenshot_image;
|
||||||
|
bool first_frame = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QGraphicsOpacityEffect>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QPropertyAnimation>
|
||||||
|
#include <QStyleOption>
|
||||||
|
#include <QTime>
|
||||||
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
|
#include "citra_qt/loading_screen.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/loader/loader.h"
|
||||||
|
#include "core/loader/smdh.h"
|
||||||
|
#include "ui_loading_screen.h"
|
||||||
|
#include "video_core/rasterizer_interface.h"
|
||||||
|
|
||||||
|
constexpr char PROGRESSBAR_STYLE_PREPARE[] = R"(
|
||||||
|
QProgressBar {}
|
||||||
|
QProgressBar::chunk {})";
|
||||||
|
|
||||||
|
constexpr char PROGRESSBAR_STYLE_DECOMPILE[] = R"(
|
||||||
|
QProgressBar {
|
||||||
|
background-color: black;
|
||||||
|
border: 2px solid white;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
QProgressBar::chunk {
|
||||||
|
background-color: #fd8507;
|
||||||
|
width: 1px;
|
||||||
|
})";
|
||||||
|
|
||||||
|
constexpr char PROGRESSBAR_STYLE_BUILD[] = R"(
|
||||||
|
QProgressBar {
|
||||||
|
background-color: black;
|
||||||
|
border: 2px solid white;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
QProgressBar::chunk {
|
||||||
|
background-color: #ffe402;
|
||||||
|
width: 1px;
|
||||||
|
})";
|
||||||
|
|
||||||
|
constexpr char PROGRESSBAR_STYLE_COMPLETE[] = R"(
|
||||||
|
QProgressBar {
|
||||||
|
background-color: #fd8507;
|
||||||
|
border: 2px solid white;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
QProgressBar::chunk {
|
||||||
|
background-color: #ffe402;
|
||||||
|
})";
|
||||||
|
|
||||||
|
// Definitions for the differences in text and styling for each stage
|
||||||
|
const static std::unordered_map<VideoCore::LoadCallbackStage, const char*> stage_translations{
|
||||||
|
{VideoCore::LoadCallbackStage::Prepare, QT_TRANSLATE_NOOP("LoadingScreen", "Loading...")},
|
||||||
|
{VideoCore::LoadCallbackStage::Decompile,
|
||||||
|
QT_TRANSLATE_NOOP("LoadingScreen", "Preparing Shaders %1 / %2")},
|
||||||
|
{VideoCore::LoadCallbackStage::Build,
|
||||||
|
QT_TRANSLATE_NOOP("LoadingScreen", "Loading Shaders %1 / %2")},
|
||||||
|
{VideoCore::LoadCallbackStage::Complete, QT_TRANSLATE_NOOP("LoadingScreen", "Launching...")},
|
||||||
|
};
|
||||||
|
const static std::unordered_map<VideoCore::LoadCallbackStage, const char*> progressbar_style{
|
||||||
|
{VideoCore::LoadCallbackStage::Prepare, PROGRESSBAR_STYLE_PREPARE},
|
||||||
|
{VideoCore::LoadCallbackStage::Decompile, PROGRESSBAR_STYLE_DECOMPILE},
|
||||||
|
{VideoCore::LoadCallbackStage::Build, PROGRESSBAR_STYLE_BUILD},
|
||||||
|
{VideoCore::LoadCallbackStage::Complete, PROGRESSBAR_STYLE_COMPLETE},
|
||||||
|
};
|
||||||
|
|
||||||
|
static QPixmap GetQPixmapFromSMDH(std::vector<u8>& smdh_data) {
|
||||||
|
Loader::SMDH smdh;
|
||||||
|
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
|
||||||
|
|
||||||
|
bool large = true;
|
||||||
|
std::vector<u16> icon_data = smdh.GetIcon(large);
|
||||||
|
const uchar* data = reinterpret_cast<const uchar*>(icon_data.data());
|
||||||
|
int size = large ? 48 : 24;
|
||||||
|
QImage icon(data, size, size, QImage::Format::Format_RGB16);
|
||||||
|
return QPixmap::fromImage(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadingScreen::LoadingScreen(QWidget* parent)
|
||||||
|
: QWidget(parent), ui(std::make_unique<Ui::LoadingScreen>()),
|
||||||
|
previous_stage(VideoCore::LoadCallbackStage::Complete) {
|
||||||
|
ui->setupUi(this);
|
||||||
|
setMinimumSize(400, 240);
|
||||||
|
|
||||||
|
// Create a fade out effect to hide this loading screen widget.
|
||||||
|
// When fading opacity, it will fade to the parent widgets background color, which is why we
|
||||||
|
// create an internal widget named fade_widget that we use the effect on, while keeping the
|
||||||
|
// loading screen widget's background color black. This way we can create a fade to black effect
|
||||||
|
opacity_effect = new QGraphicsOpacityEffect(this);
|
||||||
|
opacity_effect->setOpacity(1);
|
||||||
|
ui->fade_parent->setGraphicsEffect(opacity_effect);
|
||||||
|
fadeout_animation = std::make_unique<QPropertyAnimation>(opacity_effect, "opacity");
|
||||||
|
fadeout_animation->setDuration(500);
|
||||||
|
fadeout_animation->setStartValue(1);
|
||||||
|
fadeout_animation->setEndValue(0);
|
||||||
|
fadeout_animation->setEasingCurve(QEasingCurve::OutBack);
|
||||||
|
|
||||||
|
// After the fade completes, hide the widget and reset the opacity
|
||||||
|
connect(fadeout_animation.get(), &QPropertyAnimation::finished, [this] {
|
||||||
|
hide();
|
||||||
|
opacity_effect->setOpacity(1);
|
||||||
|
emit Hidden();
|
||||||
|
});
|
||||||
|
connect(this, &LoadingScreen::LoadProgress, this, &LoadingScreen::OnLoadProgress,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
qRegisterMetaType<VideoCore::LoadCallbackStage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadingScreen::~LoadingScreen() = default;
|
||||||
|
|
||||||
|
void LoadingScreen::Prepare(Loader::AppLoader& loader) {
|
||||||
|
std::vector<u8> buffer;
|
||||||
|
// TODO when banner becomes supported, decode it and add it as a movie
|
||||||
|
|
||||||
|
if (loader.ReadIcon(buffer) == Loader::ResultStatus::Success) {
|
||||||
|
QPixmap icon = GetQPixmapFromSMDH(buffer);
|
||||||
|
ui->icon->setPixmap(icon);
|
||||||
|
}
|
||||||
|
std::string title;
|
||||||
|
if (loader.ReadTitle(title) == Loader::ResultStatus::Success) {
|
||||||
|
ui->title->setText(QString("Now Loading\n") + QString::fromStdString(title));
|
||||||
|
}
|
||||||
|
eta_shown = false;
|
||||||
|
OnLoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreen::OnLoadComplete() {
|
||||||
|
fadeout_animation->start(QPropertyAnimation::KeepWhenStopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value,
|
||||||
|
std::size_t total) {
|
||||||
|
using namespace std::chrono;
|
||||||
|
const auto now = high_resolution_clock::now();
|
||||||
|
// reset the timer if the stage changes
|
||||||
|
if (stage != previous_stage) {
|
||||||
|
ui->progress_bar->setStyleSheet(progressbar_style.at(stage));
|
||||||
|
// Hide the progress bar during the prepare stage
|
||||||
|
if (stage == VideoCore::LoadCallbackStage::Prepare) {
|
||||||
|
ui->progress_bar->hide();
|
||||||
|
} else {
|
||||||
|
ui->progress_bar->show();
|
||||||
|
}
|
||||||
|
previous_stage = stage;
|
||||||
|
}
|
||||||
|
// update the max of the progress bar if the number of shaders change
|
||||||
|
if (total != previous_total) {
|
||||||
|
ui->progress_bar->setMaximum(static_cast<int>(total));
|
||||||
|
previous_total = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate a simple rolling average after the first shader is loaded
|
||||||
|
if (value > 0) {
|
||||||
|
rolling_average -= rolling_average / NumberOfDataPoints;
|
||||||
|
rolling_average += (now - previous_time) / NumberOfDataPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString estimate;
|
||||||
|
|
||||||
|
// After 25 shader load times were put into the rolling average, determine if the ETA is long
|
||||||
|
// enough to show it
|
||||||
|
if (value > NumberOfDataPoints &&
|
||||||
|
(eta_shown || rolling_average * (total - value) > ETABreakPoint)) {
|
||||||
|
if (!eta_shown) {
|
||||||
|
eta_shown = true;
|
||||||
|
}
|
||||||
|
const auto eta_mseconds = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
rolling_average * (total - value));
|
||||||
|
estimate = tr("Estimated Time %1")
|
||||||
|
.arg(QTime(0, 0, 0, 0)
|
||||||
|
.addMSecs(std::max<long>(eta_mseconds.count(), 1000))
|
||||||
|
.toString(QStringLiteral("mm:ss")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// update labels and progress bar
|
||||||
|
const auto& stg = tr(stage_translations.at(stage));
|
||||||
|
if (stage == VideoCore::LoadCallbackStage::Decompile ||
|
||||||
|
stage == VideoCore::LoadCallbackStage::Build) {
|
||||||
|
ui->stage->setText(stg.arg(value).arg(total));
|
||||||
|
} else {
|
||||||
|
ui->stage->setText(stg);
|
||||||
|
}
|
||||||
|
ui->value->setText(estimate);
|
||||||
|
ui->progress_bar->setValue(static_cast<int>(value));
|
||||||
|
previous_time = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {}
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <QString>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
class AppLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class LoadingScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
enum class LoadCallbackStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
class QGraphicsOpacityEffect;
|
||||||
|
class QPropertyAnimation;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
/// Slot used to update the status of the progress bar
|
||||||
|
void OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
|
||||||
|
|
||||||
|
/// Hides the LoadingScreen with a fade out effect
|
||||||
|
void OnLoadComplete();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
|
||||||
|
/// Signals that this widget is completely hidden now and should be replaced with the other
|
||||||
|
/// widget
|
||||||
|
void Hidden();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Ui::LoadingScreen> ui;
|
||||||
|
std::size_t previous_total = 0;
|
||||||
|
VideoCore::LoadCallbackStage previous_stage;
|
||||||
|
|
||||||
|
QGraphicsOpacityEffect* opacity_effect = nullptr;
|
||||||
|
std::unique_ptr<QPropertyAnimation> fadeout_animation;
|
||||||
|
|
||||||
|
// Variables used to keep track of the current ETA.
|
||||||
|
// If the rolling_average * shaders_remaining > eta_break_point then we want to display the eta.
|
||||||
|
// We don't want to always display it since showing an ETA leads people to think its taking
|
||||||
|
// longer that it is because ETAs are often wrong
|
||||||
|
static constexpr std::chrono::seconds ETABreakPoint = std::chrono::seconds{10};
|
||||||
|
static constexpr std::size_t NumberOfDataPoints = 25;
|
||||||
|
std::chrono::high_resolution_clock::time_point previous_time;
|
||||||
|
std::chrono::duration<double> rolling_average = {};
|
||||||
|
bool eta_shown = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage);
|
|
@ -0,0 +1,188 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>LoadingScreen</class>
|
||||||
|
<widget class="QWidget" name="LoadingScreen">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>746</width>
|
||||||
|
<height>495</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: rgb(0, 0, 0);</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QWidget" name="fade_parent" native="true">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,1,0,1,0">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>15</number>
|
||||||
|
</property>
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetNoConstraint</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="icon">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="title">
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: black; color: white;
|
||||||
|
font: 75 20pt "Arial";</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignHCenter|Qt::AlignTop</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item alignment="Qt::AlignHCenter|Qt::AlignBottom">
|
||||||
|
<widget class="QLabel" name="stage">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: black; color: white;
|
||||||
|
font: 75 20pt "Arial";</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Loading Shaders 387 / 1628</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item alignment="Qt::AlignHCenter|Qt::AlignTop">
|
||||||
|
<widget class="QProgressBar" name="progress_bar">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>500</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QProgressBar {
|
||||||
|
color: white;
|
||||||
|
border: 2px solid white;
|
||||||
|
outline-color: black;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
QProgressBar::chunk {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
}</string>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>50</number>
|
||||||
|
</property>
|
||||||
|
<property name="textVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="format">
|
||||||
|
<string>Loading Shaders %v out of %m</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item alignment="Qt::AlignHCenter|Qt::AlignTop">
|
||||||
|
<widget class="QLabel" name="value">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string notr="true"/>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">background-color: black; color: white;
|
||||||
|
font: 75 15pt "Arial";</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Estimated Time 5m 4s</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -47,6 +47,7 @@
|
||||||
#include "citra_qt/discord.h"
|
#include "citra_qt/discord.h"
|
||||||
#include "citra_qt/game_list.h"
|
#include "citra_qt/game_list.h"
|
||||||
#include "citra_qt/hotkeys.h"
|
#include "citra_qt/hotkeys.h"
|
||||||
|
#include "citra_qt/loading_screen.h"
|
||||||
#include "citra_qt/main.h"
|
#include "citra_qt/main.h"
|
||||||
#include "citra_qt/multiplayer/state.h"
|
#include "citra_qt/multiplayer/state.h"
|
||||||
#include "citra_qt/qt_image_interface.h"
|
#include "citra_qt/qt_image_interface.h"
|
||||||
|
@ -222,6 +223,17 @@ void GMainWindow::InitializeWidgets() {
|
||||||
ui.horizontalLayout->addWidget(game_list_placeholder);
|
ui.horizontalLayout->addWidget(game_list_placeholder);
|
||||||
game_list_placeholder->setVisible(false);
|
game_list_placeholder->setVisible(false);
|
||||||
|
|
||||||
|
loading_screen = new LoadingScreen(this);
|
||||||
|
loading_screen->hide();
|
||||||
|
ui.horizontalLayout->addWidget(loading_screen);
|
||||||
|
connect(loading_screen, &LoadingScreen::Hidden, [&] {
|
||||||
|
loading_screen->Clear();
|
||||||
|
if (emulation_running) {
|
||||||
|
render_window->show();
|
||||||
|
render_window->setFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui.action_Leave_Room,
|
multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui.action_Leave_Room,
|
||||||
ui.action_Show_Room);
|
ui.action_Show_Room);
|
||||||
multiplayer_state->setVisible(false);
|
multiplayer_state->setVisible(false);
|
||||||
|
@ -917,6 +929,9 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||||
connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget,
|
connect(emu_thread.get(), &EmuThread::DebugModeLeft, waitTreeWidget,
|
||||||
&WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
|
&WaitTreeWidget::OnDebugModeLeft, Qt::BlockingQueuedConnection);
|
||||||
|
|
||||||
|
connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen,
|
||||||
|
&LoadingScreen::OnLoadProgress, Qt::QueuedConnection);
|
||||||
|
|
||||||
// Update the GUI
|
// Update the GUI
|
||||||
registersWidget->OnDebugModeEntered();
|
registersWidget->OnDebugModeEntered();
|
||||||
if (ui.action_Single_Window_Mode->isChecked()) {
|
if (ui.action_Single_Window_Mode->isChecked()) {
|
||||||
|
@ -925,8 +940,12 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||||
}
|
}
|
||||||
status_bar_update_timer.start(2000);
|
status_bar_update_timer.start(2000);
|
||||||
|
|
||||||
|
// show and hide the render_window to create the context
|
||||||
render_window->show();
|
render_window->show();
|
||||||
render_window->setFocus();
|
render_window->hide();
|
||||||
|
|
||||||
|
loading_screen->Prepare(Core::System::GetInstance().GetAppLoader());
|
||||||
|
loading_screen->show();
|
||||||
|
|
||||||
emulation_running = true;
|
emulation_running = true;
|
||||||
if (ui.action_Fullscreen->isChecked()) {
|
if (ui.action_Fullscreen->isChecked()) {
|
||||||
|
@ -1003,6 +1022,8 @@ void GMainWindow::ShutdownGame() {
|
||||||
ui.action_Advance_Frame->setEnabled(false);
|
ui.action_Advance_Frame->setEnabled(false);
|
||||||
ui.action_Capture_Screenshot->setEnabled(false);
|
ui.action_Capture_Screenshot->setEnabled(false);
|
||||||
render_window->hide();
|
render_window->hide();
|
||||||
|
loading_screen->hide();
|
||||||
|
loading_screen->Clear();
|
||||||
if (game_list->isEmpty())
|
if (game_list->isEmpty())
|
||||||
game_list_placeholder->show();
|
game_list_placeholder->show();
|
||||||
else
|
else
|
||||||
|
@ -1326,6 +1347,10 @@ void GMainWindow::OnStopGame() {
|
||||||
ShutdownGame();
|
ShutdownGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnLoadComplete() {
|
||||||
|
loading_screen->OnLoadComplete();
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMenuReportCompatibility() {
|
void GMainWindow::OnMenuReportCompatibility() {
|
||||||
if (!Settings::values.citra_token.empty() && !Settings::values.citra_username.empty()) {
|
if (!Settings::values.citra_token.empty() && !Settings::values.citra_username.empty()) {
|
||||||
CompatDB compatdb{this};
|
CompatDB compatdb{this};
|
||||||
|
|
|
@ -32,6 +32,7 @@ class GraphicsVertexShaderWidget;
|
||||||
class GRenderWindow;
|
class GRenderWindow;
|
||||||
class IPCRecorderWidget;
|
class IPCRecorderWidget;
|
||||||
class LLEServiceModulesWidget;
|
class LLEServiceModulesWidget;
|
||||||
|
class LoadingScreen;
|
||||||
class MicroProfileDialog;
|
class MicroProfileDialog;
|
||||||
class MultiplayerState;
|
class MultiplayerState;
|
||||||
class ProfilerWidget;
|
class ProfilerWidget;
|
||||||
|
@ -75,6 +76,7 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void OnAppFocusStateChanged(Qt::ApplicationState state);
|
void OnAppFocusStateChanged(Qt::ApplicationState state);
|
||||||
|
void OnLoadComplete();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
@ -221,6 +223,7 @@ private:
|
||||||
GRenderWindow* render_window;
|
GRenderWindow* render_window;
|
||||||
|
|
||||||
GameListPlaceholder* game_list_placeholder;
|
GameListPlaceholder* game_list_placeholder;
|
||||||
|
LoadingScreen* loading_screen;
|
||||||
|
|
||||||
// Status bar elements
|
// Status bar elements
|
||||||
QProgressBar* progress_bar = nullptr;
|
QProgressBar* progress_bar = nullptr;
|
||||||
|
|
Reference in New Issue