From 823b23f8004dc8f9493bcfeee4a930ece036c204 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 22 Apr 2023 12:15:19 +0200 Subject: [PATCH] Qt: Lazy load game grid icons and optimize paint method for invisible items --- rpcs3/rpcs3qt/game_list_frame.cpp | 165 +++++++++------------- rpcs3/rpcs3qt/game_list_frame.h | 3 +- rpcs3/rpcs3qt/game_list_grid.cpp | 5 +- rpcs3/rpcs3qt/game_list_grid_delegate.cpp | 27 +++- rpcs3/rpcs3qt/movie_item.cpp | 4 +- rpcs3/rpcs3qt/movie_item.h | 4 +- 6 files changed, 103 insertions(+), 105 deletions(-) diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 04cd8a3163..a058757b2e 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -149,10 +149,9 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std m_serials.clear(); m_games.pop_all(); }); - connect(&m_repaint_watcher, &QFutureWatcher::finished, this, &game_list_frame::OnRepaintFinished); connect(this, &game_list_frame::IconReady, this, [this](movie_item* item) { - if (!m_is_list_layout || !item) return; + if (!item) return; item->call_icon_func(); }); connect(this, &game_list_frame::SizeOnDiskReady, this, [this](const game_info& game) @@ -882,29 +881,6 @@ void game_list_frame::OnRefreshFinished() } } -void game_list_frame::OnRepaintFinished() -{ - if (!m_is_list_layout) - { - // The game grid needs to be recreated from scratch - int games_per_row = 0; - - if (m_icon_size.width() > 0 && m_icon_size.height() > 0) - { - games_per_row = width() / (m_icon_size.width() + m_icon_size.width() * m_game_grid->getMarginFactor() * 2); - } - - const int scroll_position = m_game_grid->verticalScrollBar()->value(); - PopulateGameGrid(games_per_row, m_icon_size, m_icon_color); - connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); - connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot); - connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot); - m_central_widget->addWidget(m_game_grid); - m_central_widget->setCurrentWidget(m_game_grid); - m_game_grid->verticalScrollBar()->setValue(scroll_position); - } -} - void game_list_frame::OnCompatFinished() { for (const auto& game : m_game_data) @@ -2409,7 +2385,7 @@ void game_list_frame::RepaintIcons(const bool& from_settings) QPixmap placeholder(m_icon_size); placeholder.fill(Qt::transparent); - for (auto& game : m_game_data) + for (game_info& game : m_game_data) { game->pxmap = placeholder; @@ -2417,46 +2393,7 @@ void game_list_frame::RepaintIcons(const bool& from_settings) { item->set_icon_load_func([this, game, cancel = item->icon_loading_aborted()]() { - if (cancel && cancel->load()) - { - return; - } - - static std::unordered_set warn_once_list; - static shared_mutex s_mtx; - - if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(qstr(game->info.icon_path)))) - { - if (game_list_log.warning) - { - bool logged = false; - { - std::lock_guard lock(s_mtx); - logged = !warn_once_list.emplace(game->info.icon_path).second; - } - - if (!logged) - { - game_list_log.warning("Could not load image from path %s", sstr(QDir(qstr(game->info.icon_path)).absolutePath())); - } - } - } - - if (!game->item || (cancel && cancel->load())) - { - return; - } - - const QColor color = getGridCompatibilityColor(game->compat.color); - { - std::lock_guard lock(game->item->pixmap_mutex); - game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color); - } - - if (!cancel || !cancel->load()) - { - Q_EMIT IconReady(game->item); - } + IconLoadFunction(game, cancel); }); item->call_icon_func(); } @@ -2471,37 +2408,26 @@ void game_list_frame::RepaintIcons(const bool& from_settings) // Shorten the last section to remove horizontal scrollbar if possible m_game_list->resizeColumnToContents(gui::column_count - 1); - - return; } - - const std::function func = [this](const game_info& game) -> movie_item* + else { - static std::unordered_set warn_once_list; - static shared_mutex s_mtx; + // The game grid needs to be recreated from scratch + int games_per_row = 0; - if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(qstr(game->info.icon_path)))) + if (m_icon_size.width() > 0 && m_icon_size.height() > 0) { - if (game_list_log.warning) - { - bool logged = false; - { - std::lock_guard lock(s_mtx); - logged = !warn_once_list.emplace(game->info.icon_path).second; - } - - if (!logged) - { - game_list_log.warning("Could not load image from path %s", sstr(QDir(qstr(game->info.icon_path)).absolutePath())); - } - } + games_per_row = width() / (m_icon_size.width() + m_icon_size.width() * m_game_grid->getMarginFactor() * 2); } - const QColor color = getGridCompatibilityColor(game->compat.color); - game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color); - return game->item; - }; - m_repaint_watcher.setFuture(QtConcurrent::mapped(m_game_data, func)); + const int scroll_position = m_game_grid->verticalScrollBar()->value(); + PopulateGameGrid(games_per_row, m_icon_size, m_icon_color); + connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); + connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot); + connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot); + m_central_widget->addWidget(m_game_grid); + m_central_widget->setCurrentWidget(m_game_grid); + m_game_grid->verticalScrollBar()->setValue(scroll_position); + } } void game_list_frame::SetShowHidden(bool show) @@ -2647,7 +2573,10 @@ void game_list_frame::PopulateGameList() icon_item->set_icon_func([this, icon_item, game](int) { - ensure(icon_item && game); + if (!icon_item || !game) + { + return; + } if (std::shared_ptr movie = icon_item->movie(); movie && icon_item->get_active()) { @@ -2821,7 +2750,7 @@ void game_list_frame::PopulateGameGrid(int maxCols, const QSize& image_size, con const QString game_icon_path = m_play_hover_movies ? qstr(fs::get_config_dir() + "/Icons/game_icons/") : ""; - for (const auto& app : matching_apps) + for (const game_info& app : matching_apps) { const QString serial = qstr(app->info.serial); const QString title = m_titles.value(serial, qstr(app->info.name)); @@ -2831,6 +2760,10 @@ void game_list_frame::PopulateGameGrid(int maxCols, const QSize& image_size, con ensure(item); app->item = item; item->setData(gui::game_role, QVariant::fromValue(app)); + item->set_icon_load_func([this, app, cancel = item->icon_loading_aborted()]() + { + IconLoadFunction(app, cancel); + }); if (!notes.isEmpty()) { @@ -3032,10 +2965,52 @@ std::string game_list_frame::GetGameVersion(const game_info& game) return game->info.app_ver; } +void game_list_frame::IconLoadFunction(game_info game, std::shared_ptr> cancel) +{ + if (cancel && cancel->load()) + { + return; + } + + static std::unordered_set warn_once_list; + static shared_mutex s_mtx; + + if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(qstr(game->info.icon_path)))) + { + if (game_list_log.warning) + { + bool logged = false; + { + std::lock_guard lock(s_mtx); + logged = !warn_once_list.emplace(game->info.icon_path).second; + } + + if (!logged) + { + game_list_log.warning("Could not load image from path %s", sstr(QDir(qstr(game->info.icon_path)).absolutePath())); + } + } + } + + if (!game->item || (cancel && cancel->load())) + { + return; + } + + const QColor color = getGridCompatibilityColor(game->compat.color); + { + std::lock_guard lock(game->item->pixmap_mutex); + game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color); + } + + if (!cancel || !cancel->load()) + { + Q_EMIT IconReady(game->item); + } +} + void game_list_frame::WaitAndAbortRepaintThreads() { - gui::utils::stop_future_watcher(m_repaint_watcher, true); - for (const game_info& game : m_game_data) { if (game && game->item) diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index ad817b4a3a..994053b752 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -80,7 +80,6 @@ public Q_SLOTS: private Q_SLOTS: void OnRefreshFinished(); - void OnRepaintFinished(); void OnCompatFinished(); void OnColClicked(int col); void ShowContextMenu(const QPoint &pos); @@ -102,6 +101,7 @@ protected: private: QPixmap PaintedPixmap(const QPixmap& icon, bool paint_config_icon = false, bool paint_pad_config_icon = false, const QColor& color = QColor()) const; QColor getGridCompatibilityColor(const QString& string) const; + void IconLoadFunction(game_info game, std::shared_ptr> cancel); /** Sets the custom config icon. Only call this for list title items. */ void SetCustomConfigIcon(QTableWidgetItem* title_item, const game_info& game); @@ -174,7 +174,6 @@ private: QMutex m_games_mutex; lf_queue m_games; QFutureWatcher m_refresh_watcher; - QFutureWatcher m_repaint_watcher; QSet m_hidden_list; bool m_show_hidden{false}; diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index bf581d23ce..daa1e56ea8 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -66,7 +66,10 @@ movie_item* game_list_grid::addItem(const game_info& app, const QString& name, c item->set_icon_func([this, app, item](int) { - ensure(item); + if (!item) + { + return; + } const qreal device_pixel_ratio = devicePixelRatioF(); diff --git a/rpcs3/rpcs3qt/game_list_grid_delegate.cpp b/rpcs3/rpcs3qt/game_list_grid_delegate.cpp index 1ef4ef096e..52a0972133 100644 --- a/rpcs3/rpcs3qt/game_list_grid_delegate.cpp +++ b/rpcs3/rpcs3qt/game_list_grid_delegate.cpp @@ -1,4 +1,7 @@ #include "game_list_grid_delegate.h" +#include "movie_item.h" + +#include game_list_grid_delegate::game_list_grid_delegate(const QSize& size, const qreal& margin_factor, const qreal& text_factor, QObject *parent) : QStyledItemDelegate(parent), m_size(size), m_margin_factor(margin_factor), m_text_factor(text_factor) @@ -23,13 +26,31 @@ void game_list_grid_delegate::paint(QPainter* painter, const QStyleOptionViewIte painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); painter->eraseRect(r); + // Paint from our stylesheet + QStyledItemDelegate::paint(painter, option, index); + + // Check if the item is visible + if (const QTableWidget* table = static_cast(parent())) + { + if (movie_item* item = static_cast(table->item(index.row(), index.column()))) + { + if (!table->visibleRegion().intersects(table->visualItemRect(item))) + { + // Skip all further actions if the item is not visible + return; + } + + if (!item->icon_loading()) + { + item->call_icon_load_func(); + } + } + } + // Get title and image const QPixmap image = qvariant_cast(index.data(Qt::DecorationRole)); const QString title = index.data(Qt::DisplayRole).toString(); - // Paint from our stylesheet - QStyledItemDelegate::paint(painter, option, index); - // image if (image.isNull() == false) { diff --git a/rpcs3/rpcs3qt/movie_item.cpp b/rpcs3/rpcs3qt/movie_item.cpp index bbb01a7f31..7c54f247c0 100644 --- a/rpcs3/rpcs3qt/movie_item.cpp +++ b/rpcs3/rpcs3qt/movie_item.cpp @@ -129,7 +129,7 @@ void movie_item::set_size_calc_func(const size_calc_callback_t& func) void movie_item::wait_for_icon_loading(bool abort) { - if (m_icon_load_thread) + if (m_icon_load_thread && m_icon_load_thread->isRunning()) { *m_icon_loading_aborted = abort; m_icon_load_thread->wait(); @@ -139,7 +139,7 @@ void movie_item::wait_for_icon_loading(bool abort) void movie_item::wait_for_size_on_disk_loading(bool abort) { - if (m_size_calc_thread) + if (m_size_calc_thread && m_size_calc_thread->isRunning()) { *m_size_on_disk_loading_aborted = abort; m_size_calc_thread->wait(); diff --git a/rpcs3/rpcs3qt/movie_item.h b/rpcs3/rpcs3qt/movie_item.h index 8c73cba4a3..96888672a5 100644 --- a/rpcs3/rpcs3qt/movie_item.h +++ b/rpcs3/rpcs3qt/movie_item.h @@ -61,12 +61,12 @@ public: return m_size_on_disk_loading; } - std::shared_ptr> icon_loading_aborted() const + [[nodiscard]] std::shared_ptr> icon_loading_aborted() const { return m_icon_loading_aborted; } - std::shared_ptr> size_on_disk_loading_aborted() const + [[nodiscard]] std::shared_ptr> size_on_disk_loading_aborted() const { return m_size_on_disk_loading_aborted; }